寻序集开篇 · AI × 测试开发实验记录 #001
副标题:AI 写接口测试的真实水平,一次完整实测(接口自动化测试 · pytest · AI 生成测试用例)

上周三,我让大模型帮我写接口自动化测试用例。15 分钟,它交给我 40 个用例。

但在逐条跑之前,我发现了它写的一段"神操作"代码:它给 GitHub 写了一个「删除用户」的用例,调用的接口叫 DELETE /users/{username}——而 GitHub API 里根本没有这个接口。用户只能在自己网页端删自己的账号。它编得一本正经,仿佛真有这个接口一样。

先问你一个问题:你觉得这 40 个用例里,真正能直接用的有几个?

我逐条跑完后的实测结果:31 个能用,6 个要改,3 个必须扔掉。

这篇 pytest 接口测试实战记录,把 AI 生成测试用例的整个过程摊开给你看:

  • 我设计 Prompt 的全过程(不是简单丢一句话,是 3 版演进到最终版)
  • 它产出的代码长什么样(好的坏的都给你)
  • 3 个翻车案例逐个拆解(它到底错在哪、为什么错)
  • 我改完后的完整代码(可以直接跑)

读完你会得到一套「AI 写接口测试的正确姿势」。 下次你再让 AI 写测试,至少能少踩我今天踩过的坑。


我是寻序集,一名大厂测试开发工程师,入行做测开 1.5 年,目前负责一个 3 万用例的自动化测试体系。

解释一下为什么叫"寻序集"。做测试这行久了你会发现,这份工作的本质,就是在一个看似混乱的系统里寻找秩序:接口为什么偶发失败?压测数据为什么忽高忽低?AI 写的代码为什么时对时错?

我们每天干的事,就是在这堆无序里,找出那个"有序"。

这个账号,我打算认真做一年。为了能坚持,我给自己定了三条铁律:

  1. 每周 3 篇,雷打不动:周一、周三《AI × 测试开发实验室》,周五《测试开发工程化》
  2. 每篇真实可复现:代码能跑、数据能对,不写"看起来对"的假教程
  3. 合规透明:全部用公开接口和通用方法,不涉及任何公司内部信息

这是第 001 号实验记录。开始。


一、为什么选 GitHub API 做实验

先说被测对象。

选它有三个理由:

  1. 公开免费:search 类接口无需认证就能调用,谁都能复现(本文所有代码我用真实 API 逐条验证过)
  2. 稳定有文档:接口文档完整,不会今天好明天坏
  3. 场景丰富:正常、空结果、参数错误、限流……该有的边界它都有

我要覆盖的核心接口和场景:

接口正常场景边界/异常场景
GET /search/repositories200 返回搜索结果无匹配返回空结果、缺 q 返回 422
GET /search/users200 返回用户列表同上
GET /rate_limit200 返回限流详情字段结构易断言错

目标很明确:让 AI 产出一套能直接跑的接口自动化用例。

二、Prompt 是怎么设计出来的(3 版演进)

第 1 版:最省事的写法

帮我写 GitHub API 的接口自动化测试。

结果:结构对了一半,但问题一堆——没有 fixture、断言写死了具体值(数据一变就挂)、完全没有边界场景、限流不处理。可用率不到 30%。

这版实验我 5 分钟就扔了。它证明了一个规律:越模糊的要求,产出越废。

第 2 版:加上技术约束

用 Python + requests + pytest 写 GitHub API 的接口自动化测试,
要参数化,要 fixture,断言响应结构。

结果:框架对了,参数化、fixture 都来了。但边界场景开始"编"——它自己发明了几个不存在的接口,限流处理也是错的。可用率大约 50%。

这版证明了第二个规律:技术约束能纠正"写法",但纠正不了"认知"。

第 3 版:最终版(可直接抄)

我给 AI 的最终版 Prompt 长这样:

你是资深测试开发工程师。请用 Python + requests + pytest 为 GitHub 公开 API
编写一套可运行的接口自动化测试用例。

被测接口(search 类接口无需认证,均可直接调用):
1. GET https://api.github.com/search/repositories?q={query}&per_page=10 —— 搜索仓库
2. GET https://api.github.com/search/users?q={query} —— 搜索用户
3. GET https://api.github.com/rate_limit —— 查看限流详情

要求:
1. pytest 组织用例,使用 fixture 复用 HTTP Session,使用参数化
2. 每个接口至少覆盖:200 正常、空结果边界、参数缺失 三个场景
3. 断言响应结构(关键字段存在性与类型),不要断言具体值
4. GitHub 存在 rate limit(未认证 10 次/分钟):请求前检查 X-RateLimit-Remaining 头,
   余额为 0 时使用 pytest.skip 跳过,不要误报失败
5. 搜索接口的 q 参数必须支持中文,使用 requests 的 params 传参,不要手动拼 URL
6. 输出完整可运行代码 + requirements.txt + 一条运行命令

注意第 4、5 条——这两条是我故意加进去的"标准约束",因为我知道 AI 大概率会在这两处翻车(后面的实测验证了)。

