news 2026/7/23 5:54:46

测试工程师如何与 AI 共事:把 LLM 接进测试流水线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
测试工程师如何与 AI 共事:把 LLM 接进测试流水线

系列专栏:测试工程师每日一博 · Day 13
上一篇:[Day 12 · 测试右移:可观测性、混沌工程、流量回放]
这一篇是 12 天以来第一次离开"传统测试自动化"主线,横切到 AI。
目标不是谈"AI 取代测试",而是谈测试工程师怎么把 LLM 嵌进已经存在的流水线

一、为什么是 Day 13

前 12 天我们顺着测试金字塔(Day 1)一路爬到右移(Day 12),覆盖了 Mockito、覆盖率、Testcontainers、WireMock、性能、CI、TDD、契约、Flaky、左移、右移——这是传统轴的"全力图"。

那 AI/LLM 应该插在哪?每个环节都能插才是答案:

系列注入 LLM 的口子
Day 1 金字塔LLM 生成单元用例的边界值
Day 4 TestcontainersLLM 解释"起容器失败"的报错根因
Day 6 性能LLM 总结 JMeter 报告 + 给出 3 个 hot spot 假设
Day 7 CILLM 自动给 flaky 跑分类(时间依赖、并发、环境……)
Day 10 FlakyLLM 直接消费日志做时间线摘要
Day 11 左移LLM 把 Gherkin 草稿扩成 acceptance criteria
Day 12 右移LLM 读 Prometheus 异常 + 关联 trace 一句话总结

也就是说,LLM 不是第 13 个独立工具,而是横切每一根柱子的胶水。这就是为什么 Day 13 才写 AI——前 12 篇没攒够场景,LLM 就只能表演"写代码";而真正能放大杠杆的场景是:把 LLM 嵌进已经存在的测试流水线

二、先撕掉三个错误认知

撕掉这三个,后面才好谈:

  1. “AI 会取代测试工程师”——错。被取代的不是工程师,是"重复劳动"那一份工时。工程师的高级职能(质量策略、缺陷预测、风险评估)是被放大,不是被削弱;
  2. “AI 写测试就是给它一段需求让它生成”——错。无上下文的生成只会得到 happy path 和模糊断言;
  3. “AI 生成的测试可以信”——错。LLM 会幻觉出 API,虚构 selector,编造业务字段。AI 生成的测试必须用对待第三方代码的方式 review

绕开这三坑,AI 就是工具箱里又一把螺丝刀。

三、AI 在测试里的四块拼图

拼图输入输出接管哪种劳动
1. 用例生成业务规则 / OpenAPI / GherkinJUnit/Pytest/Playwright 用例体力
2. 断言维护失败的 E2E + DOM diff修复后的 selector + 解释维护
3. 报告解读CI 日志 / 性能 report / Trace一句话根因 + 三个候选修复分析
4. 数据合成字段约束 / 业务规则Faker + 校验一致的合成数据准备

①②③④ 分别对应系列前 12 篇里低 ROI 但高时长的工作。下面每一块拆一篇实践。

四、实践一:用 LLM 生成单元测试(电商优惠券金额计算)

4.1 看一段被测代码

publicclassCouponCalculator{publicBigDecimalapply(BigDecimalorderAmount,Couponcoupon,LocalDateTimenow){if(coupon==null)returnorderAmount;if(coupon.getExpireAt().isBefore(now))thrownewCouponExpiredException(coupon.getId());if(coupon.getMinSpend()!=null&&orderAmount.compareTo(coupon.getMinSpend())<0)returnorderAmount;BigDecimaldiscount=coupon.getType()==CouponType.FIXED?coupon.getValue():orderAmount.multiply(coupon.getValue()).setScale(2,RoundingMode.HALF_UP);returnorderAmount.subtract(discount).max(BigDecimal.ZERO);}}

4.2 让 LLM 产出"易测点"的 prompt

