让大模型写E2E测试:覆盖了90%场景但总漏极端情况,我们怎么补位

2026年4月29日

30秒速览

  • AI生成E2E测试确实快,但只擅长阳光大道,网络异常、状态组合、权限过期这类歪门邪道的场景它想都想不到。我们用自动化注入故障模板和人工审查兜底,把覆盖盲区填上了,而且把测试工程师从写脚本里解放出来,专注分析什么可能出错。别让AI替你猜想隐患,得你教它哪儿会炸,它再替你批量造雷管。

为什么我把手写Playwright用例的脚本丢了,转而押注GPT-4生成

去年年底,我们团队接手了一个电商平台的重构项目,前后端全部重写,测试用例要从零开始搭。按以往的做法,两个测试工程师全手工编写核心流程的端到端测试,少说也得花上三周。那时候老板天天催上线,我作为技术负责人,只能琢磨怎么把这个时间压下去。

我第一个念头就是用AI来生成。当时GPT-4刚出来不久,网上一片“AI写测试太香了”的声音。说实话我最初挺怀疑的——让一个大语言模型去理解一个动态Web应用的状态流转,再吐出能稳定运行的Playwright脚本,这事儿靠谱吗?但我还是拉了个小实验:把我们产品需求文档的几页关键流程描述(登录、搜索、加购、下单、支付)直接丢给GPT-4,让它在Prompt里生成Playwright测试文件。第一次跑出来的结果让我吃了一惊,生成的代码居然跑通了八成的用例,而且不少地方还知道用waitForSelector等待元素出现,避免了经典的“元素未找到”报错。那个效率实在太诱人,我们花三小时调整Prompt并微调了几个选择器之后,就跑通了一套覆盖整个下单链路的测试套件。如果纯手动写,至少得两天。

我当即决定:这个活儿,不能让AI完全取代人,但可以大幅把人的时间从枯燥的流水账脚本里解放出来。于是我们定了个策略:让GPT-4批量生成“快乐路径”与常规分支的测试用例,人工只负责审查和补极端场景。可当时我完全没想到,后面会被那些“极端场景”绊住脚。

为了说明当时的具体做法,我先简单列一下我们怎么调Prompt的。我们给GPT-4输入的不只是需求描述,还包括了DOM片段与API接口文档的简化版。Prompt模板大概是这样的(这里我略去实际业务细节):


你是一个资深测试工程师,需要为以下电商页面编写Playwright E2E测试。
页面URL:https://shop.example.com/product/{productId}
核心元素:
- 商品标题:h1.product-title
- 加入购物车按钮:button[data-testid="add-to-cart"]
- 购物车图标内的数量角标:span.cart-count
预期行为:
1. 用户访问商品页,能看到标题与价格
2. 点击“加入购物车”后,角标数字加1
3. 如果库存为0,按钮应显示“缺货”并不可点击

请生成测试文件,仅使用@playwright/test框架,包含必要的断言与等待。

AI基本能写出正确的结构,但真正的问题在后面:它会假设网络永远通畅、用户操作永远线性、库存永远大于0。这些假设在生成时看起来顺理成章,但一上集成环境就暴露了。

我们一开始用的是最傻的“全量生成、人工挑着改”模式。大概跑了三天,就发现一个规律:GPT-4对“显性边界”把握还不错。比如我在Prompt里明确提了库存为0的按钮状态,它就会写对应的断言。但那些“隐性边界”——比方说用户快速双击加入购物车,导致发送两个并发请求;或者网站在添加购物车时异步操作未完成就跳转页面——AI完全不会主动去覆盖。这其实不是AI“笨”,而是大语言模型缺乏对真实网络延迟、并发资源竞争及前端状态机爆炸的直觉。它只能根据语言模式推断行为,而行为背后的物理世界不确定性,是它想象力之外的真空地带。这一点,我后面会详细拆解。

于是我们在生成流水线里加了一层“异常注入脚本”,并设定了人工评审的锚点。整个混合策略让我们最终用三个工作日完成了一个原本需要三周的任务,而且极端场景覆盖比纯手写还要全面。下面我就展开说说那条从狂喜到填坑的路。

当AI生成的测试覆盖了90%的“快乐路径”,剩下10%的坑是怎么把我们绊倒的

在我们用GPT-4生成第一批电商测试并跑通核心链路的那个下午,团队里确实有种过节的气氛。我们点了一杯奶茶庆祝,觉得AI测试时代真的来了。然而第二天一上班,CI/CD流水线就抽风了——一个看起来十分平常的“加入购物车并结算”测试,竟然在凌晨的定时构建中失败,而且连续三次重试都一样。我打开错误日志,发现测试在提交订单时因为支付接口返回了一个503而崩掉,但生成的脚本根本没有捕获这种异常,导致整个套件直接中断。

我当时想,是不是网络波动,重启就好了。但重启后依然偶发失败,概率大概1/20左右。进一步排查才发现,是支付服务在维护窗口期有短暂不可用,而我们的测试完全没考虑这种状态。更让我头疼的是,类似的“非快乐路径”在随后的几周里不断涌现:用户登录态在测试中途意外过期、第三方物流接口超时导致页面停留过久、异步库存扣减返回“扣减失败”但UI上无任何提示……这些全是AI生成用例的盲区。我开始系统性地盘点那些漏掉的极端情况,最后归成了三个大类。