Prompt 设计心法(3 条):

  1. 给角色:让它以"资深测试开发工程师"身份工作,产出质量明显不同
  2. 给边界:明确被测对象、场景清单、规则——信息密度决定可用率
  3. 给反例预判:把你知道的坑(限流、中文参数)提前写进去,让它别踩

三、AI 的首版产出(节选)

15 分钟后,它交出了首版。核心代码长这样:

# AI 首版代码(节选)—— test_github_api.py
import pytest
import requests

BASE_URL = "https://api.github.com"

@pytest.fixture(scope="module")
def session():
    s = requests.Session()
    s.headers.update({"Accept": "application/vnd.github+json"})
    return s

@pytest.mark.parametrize("query", ["pytest", "requests"])
def test_search_repositories(session, query):
    resp = session.get(f"{BASE_URL}/search/repositories",
                       params={"q": query})
    assert resp.status_code == 200
    body = resp.json()
    assert body["total_count"] == 100   # 断言写死具体值
    assert len(body["items"]) >= 5

第一反应是惊喜的:fixture、参数化、Session 复用、结构断言——模式化的东西它一次就写对了。 这部分它确实很强,不吹不黑。

四、实测结果:哪些能用、哪些要改、哪些扔了

✅ 直接用:31/40

  • fixture / 参数化 / Session 复用,规范正确
  • 200 正常场景的基础断言逻辑没问题
  • 中文参数用 params 传参的处理是对的

🔧 要改的:6/40(挑 3 个典型的讲)

坑 1:断言写死了具体值

它写了 assert body["total_count"] == 100——GitHub 的搜索结果每天都在变,这断言今天过了,明天就挂。改法:断言结构不断言数值,比如 assert body["total_count"] >= 0 + assert isinstance(body["items"], list)

坑 2:没有覆盖"空结果"这个边界场景

最容易被 AI 忽略的场景:q=zzz_nonexistent_987 这种搜不到的查询,应该返回 200 + items=[]。AI 默认搜索"一定有结果",但测试最该测的就是"没有结果时系统表现正不正常"。这是测试意识的问题,不是写法问题。

坑 3:参数化数据写死

AI 把测试数据硬编码在代码里,我抽到了外部 JSON 做数据驱动,用例和数据分离,换数据不用改代码。

❌ 直接扔:3/40(逐个拆解)

废例 1:字段名是"假设"出来的

assert body["limit"] == 60

它断言 /rate_limit 的顶层 limit 字段——但我实际调用发现,真实响应结构是 resources.core.limit顶层根本没有 limit 这个字段(我验证过,确实如此)。这个用例一跑就挂,而且测不出任何东西——典型的"看起来很对"。

废例 2:编造了一个不存在的接口

就是开头那段"神操作":DELETE /users/{username}。GitHub API 文档里根本没有这个端点,用户只能在自己网页端删自己的账号。AI 会一本正经地编场景,这是最危险的地方——因为它编得太像真的了,外行根本看不出来。

废例 3:用例之间逻辑自相矛盾

它既写了"403 一律是权限不足"的用例,又写了限流处理的用例(限流也返回 403)——两处逻辑冲突。它不会发现自己写的用例之间互相矛盾——这也是为什么必须有"人"在最后兜底。

五、我改完后的完整代码(可直接跑,未认证模式)

这份代码我逐条在真实 GitHub API 上验证过,保存下来,下次让 AI 写测试直接套用。

你保存为 test_github_api.py,装好依赖就能跑(无需 token):

# test_github_api.py —— 修改后完整版
import pytest
import requests

BASE_URL = "https://api.github.com"
RATE_LIMIT_HEADER = "X-RateLimit-Remaining"

@pytest.fixture(scope="module")
def session():
    s = requests.Session()
    s.headers.update({"Accept": "application/vnd.github+json"})
    return s

def _check_rate_limit(resp):
    """限流保护:余额为 0 才跳过,否则真实失败"""
    remaining = resp.headers.get(RATE_LIMIT_HEADER)
    if remaining is not None and int(remaining) == 0:
        pytest.skip("GitHub rate limit exhausted")
    return resp

@pytest.mark.parametrize("query", ["pytest", "接口自动化", "selenium"])
def test_search_repositories_success(session, query):
    """正常搜索:200 + 结果结构正确(不断言具体值)"""
    resp = session.get(f"{BASE_URL}/search/repositories",
                       params={"q": query, "per_page": 5})
    _check_rate_limit(resp)
    assert resp.status_code == 200
    body = resp.json()
    assert "total_count" in body
    assert isinstance(body["items"], list)

@pytest.mark.parametrize("username", ["octocat", "torvalds"])
def test_search_users_success(session, username):
    """正常搜索用户:200 + 结构正确"""
    resp = session.get(f"{BASE_URL}/search/users", params={"q": username})
    _check_rate_limit(resp)
    assert resp.status_code == 200
    assert "items" in resp.json()

