三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

用AI做自动化测试,哪些是真不行,哪些是你不会用?

用AI做自动化测试,哪些是真不行,哪些是你不会用?

在测试圈子里待久了,你会反复听到同样几种对 AI 测试的抱怨。

有些抱怨,是真的。AI 现阶段确实干不好,硬上也没用。

还有一些,说白了是自己基本功没到家,自身技能不足不自知,硬要锅甩给了"AI "。老实讲,这种我一点都不同情。

但更多的时候其实是第三种——大多数人处在半吊子水平:你说他不会吧,能让 AI 跑出点东西;你说他会吧,脚本三天两头崩。Prompt 写两句就完事,业务规则讲不清楚,可测性基建也懒得搞,AI 自然时灵时不灵,今天跑通明天就废。这种真不能怪 AI,是自己没学到家。

今天咱们来掰开聊一聊。

AI 也能做,但做的不够好的几件事

这一类是我重点想聊的。AI 确实能上手——能产出代码、能跑通流程、能交付一个看起来像那么回事的 Demo。但你真拿它去测生产业务,就会发现它只做到了 60 分,剩下那 40 分还得靠人补。下面这几件,就是典型的"能做,但做不好"。

1. 业务断言

"业务断言"听着就是一句 assert,但真正卡人的从来不是语法,是"断什么"。

要断的不是接口返回 200,是业务结果对不对——订单金额算对没、风控规则触发对没、积分发放对没。这一层,很多时候,连人工都不一定能讲得很不清楚,更何况是想让AI来做好 。

举个促销的例子:满 300 减 50,会员再打 8 折,还能叠加一张 20 元优惠券。

让 AI 点完一个下单流程没问题。但你让它判断"这个订单的最终金额对不对"——它开始瞎猜了。

AI 能写出这种:

# AI 默认输出:通用的接口断言
def test_create_order():resp = client.post("/api/order", json=payload)assert resp.status_code == 200assert resp.json()["order_id"] is not None

但真正要验的,是这个:

# 真正的业务断言——AI 写不出来
data = resp.json()
# 满减 → 折扣 → 券,顺序不能错
expected = (300 - 50) * 0.8 - 20
assert data["final_price"] == round(expected, 2)
assert data["discount_sequence"] == "fullcut_discount_coupon"
# VIP 叠加优惠会触发风控规则
assert data["risk"]["triggered_rules"] == ["RULE_VIP_OVERLAP"]
# 积分 = 实付金额 × 会员积分系数
assert data["points_earned"] == int(expected * member_point_ratio)

第二段代码里的每一行,都需要知道促销规则、风控规则、积分规则。这些不是文档里读一遍就能抓住的——是在需求池里泡、跟产品吵、上线翻车翻出来的。

这一块,AI 替代不了,别浪费时间硬塞。

2. 复杂异步链路验证

"异步链路验证"听着抽象,场景却很常见——用户点个按钮,背后牵动的是一整条链路。下单扣库存、库存扣了发消息、消息触发推物流、物流到了回写状态。中间任何一环异步挂掉,前端照样显示"成功"。

AI 的问题在哪?它只看得见同步返回那一秒,背后那一串异步发生了什么,它压根不知道。

举个真实场景:下单 → 扣库存 → 生成订单 → 触发消息 → 推送物流 → 更新状态。一条链路穿六个系统、三张表、两个消息队列。

AI 默认只会写这种:

# AI 写的:只验了同步返回
def test_order_flow():resp = client.post("/api/order", json=payload)assert resp.status_code == 200print("✅ 订单创建成功")

看起来过了,实际上真正会出问题的全是异步环节:

# 真正要验的——AI 不会主动写
assert resp.status_code == 200
order_id = resp.json()["order_id"]# 1. 订单创建消息真的发出了
msg = consume_mq("order.created", timeout=5)
assert msg["order_id"] == order_id# 2. 库存真的扣了
assert db.query("SELECT stock FROM sku WHERE id=%s", sku_id) == origin_stock - 1# 3. 物流队列收到了推送
assert mq_count("logistics.push") == 1# 4. 订单状态在 5s 内流转到 PUSHED
wait_until(lambda: order_status(order_id) == "PUSHED", timeout=5)# 5. 幂等:重复调用不会重复扣库存
client.post("/api/order", json=payload)
assert db.query("SELECT stock FROM sku WHERE id=%s", sku_id) == origin_stock - 1

中间任何一环异步失败,AI 都看不见。你以为它过了,库存早超卖了。

这种链路,目前没有绝对银弹。靠人先梳理清楚规则、靠监控、靠对账,AI 进来最多是个跑腿的。

3. 性能根因分析

性能问题,难的不是"发现慢"——加个埋点、配个阈值告警,谁都会。难的是"为什么慢"。

慢在 SQL?慢在连接池?慢在下游?慢在 GC?还是 Redis 里藏了个大 key?这背后靠的是经验、是排查路径、是对整套系统的理解。AI 能报信儿说"接口慢了",但真要往根因深挖,它顶多算个新手。

AI 能告诉你"接口慢了 800ms",也能告诉你"SQL 没走索引"。

# AI 能做的:阈值告警
def test_api_perf():resp = client.get("/api/products")assert resp.elapsed.total_seconds() < 0.5

但真正的根因定位,它干不了:

# 下面这套,AI 能辅助但不能独立完成
1. 看火焰图 → 70% 时间卡在 redis_cmd
2. 查慢日志 → 发现一个 KEYS * 扫描
3. 查监控曲线 → GC 时间同步上涨
4. 结合经验 → 判断是连接池配置 + 大 key 共同导致
5. 给出方案 → 业务侧拆 key,运维侧调连接池参数

报信儿归它,破案而得要依靠人来做。

AI 其实可以,但你不会用的几件事

接下来说第二类——这一类跟前面正好相反。下面这几件事,AI 其实完全能做,做得也不差。但凡你觉得"AI 在这块不行",十有八九问题不在 AI,在于你——要么是应用的可测性没做好,要么是 Prompt 没讲清楚,要么是基建没搭扎实。简单说,这一类的锅,多半得你自己背。

1. 元素定位

先说个事实——现在主流的 AI(Claude、Cursor、Copilot)写 Playwright 代码,默认就用 get_by_roleget_by_label;写 Appium 默认就用 accessibility id绝对 XPath 早就不是 AI 的默认选择了。

那为什么你的 UI 脚本还是崩?问题不在 AI 选什么定位策略,在你应用根本没给 AI 留可用的"锚点"。

AI 默认写出来的代码,现在长这样:

# ✅ Playwright:AI 默认就用智能定位,根本不写 XPath
page.get_by_role("button", name="提交订单").click()
page.get_by_label("手机号").fill("13800138000")
# ✅ Appium:AI 也默认走 accessibility id
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login_button").click()
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "phone_input").send_keys("13800138000")

代码看着没问题,一跑就崩。为什么?因为你应用长这样:

<!-- 网页:button 没 aria-label,input 没 label -->
<button class="btn-primary">提交</button>
<input type="text" placeholder="手机号" />
# App:所有元素的 ContentDescription 都叫 "button"
# AI 想抓 accessibility id,抓出来全是同一个名字

AI 想 get_by_role("button", name="提交订单"),结果你页面里同时有 5 个"提交"按钮;想 ACCESSIBILITY_ID("login_button"),结果你 App 压根没设这个标签。

这不是 AI 写得烂,是你应用的可测性烂。

新一代 Agent 工具(agent-device、mobilerun)也是同一个逻辑——它们基于无障碍树给元素分配语义引用:

$ agent-device snapshot
# @e1 [button] "提交订单"
# @e2 [text-field] "手机号"$ agent-device fill @e2 "13800138000"
$ agent-device click @e1