第一类是“状态爆炸”。GPT-4很擅长处理有限状态,比如库存>0、库存=0。但真实应用中,购物车里有多个商品时,每种商品可能处于不同状态(有货、缺货、下架、限购),这些状态组合在一起会让用例数量指数级增长。举一个具体例子:我们有一个促销活动,同一订单里如果包含A和B两种商品,总价打八折;但如果A缺货,折扣规则就只适用B;如果B也缺货,整个订单就不允许提交。AI生成的测试只会分别验证A有货和B有货的单品场景,从来不会写一个“A有货B缺货时,订单总价是否正确”的用例。而这类组合恰恰是上线后用户踩坑最多的地方。我们后来不得不自己写了个小程序,根据商品SKU状态矩阵自动生成这类组合用例,然后注入到测试套件里。

第二类是“异步竞态”。现代Web应用到处是异步操作,但大模型生成测试时,往往隐式地认为所有操作都是原子且瞬间完成的。我印象最深的一个Bug:用户快速双击“加入购物车”按钮,前端在200毫秒内发出两个POST请求,后端因为未做幂等处理,导致库存扣减两次。AI生成的测试脚本里,操作全是用一次click完成,永远只有单一的请求。为了解决这个,我们设计了一个“动作速度变异”的补丁:在测试运行时,随机在某些步骤之间插入短延迟,或者模拟快速连续点击,甚至并行触发多个异步请求。这个补丁让我们发现了至少四个生产级别Bug,包括购物车数量翻倍、优惠券重复使用等。这些Bug如果只靠AI生成的原版测试,可能到灰度发布时才会暴露。

第三类是“权限边界与上下文失效”。登录态、CSRF Token、OAuth2的Access Token刷新,这些是E2E测试的基础设施,GPT-4也能写出登录并保持会话的脚本。但它不会去测试“在结账过程中Token过期后页面行为”。我们有一个真实的事故:用户从浏览商品到提交订单花了15分钟,Access Token刚好失效,而页面没有跳转到登录页,反而让用户提交了一个没有关联用户ID的幽灵订单。当测试人员手工复现时才发现,需要先让页面闲置一段时间再继续操作。AI永远不会想到要模拟这种时间流逝的上下文。后来我们写了一个全局的Token失效注入器,每隔固定时间就主动让Auth服务返回401,然后检验各个页面的重定向与提示。这个逻辑必须由人设计并植入,AI做不到。

经过这一系列教训,我们总结出一条规律:GPT-4能很好地覆盖应用“规约之内的正确行为”,也就是符合需求文档和UI交互路径的那部分;但对于“规约之外的可能错误”——比如网络异常、服务降级、并发冲突、状态组合——AI不仅不会写,甚至根本想不到它们需要被测试。这恰好印证了那句老话:测试的价值不在于验证系统能做它该做的,而在于验证系统不会做它不该做的。而这个“不该做的”范畴,目前只能靠人类的经验和系统性的故障模型来捕猎。

我们搞了一条“测试产线”:生成、异常注入、人工复核,把AI想象力短板缝补起来

意识到AI的短板之后,我们没有退回到全手工,而是设计了一套混合流水线。这套流程我给它起了个很土的名字叫“测试产线”,因为它真的像一条工厂流水线:原材料(需求文档+页面快照)进去,经过冲压(AI生成)、补焊(异常注入)、质检(人工复核)三个环节,最后出来可以直接跑的测试套件。

生成环节我前面提过了,核心就是Prompt工程配合少量页面元数据。但我们后来加入了一个关键步骤:在调用GPT-4之前,先用Playwright的codegen工具或手动访问页面,抓取关键元素的真实选择器列表,形成一份“元素地图”塞进Prompt。这样减少了AI自己编造选择器的几率,生成的测试稳定性提高了不少。不过这部分不是重点,重点是后面的“异常注入”机制。

异常注入是我们为了弥补AI想象力盲区量身定做的。它的思路是:对于每个由AI生成的测试用例,我们自动扫描其中涉及的HTTP请求和UI操作,然后根据预定义的故障模板,生成若干变异版本。比如原测试用例如下(简化版):


test('checkout with valid cart', async ({ page }) => {
  await page.goto('/cart');
  await page.click('button[data-testid="checkout"]');
  await page.fill('#address', '123 Main St');
  await page.click('button[data-testid="place-order"]');
  await expect(page.locator('.order-success')).toBeVisible();
});

注入系统会解析出两个网络请求:GET /cart页面的API调用、POST /order的提交请求。然后它会创建至少三个变异版本:

  • 网络延迟版:对POST /order插入1-5秒延迟
  • 服务错误版:让POST /order直接返回500或503
  • 响应异常版:让POST /order返回200但包含错误码的业务异常(如库存不足)

实际执行时,我们使用Playwright的路由拦截(page.route)在测试运行时动态改写响应。举个变异后的代码例子:


test('checkout with server error', async ({ page }) => {
  await page.route('**/api/order', route => {
    route.fulfill({
      status: 503,
      body: JSON.stringify({ error: 'Service Unavailable' })
    });
  });
  await page.goto('/cart');
  await page.click('button[data-testid="checkout"]');
  await page.fill('#address', '123 Main St');
  await page.click('button[data-testid="place-order"]');
  // 检查页面是否展示了友好的错误提示,而不是崩溃
  await expect(page.locator('.error-message')).toBeVisible();
});

这套注入模板是我们团队根据过往线上故障总结出来的,包含了网络层、应用层、认证层共十几种异常类型。AI生成的每个用例都会自动被注入系统展开成5-8个变异用例,一下子就把极端场景覆盖率提了上去。而且因为这些注入的异常是基于真实故障模式提炼的,发现潜在缺陷的能力比单纯堆人数手写还要强。

但光靠自动注入还不够,因为模板终究是死的,不能覆盖所有情况。比如注入系统能模拟支付失败,但它不知道某个特定营销活动里,失败后要回滚已发放的优惠券。这种复杂业务规则必须靠人脑。于是我们设定了“人工复核”的锚点:所有变异后的用例,必须经过一轮人工审查才能合入主干。不过审查的重点不是语法——语法错误早被跑通了——而是判断自动生成的断言是否真正触及到了核心质量属性。我们会特别注意以下几点:断言的粒度是否过于粗糙(比如只检查页面不崩溃,却没检查错误提示的具体文案);是否遗漏了数据一致性的校验(比如下单失败后购物车不应该被意外清空);是否考虑了前端降级展示(错误时该显示骨架屏还是友好提示)。经过几轮磨合,我们的测试产线逐渐稳定下来,平均一个人一天能审查并完善40-50个用例,效率比纯手工写高了不止一个量级,同时发现的缺陷数量也提升了三成左右。

这个产线还有一个意外收获:我们积累了一套“故障模式库”。每次线上出了新Bug,我们就分析是哪个极端场景没被覆盖,然后抽象出一个新的注入模板或者评审检视项,回填到流水线里。这样一来,流水线就像有了记忆,不会再在同一个地方跌倒。这个做法也让我们在面对越来越复杂的业务时,始终保持测试信心。

这件事让我对测试左移和AI驱动质保有了几个不成熟但很真实的想法

经过这大半年的AI+人工混合测试实践,我有一些自己的判断,可能并不符合主流叙事,但确实是在战壕里摸爬滚打后的真实感受。

首先,“测试左移”不能只是把测试活动提前到开发阶段,更应该把“故障想象力”左移到需求分析和设计环节。AI生成测试的大规模应用,很容易让人陷入一个陷阱:既然生成用例这么方便,那测试完全可以放在代码写完之后再补。事实上,我们最初的确是这样做的,但很快就尝到了苦头——AI生成的测试只校验软件实现了什么,而不校验设计遗漏了什么。真正的左移,应该是在画架构图、定接口契约时,就把可能的异常场景和边界条件作为“非功能需求”明确下来,然后这些描述就可以作为Prompt的一部分指导AI生成对应测试。我们发现,当在需求文档里明确写上“用户在支付过程中网络中断重连后应回到支付页而非购物车”这种约束时,GPT-4就能生成相应的测试用例。所以问题的根儿不完全在AI,也在于我们人类有没有把隐性知识显性化。换句话说,AI是一面镜子,照出的是我们自己对系统容错能力的思考深度。

其次,我不认为可预见的未来里会出现全自动的“AI质保工程师”。AI可以成为超级辅助,但测试的本质是对未知的探索,而探索需要“我怀疑这里可能出问题”的直觉。目前的AI不具备这种基于经验、基于对物理世界复杂性的直觉。比如我们那个Token超时的幽灵订单,AI之所以测不到,是因为它在训练数据中没见过长时间闲置导致Token失效并引发订单关联丢失的完整故事链。人可以靠一个“15分钟没操作”的模糊感觉就设计出案例,AI得等到被明确告知“请在操作间插入15分钟闲置”才会照做。这个差距不是靠更先进的模型就能弥合的,因为它涉及到了因果推理与现实世界交互的鸿沟。所以我的策略是,永远把AI定位在“生成常规用例+根据显式规则变异”上,把创造性设计和探索性测试留给人类。

最后一点,关于组织层面的变化。引入这条流水线之后,我们测试工程师的角色发生了转移——从用例编写者变成了故障模式分析师。他们不再花时间写“点击这里、断言那里”的脚本,而是花更多时间分析生产环境日志、复盘逃逸缺陷、设计新的异常注入模板。这样一来,团队对质量的掌控力反而更强了。我觉得这种模式可能是未来几年AI驱动质保的可行方向:用AI处理已知,让人聚焦未知。虽然我现在还不能断言这条路一定对,但至少在我们这个团队里,它让我们用更少的人做了更多、更可靠的测试。如果让我给同行一个建议,那就是:别指望AI替你思考“什么可能出错”,但你可以用AI替你执行“你已经知道可能出错但没空写”的那些测试。

关于作者

发表评论