def test_search_empty_result(session):
    """空结果边界:搜索不存在的关键字,应返回 200 + 空列表"""
    resp = session.get(f"{BASE_URL}/search/repositories",
                       params={"q": "zzz_nonexistent_query_9876543210"})
    _check_rate_limit(resp)
    assert resp.status_code == 200
    body = resp.json()
    assert body["total_count"] == 0
    assert body["items"] == []

def test_search_missing_param(session):
    """参数缺失:缺 q 参数应返回 422"""
    resp = session.get(f"{BASE_URL}/search/repositories")
    _check_rate_limit(resp)
    assert resp.status_code == 422

def test_rate_limit_endpoint_structure(session):
    """限流接口:字段名必须按真实结构断言(顶层没有 limit!)"""
    resp = session.get(f"{BASE_URL}/rate_limit")
    _check_rate_limit(resp)
    assert resp.status_code == 200
    body = resp.json()
    assert "resources" in body
    assert "core" in body["resources"]
    assert "limit" in body["resources"]["core"]
# requirements.txt
requests==2.32.5
pytest==8.0.0
# 运行
pytest test_github_api.py -v

六、效果对比:人工 vs AI

维度人工写AI 写(首版)AI + 我修改后
耗时约 2 小时15 分钟15 分钟 + 40 分钟修改
用例数404040
可直接运行403140
关键分支覆盖85%60%85%
边界场景质量部分"看起来对"

结论很清楚:AI 把 2 小时压缩到 15 分钟,但剩下的 40 分钟修改时间,一分都不能省。 它省的是你写"模式化代码"的时间,省不了你"想清楚标准和边界"的时间。

注:以上为本次实验数据。GitHub 接口行为可能随官方调整而变化(比如现在未认证下 /users/{username} 已要求认证,我实测是 403,所以改用 search 类接口),你复现时跑出的用例数与翻车点可能有浮动——方法不变,数字会变。

七、我的 5 条总结(建议收藏)

1. AI 擅长搭框架,不擅长定标准。
pytest 结构、参数化、fixture 这些模式化内容,AI 一次就对。但"这个接口响应必须有哪些字段、什么状态码才算对"这种业务标准,必须你定、你审。我管着 3 万用例的体系,最怕的就是"看起来对"的用例混进来——它们不报错,但也不测任何东西。AI 写的是"看起来对",我要的是"确实对"。

2. Prompt 的信息密度 = 产出的可用率。
第一版只给一句话,可用率不到 30%;第三版给了完整边界,可用率 77%。这个数不是我编的,是三版实验跑出来的。你给 AI 的约束越多,它替你踩的坑越少。

3. 把 AI 当实习生,不当专家。
现在让 AI 写用例,我的心态就是:来了个干活特别快的实习生,方向必须我把关。限流判断、字段名、场景真实性——这三处它全错了,而这三处恰恰是测试的灵魂。用 AI 提效的前提是你自己先懂,否则你连它错在哪都看不出来。

4. AI 会一本正经地"编"。
它编了一个 GitHub 根本不存在的 DELETE /users/{username} 接口,还一脸确定。我后来翻 GitHub API 文档才确认这个端点从来就不存在。对 AI 产出的代码,先怀疑再相信。 最好的怀疑方式:让代码真的跑一遍。

5. 最好的搭档模式:AI 出初稿,你做"标准与边界"评审。
我的工作流现在很固定:AI 写 → 我审(标准、边界、场景真实性)→ 修 → 跑 → 入库。AI 负责速度,我负责质量。 省下的 2 小时,我用在真正值钱的地方——想清楚这次测试到底要守住什么。

转给正在把 AI 当专家用的同事看看——AI 写测试的真相:80% 很快,20% 是灵魂,而那 20% 恰好全错。

八、这个实验,我接下来还会做 3 次

这次是接口自动化场景。后面我计划继续实测:

  • #002:一套可复用的「AI 写测试 Prompt 模板」——把这次的经验固化成模板,你直接抄。评论区被问最多的就是这个,我优先做
  • #003:AI 生成测试数据的实测——批量造数据到底能不能信?跟上期"字段假设错、数据写死"的坑直接相关
  • #004:把 AI 生成的用例接入 CI,跑起来之后会发生什么

如果你也在让 AI 写测试,欢迎关注这个系列。我们一起来测一测:AI 到底能帮测试做到什么程度。

九、最后,给你一份小礼物

这次实验的完整 Prompt(3 版演进 + 最终版)我整理成了《AI 测试 Prompt 模板包》入门版,里面有:

  • 写接口用例的 Prompt
  • 生成 pytest 断言的 Prompt
  • 构造测试数据的 Prompt
  • 审查测试代码的 Prompt

你看到的那版只是 1/4。关注公众号【寻序集】,回复「模板」领取入门版。

(至于高配版——审查、造数、断言 3 套完整 Prompt——我还在认真打磨,会整理成付费模板包,上线时第一时间在公众号通知,不会让你错过。)

最后问一句:你平时会让 AI 帮你写测试吗?翻车过吗? 评论区聊聊,我每条都回——顺便告诉我你最想要哪个场景的模板,我优先做。


—— 寻序集 · 在无序中发现有序 ——

下周见。

更多推荐