但前提还是那句话——你 App 得有无障碍树可读。把可测性基建做好,AI 找元素的稳定性立刻上一个台阶。不做这件事,换什么模型来都白搭。

2. 测试数据

"测试数据"是抱怨重灾区——"AI 生成的数据全是 test123、张三李四,一点不真实"。

但你先别急着骂。AI 给你 test123,是因为它脑子里只有 test123 该有的信息量。你跟它说"生成 10 个用户",它不知道你的业务、不知道沙箱号段、不知道风控规则——除了 test1 到 test10,它还能给你啥?

❌ 你的 Prompt:

"生成 10 个测试用户"

AI 老老实实输出:

test1, test2, test3 ... test123, password123

你把规则说清楚,立刻不一样:

✅ 你应该写的 Prompt:

"""
生成 10 个测试用户,要求:
- 用户分三类:普通用户 5 个、VIP 3 个、商家账户 2 个
- 手机号以 138/139/188 开头(沙箱号段)
- VIP 账户 level 取值 3-5,余额 1000-50000
- 商家账户必须有 shop_id 且 status='active'
- 避开风控:同一手机号 24h 内不超过 3 次注册
- 每个用户带一个 profile:注册时间分散在最近 30 天
"""

按这个 Prompt 生成的数据,能进沙箱、能跑通业务流程、能触发各种边界场景。

AI 是个实习生。你不给需求文档,它只能给你 test123。

3. 脚本生成

让 AI 写测试代码,它默认产出的是线性脚本——从头到尾一路堆,能跑就行。

# ❌ AI 默认输出:线性脚本,能跑但难维护
def test_order():client.post("/login", json=cred)resp = client.post("/cart", json={"sku": "A1"})assert resp.status_code == 200resp2 = client.post("/order")assert resp2.status_code == 200resp3 = client.get("/order/" + order_id)# ... 一路堆下去,没有分层,没有复用

能跑,但问题一堆:步骤之间强耦合、登录流程每个用例都重写一遍、改一个接口满地回归、跑完留一堆脏数据。

这不是 AI 写得烂,是你没告诉它该按什么框架写。

你给它规则,立刻不一样:

✅ 你应该给 AI 的规则:
"""
按 Page Object 模式生成,要求:
- 页面/服务封装成类(LoginPage、OrderService)
- 业务动作封装成方法(login()、create_order())
- 测试数据用 fixture 注入,不写死在脚本里
- 每个 test 函数遵循 Arrange-Act-Assert 三段式
- 用 pytest 框架,支持参数化
- 公共步骤抽成 fixture,便于复用
"""

按这个规则生成的代码,长这样:

# ✅ AI 按规则输出:分层、可复用、好维护
class OrderService:def __init__(self, client):self.client = clientdef create(self, payload):return self.client.post("/order", json=payload)@pytest.mark.parametrize("user,expected", [(UserData.vip(), "VIP_DISCOUNT"),(UserData.normal(), "NORMAL"),
])
def test_order_discount(auth_client, order_service):# Arrangeorder_data = OrderBuilder().with_user(user).build()# Actresp = order_service.create(order_data)# Assertassert resp.json()["discount_type"] == expected

AI 默认产出的是"能跑的代码",不是"能维护的代码"。前者靠一句 Prompt,后者得靠你给规则、给框架、给范式。

4. 等待策略

自动化测试里有一种特别折磨人的问题——flaky test。同一个脚本,这次跑过了,下次又莫名崩了,重跑一次又过,查都没法查。

但你打开那些 flaky 的脚本看一眼,十有八九都是同一个问题——等待没做好

AI 写测试脚本,默认产出的都是这种代码:

# ❌ AI 默认输出——这是 flaky 的万恶之源
time.sleep(1)
driver.find_element(By.ID, "submit").click()

time.sleep(1)——多么朴实无华,多么糟糕。

为什么糟糕?因为它在等时间,不在等"该等的东西"。

环境快的时候,0.3 秒就加载完了,你 sleep 1 秒,浪费 0.7 秒;环境慢的时候,2 秒才加载完,你 sleep 1 秒,元素还没出来就点了——崩。