importopenai,json,textwrap SYS="You are a test engineer. Output strict JUnit 5 + AssertJ tests, no prose."PROMPT=textwrap.dedent(""" Generate parameterized tests for CouponCalculator.apply. Constraints: 1. Cover: null coupon, expired coupon, minSpend not met, FIXED type, PERCENTAGE type, discount greater than orderAmount (must clamp to zero). 2. Pass `now` as a parameter — do NOT use LocalDateTime.now() in test. 3. Use BigDecimal comparisons via AssertJ usingComparatorForType. 4. Do not invent APIs. If you need a Coupon factory, write a static helper inline. Code only, no markdown fence. """)resp=openai.chat.completions.create(model="gpt-4o-mini",temperature=0.2,# low temperature = more deterministicresponse_format={"type":"json_object"},messages=[{"role":"system","content":SYS},{"role":"user","content":PROMPT},],)parsed=json.loads(resp.choices[0].message.content)open("CouponCalculatorAITest.java","w").write(parsed["test_code"])

4.3 这段代码做了什么

主旨是约束 prompt 而不是放任生成:

  • 覆盖清单:明确列出 6 个 case,LLM 不会自己想到 “discount > orderAmount”;
  • 时钟注入:不允许LocalDateTime.now(),否则又一次掉进 Day 10 的 flaky 时间坑;
  • 断言规范:禁止assertEquals比 BigDecimal(精度默认比较坑);
  • 不臆造 API:“如果需要 helper 自己写”——挡住 LLM 假造CouponFactory的习惯;
  • temperature=0.2:测试代码要可复现,不要 creativity。

生成之后依然要人工 review,尤其是边界值和断言数值。

4.4 一个幻觉实验

把"discount 大于 orderAmount 要 clamp 到 zero"那行约束注释掉,再问 LLM。
它大概率会漏这个 case——LLM 对"满 100 减 200"这种异常优惠不敏感。

这就是为什么 prompt 工程本质上跟写"反向需求 / 边界用例"等价(Day 11 左移的支柱之一):你对边界的描述能力 = AI 生成用例的质量上限

4.5 用 Ground Truth 做 CI 校验

生成的用例不能直接合入主干,得有"反向闸门":每个生成用例必须配一份 ground truth 期望值,CI 跑一遍比对。

importsubprocess,json,pathlibdefgrade_generated_tests(java_path:str,expectations:list[dict])->dict:""" expectations: [{"case": "expired", "expected_throw": "CouponExpiredException"}, {"case": "minNotMet", "expected_amount": "100.00"}, ...] """subprocess.run(["mvn","test",f"-Dtest={pathlib.Path(java_path).stem}"],check=True,capture_output=True)report=json.load(open("target/surefire-reports/summary.json"))passed=sum(1foreinexpectationsife["case"]inreport["passed_cases"])return{"pass_rate":passed/len(expectations),"ai_only_passed":set(report["passed_cases"])-{e["case"]foreinexpectations},"expected_but_failed":{e["case"]foreinexpectations}-set(report["passed_cases"]),}# 在 CI 加一步: grade_generated_tests(...)# 若 ai_only_passed > 0 → LLM 多写了几个用例, review 看是 hallucination 还是真补 case# 若 expected_but_failed → LLM 漏 / 错写, 必 fix

这块把"AI 生成的测试"从黑盒变成白盒:它究竟多产了什么、漏了什么,一眼清楚。回归到 Day 9 契约测试的思路:期望值是契约,LLM 输出是 consumer,契约不通过 → 红灯。

五、实践二:UI selector 自愈

E2E 测试最痛的是 selector 漂移——产品改一次 CSS class,几百条用例全红。

5.1 老办法 vs 新办法

// 老办法(脆)constcheckoutBtn=page.locator('button.submit-btn-blue.v2');// 新办法(多层次降级 + LLM 自愈)constcheckoutBtn=awaitSelfHealingLocator.find(page,{role:'button',accessibleName:'提交订单',fallbackTextPattern:/(提交|下单|去结算)/,onHeal:async({oldSel,newSel,html})=>{awaitnotifySlack(`[selector healed]\n old:${oldSel}\n new:${newSel}\n html:${html}`);},});

SelfHealingLocator.find内部逻辑:先用 robust selector,失败时把 DOM 截图 + 老 selector 喂给一个 fast LLM(如 Claude Haiku / GPT-4o-mini),让它返回最匹配的元素;命中后写回一个"selector registry"(JSON / YAML),下次起跑就直接读这份 registry,不依赖 LLM 实时在线

5.2 关键设计

  • 降级链:role + name → text pattern → sonic 匹配 → LLM(每层失败才走下一层);
  • 本地缓存:LLM 结果写入仓库,不每次重跑(节省 token 也是稳定);
  • 审计:onHeal钩子必须 Slack 通知——selector 漂移是产品意图还是 bug,人决定,LLM 不决定。

六、实践三:Flaky 根因分析(回扣 Day 10)

Day 10 我们谈 flaky 三大根因:时间依赖、并发等待、测试污染。LLM 最适合干"读一堆日志,告诉你属于哪类"。

defclassify_flaky(failure_log:str)->dict:""" 输入一段 CI flaky 失败日志,输出根因类型 + top 3 关键证据 + 修复建议 """taxonomy=[{"name":"time-dependent","evidence":["LocalDateTime.now()","Thread.sleep","scheduleAtFixedRate"]},{"name":"resource-race","evidence":["InterruptedException","OutOfMemoryError","Connection refused"]},{"name":"data-pollution","evidence":["duplicate key","ConstraintViolation","CacheLoader returned null"]},]resp=llm.chat(messages=[{"role":"system","content":"Classify ONE flaky test root cause. Output JSON only."},{"role":"user","content":f"Taxonomy hints:{json.dumps(taxonomy)}\n\nLog:\n{failure_log}"},],temperature=0,response_format={"type":"json_object"},)returnjson.loads(resp.choices[0].message.content)# 接进 Surefire -> 失败时调一次,把 summary 落到 CI artifact

把分类结果自动归并到周报里:“本周 flaky 23 次,根因分布:时间依赖 15 / 资源竞争 6 / 数据污染 2”——这就是把 LLM 当"数据加工器"用,人不读日志,LLM 读日志。

七、实践四:合成数据(回扣契约测试 Day 9 和左移 Day 11)

测试用真实数据三种死法:PII 泄露、合规违规、统计偏差。LLM 加 Faker 是黄金组合。

fromfakerimportFakerfromrandomimportchoice f=Faker("zh_CN")defmobiles_n(count:int,province:str="广东")->list[str]:""" 合规合成手机号 — 不取真实库,但符合三大运营商前缀规则 """prefixes_by_province={"广东":["138","139","135"],"北京":["136","137"],# ...其它省}return[f.numerify(text=choice(prefixes_by_province[province])+"########")for_inrange(count)]# 进一步:给 LLM 业务规则 + 一个 schema,生成符合业务规则的批量测试用户schema={"手机号":"广东移动号","余额":"(50, 999)","等级":"V1+V2+V3","异常标记":"0 或 1",}users=llm_synthesize(schema,n=500,lang="zh")

这块特别契合Day 11 Gherkin acceptance criteria:Gherkin 里的 “Given 一个已登录的 V2 用户”——直接喂给 LLM,回去出 5 个等效的合成数据样例,把抽象场景变成可执行数据。

八、提示工程就是新单元测试

LLM 这种工具:提示词 = 单元测试中的"测量装置"——它怎么稳定,输出才能稳定。

8.1 三条不变量

  1. 可复现:同样输入两次结果一致(temperature=0或加固定 seed);
  2. 可断言:输出用response_format=json_object,断言走 schema 而不是字符串 include;
  3. 可重放:每个 prompt 永远配一段黄金样本(golden example)——类似单元测试的 fixture。

8.2 用 fast LLM 跑 eval

deftest_prompt_no_hallucinated_assertions(eval_set=50):""" 对一个生成 JUnit 用例的 prompt 跑 50 个样本, 断言"不能编造"字段出现次数 == 0 """bad=0forcaseinload("eval/calc_eval.json"):output=generate(case)if"factory.createCoupon"notincase["source"]\and"factory.createCoupon"inoutput:bad+=1assertbad==0,f"hallucination rate too high:{bad}/{eval_set}"

这就是把 prompt 当一等公民来测——曲线回到测试金字塔底座(Day 1)的思路:即便 prompt 不是代码,它也该有它自己的"单元测试"。

8.3 prompt 也要进版本库

实操层面:在仓库里建prompts/目录,每个 prompt 一份 YAML,带着元数据。

# prompts/coupon_test_gen.v3.yamlid:coupon_test_genversion:3model:gpt-4o-minitemperature:0.2status:stable# draft | staging | stable | deprecatedgolden_examples:-case_id:calc_eval_001expect_contains:"CouponExpiredException"-case_id:calc_eval_002expect_not_contains:"factory.createCoupon"metrics:hallucination_rate:0.02pass_rate:0.94changelog:-v3:加约束 "do not invent APIs"(v2 时 5% 输出含臆造 factory)-v2:改用 temperature 0.2(v1 时复现率仅 60%)

CI 启动时遍历prompts/*.yaml,对每个status: stable的跑一遍 golden eval。任一指标跌出阈值,PR 红灯,逼着改 prompt 的人写明新增 case 怎么修。

九、数据安全:三道闸

把 AI 接进测试流水线,最大的隐忧不是"AI 写错代码",是测试数据进了第三方 LLM

  1. 不送:把 PII 字段在 prompt 之前充脱敏(139****1234);
  2. 本地:敏感业务用本地开源模型(Qwen2.5-Coder、CodeT5+、DeepSeek-Coder),离线推理;
  3. 审计:prompt 日志单独成仓,不允许明文 secret 进 prompt。

合成数据(Day 13 第七节)是天然的解决方案——你不该往 LLM 里塞生产 dump。

9.1 一个 prompt 脱敏中间件

把"脱敏"做成网关中间件,业务代码无感知:

importre,hashlib PII_RULES=[(re.compile(r"1[3-9]\d{9}"),lambdam:m.group()[:3]+"****"+m.group()[-4:]),(re.compile(r"\b\d{15,18}[X0-9]\b"),lambdam:hashlib.md5(m.group().encode()).hexdigest()[:8]),(re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}"),lambdam:m.group().split("@")[0][:2]+"***@"+m.group().split("@")[1]),]defredact(text:str)->str:forpat,replinPII_RULES:text=pat.sub(repl,text)returntextdefsafe_llm_call(prompt:str,**kw):"""所有接 LLM 的入口必须走这一层,否则 CI 拦"""# 1) 脱敏cleaned=redact(prompt)# 2) 拦截forsecret_patternin["AKIA[0-9A-Z]{16}","sk-[A-Za-z0-9]{20,}","-----BEGIN PRIVATE KEY-----"]:ifre.search(secret_pattern,cleaned):raiseRuntimeError(f"SECRET LEAK IN PROMPT:{secret_pattern[:8]}...")# 3) 记审计audit_log(cleaned)# 4) 真实调用returnraw_llm_call(cleaned,**kw)

这个safe_llm_call是测试团队的"上层水管":任何人调 LLM 都必须经过它,然后 CI 红线卡一道——一旦发现绕过safe_llm_call直接调openai.chat,PR 拒掉。这就是用工程方式执行"数据不进 LLM"政策,而不是靠自觉。

十、AI 工具对位表(避开"取代"叙事)

类型工具示例互补角色
IDE 助手Copilot、Cursor、JetBrains AI Assistant写测试样板代码
E2E 自愈Mabl、Testim、Healeniumselector 漂移修复
报告解读自家网关接 LLM性能 / flaky 日志总结
用例生成Diffblue Cover(JVM 单测)单测覆盖补充
数据合成Tonic.ai、Gretel、Mostly AIPII 合规替换

注意没有一个工具是"端到端替代人"。把它们当外骨骼,不当替代品。

10.1 一个 ROI 排序小工具

测试团队上 AI 工具最容易踩的坑是"花钱买了个寂寞"。可以用一个小矩阵算 ROI:

defai_tool_roi(hours_saved_per_week:float,hourly_cost:float,tool_monthly:float,hallucination_rate:float)->dict:weekly_save=hours_saved_per_week*hourly_cost"""幻觉返工成本 = AI 输出错的部分由人工补救"""weekly_rework=hours_saved_per_week*hallucination_rate*hourly_cost*2weekly_cost=tool_monthly/4net=weekly_save-weekly_rework-weekly_costreturn{"weekly_save_usd":round(weekly_save,2),"weekly_rework_usd":round(weekly_rework,2),"weekly_cost_usd":round(weekly_cost,2),"net_weekly_usd":round(net,2),"payback_weeks":round(tool_monthly/max(net,0.01),1)ifnet>0elseNone,}# 例子:一个 selector 自愈工具每月 300 美元,声称一周省 8 工时print(ai_tool_roi(hours_saved_per_week=8,hourly_cost=40,tool_monthly=300,hallucination_rate=0.1))# 净收益若为负 → 别买; 为正但 payback > 12 周 → 再聊

关键在hallucination_rate这一项——以前买工具不看幻觉率,买完才发现"省 8 工时 → 返工修 2 工时"。把这一项写进 ROI 公式,选型会更冷静。

十一、反模式清单

新增几个 AI-flavor 反模式:

反模式后果干掉的办法
灌生产 dump 进 LLM合规事故 + token 费用爆炸用合成数据
用 temperature=0.7 生成测试用例跑两次结果不同,无法做 CI强设 0~0.2
LLM 生成的测试不经 review假断言偷偷混进回归套件CI 强制:AI 标记的文件必须人在 PR approve
Prompt 不进版本库团队成员偷偷调多次,行为漂移prompt 进prompts/目录,走 PR review
selector 自愈默默修复真正的产品改动被"友好地"修没onHeal 必告警 + 限速
把 LLM 当 oracle“LLM 说这测试对就是对”LLM 永远是 suggest,断言由人 + 真 fixture 决定

十二、回扣系列

当我们说…LLM 接到的活
Day 1 测试金字塔底端重复劳动用例生成 + Diffblue
Day 7 CI “哪条 flaky 抢我的 CPU”自动失败根因归类
Day 10 Flaky 治理日志一句话根因
Day 11 需求评审 Three Amigos把 Gherkin 草稿扩成 acceptance criteria
Day 12 右移监控Prometheus 异常一句话总结

也就是说,AI 是横切所有柱子的一根钢筋,而不是顶替任何一根柱子。

12.1 一个真实端到端场景:左移 → CI → 右移全链路 AI 协作

把整个系列串一遍,看 AI 在每个环节的具体输出:

Day 11 左移阶段:产品同学提了"会员积分兑换商品"。半小时候后,LLM 把这条需求扩成 8 条 acceptance criteria(Gherkin),其中 3 条是逆向需求——“积分不足 / 商品已下架 / 限购超量”——这 3 条人不太愿意主动想。LLM 帮你想了。

Day 9 契约阶段:基于这 8 条,Gherkin 喂给 Pact JVM 的 consumer test 生成器,自动产生 consumer-side 的 pact 文件,provider 只需 verify。

Day 4 Testcontainers / Day 1 单元:Java 代码 commit 时,Copilot 已根据差分(diff)为新增类生成 5-10 个候选用例,Diffblue 跑一遍,coverage 落地从 0 到 60%。剩下的 40% 留给人写——人写的是边界、并发、状态机,这些 LLM 不会写。

Day 10 flaky:CI 失败的 200KB 日志不走人工眼。fast LLM 5 秒返回"根因:OrderConsumerTest#shouldAsyncNotify等待 30s 超时,top 1 证据:Awaitility timeout after 30000ms,建议:把超时上限抬到await().atMost(60, SECONDS)"。周一早上看的就是 5 句话周报,不是 200KB 日志。

Day 12 右移:Prometheus 触发 burn rate 告警。LLM 读告警 + 关联 trace + 最近 30 条类似事件,5 秒返回"与上周三 14:00 的容量问题相似度 87%,根因候选:订单服务慢 SQLSELECT * FROM order WHERE uid=?缺索引"——人去确认索引,LLM 不动手

五段连起来读:LLM 在每个节点上是"小助手",但每个节点都有一个人在做决策。这才是把 AI 接进测试流水线的正确姿势。

十三、原则三句话

  1. AI 不替代你的判断力,AI 把你的判断力放大 N 倍(N ∈ [0, +∞));
  2. 所有 prompt 必须像单元测试一样可复现、可重放、可断言;
  3. 生产数据不进 LLM——零容忍,等同把生产密钥贴到开源仓库。

十四、思考题

留给晚上:

  1. 你团队的 flaky 失败日志现在谁在阅读?如果是"没人",说明这块 ROI 极高;
  2. 现在写的测试中,有多少可以用静态分析直接判定"AI 生成可信",又有多少必须人工 review?
  3. Day 12 右移那根柱子(SLO/可观测),LLM 介入后能缩短"发现 → 定位 → 修复"链路里的哪一段?
  4. 提示词一旦进版本库,你怎么 regression test “今天的 prompt 在三个月前的数据上依然是 best”?——这正是 Day 9 契约测试的思路迁移。

十五、TL;DR

  • LLM 不是第 13 个独立工具,是横切 Day 1-12 的胶水;
  • 四块拼图:用例生成 / selector 自愈 / 报告解读 / 合成数据;
  • 提示工程 = 新单元测试(可复现 / 可断言 / 重放中带黄金样本);
  • 生产数据不进 LLM = 零容忍规则;
  • AI 工具是外骨骼,不是替代品。

下一篇:Day 14 ·测试数据治理:从 fixture 到 synthetic data
把今天第七节"合成数据"那块拆出来单独讲深——为什么 fixture、fuzz、synthetic 三层要分开用,以及各自的边界在哪。下篇见 👋

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/22 2:53:35

终极Czkawka重复文件清理工具:5步快速上手释放磁盘空间

终极Czkawka重复文件清理工具&#xff1a;5步快速上手释放磁盘空间 【免费下载链接】czkawka Multi functional app to find duplicates, empty folders, similar images etc. 项目地址: https://gitcode.com/GitHub_Trending/cz/czkawka Czkawka&#xff08;波兰语意为…

作者头像 李华
网站建设 2026/7/22 2:53:24

Unity导入Mixamo动画全流程:5分钟解决材质丢失与骨骼错位

1. 项目概述&#xff1a;从Mixamo到Unity的“最后一公里”如果你在Unity里做过角色动画&#xff0c;大概率听说过Mixamo这个神器。它就像一个在线动画超市&#xff0c;里面有成千上万套高质量、可直接使用的角色动画&#xff0c;从基础的走跑跳到复杂的战斗、舞蹈&#xff0c;几…

作者头像 李华
网站建设 2026/7/22 2:52:01

【CISP】信息安全管理

title: 信息安全管理核心知识体系全面总结&#xff08;建议收藏&#xff09; tags: [信息安全, ISMS, 风险管理, PDCA, 等保] categories: 信息安全 信息安全管理核心知识体系全面总结 本文基于《信息安全管理》培训教材&#xff08;版本 4.2&#xff09;&#xff0c;系统梳理…

作者头像 李华
网站建设 2026/7/22 2:50:03

记忆体系工程实战:从设计选型到生产落地

一个团队在构建 Agent 记忆系统时做了一个看起来合理的技术选择&#xff1a;用 Pinecone 存储所有的记忆——用户偏好、对话历史、技术知识、程序性方法&#xff0c;全部打成向量塞进一个托管向量数据库。 六个月后&#xff0c;他们遇到了三个问题&#xff1a; 第一&#xff…

作者头像 李华
网站建设 2026/7/22 2:49:31

【Linux C】有什么思路做内存管理,避免OOM

Linux C 开发&#xff1a;如何管理内存、避免 OOM 写在前面 在 Linux C 开发中&#xff0c;"如何管理内存、避免 OOM"是高频面试题&#xff0c;也是线上事故的常见根源。很多人答得零散——这里说一句 malloc 要检查返回值&#xff0c;那里说一句用 valgrind。其实它…

作者头像 李华
网站建设 2026/7/22 2:49:25

英文会议音视频转中文纪要的技术路径与功能分析

一、问题的提出国际教育领域的从业者——包括国际学校教师、教育类内容创作者、跨国教研项目参与者——日常面临大量英文音视频材料的处理需求。典型场景包括&#xff1a;海外线上教研会议录制、英文学术论坛回放、外教沟通记录、海外公开课素材采集等。二、音视频转写方案的核…

作者头像 李华