崩了你肯定想:"那我把 sleep 加到 3 秒不就行了?"

# ❌ 更糟的"修复"
time.sleep(3)
driver.find_element(By.ID, "submit").click()

恭喜你,现在每个用例多浪费 2 秒,跑 100 个用例多花 3 分钟,跑 1000 个用例多花半小时。更狠的是——该崩的时候还是会崩,因为总有人网速比你想象的更差。

正确的思路反过来:别等时间,等信号。

页面给我们的信号其实很多——loading 消失了、骨架屏消失了、某个 DOM 出现了、URL 跳转了、window 变量就绪了。等这些"业务信号",比拍脑袋等 1 秒 3 秒靠谱多了。

# ✅ Playwright:等业务信号,不等时间
# 等加载动画消失
page.wait_for_selector("[data-loading]", state="hidden")
page.get_by_role("button", name="提交").click()# 等 URL 跳转
page.wait_for_url("**/dashboard")# 等前端信号量就绪
page.wait_for_function("() => window.appReady === true")
# ✅ Appium:WebDriverWait 显式等待
# 等元素出现
WebDriverWait(driver, 10).until(EC.presence_of_element_located((AppiumBy.ID, "content_loaded"))
)# 也能等自定义条件——比如购物车数量变成非 0
WebDriverWait(driver, 10).until(lambda d: d.find_element(AppiumBy.ID, "cart_count").text != "0"
)

你看,等的是"loading 消了""URL 跳了""count 变了"——这些是业务上"真的准备好了"的信号,而不是拍脑袋的 1 秒 3 秒。

但问题是——AI 不会主动这么写

它不知道你那个列表是异步加载的,不知道点击之后会有 loading,不知道弹窗有 300 毫秒动画,不知道提交成功会跳到 /dashboard。这些"业务时序"信息,得你在 Prompt 里告诉它:

✅ 你应该在 Prompt 里加这几句:
"""
时序约定:
- 列表是异步加载,等到骨架屏消失再操作
- 点击"提交"后会出现 loading,等到 loading 消失再断言
- 弹窗有 300ms 进入动画,别在动画期间点按钮
- 提交成功会跳转到 /dashboard,等到 URL 变化再继续
"""

AI 按这个 Prompt 写出来的代码,立刻就稳了。

等待策略这事,本质是测试的基本功。AI 不会主动判断"什么时候该等、等什么",你得在 Prompt 里把"业务时序"讲清楚。这件事不讲明白,换什么模型来都救不了你的 flaky test。

最后啰嗦两句

写到这,回到开头那个问题——AI 做自动化测试,到底行不行?

我的回答是:行,但看你怎么用。

业务断言、异步链路、性能根因——这几件事,AI 现阶段真的做不好。让它在这些地方当主力,是难为它,也是难为自己。让它跑跑腿、提提效、辅助一下人就够了。

元素定位、测试数据、脚本生成、等待策略——这几件事,AI 真的能做。但凡你觉得"不行",先别急着骂,回头问自己三个问题:

  • 你的应用有没有给 AI 留可用的"锚点"(无障碍标签、testid、aria-label)?
  • 你的 Prompt 有没有把业务规则、时序、边界讲清楚?
  • 你的测试基建有没有搭扎实(数据工厂、环境隔离、代码框架)?

这三件事都没做好,换什么模型来都白搭。

至于那些喊着"AI 全自动替代测试工程师"的——醒醒,那是推销话术。

说到底,AI 做自动化测试这件事,考验的从来不是 AI 有多强,是用 AI 的人,测试基本功扎不扎实

一个连可测性、断言、等待策略都讲不清楚的测试,配什么模型都救不了;

会用 AI 的测试,迟早会让不会用的失业。 这句话我说的。

下次你想骂"AI 不行"之前,先回头看看自己的 Prompt 和代码基建。那才是问题的根源。

← 返回列表