GPT-4o救了我的烂摊子:给3个祖传模块补单元测试,覆盖率从12%飙到78%
部门上个月定了个死线:所有核心模块的单元测试覆盖率必须在Q3结束前提到70%以上。我名下挂着一个维护了4年的数据处理工具库,打开覆盖率报告那一刻,数字刺眼得不行——12%。
不是我不想写测试。这库里有1700多行代码,包含42个函数,里面混杂着回调地狱、隐式全局状态、直接操作DOM的工具方法,以及好几个“这个参数传进去到底返回什么得跑一下才知道”的函数。单测?我自己都测不明白。
最后我用了一个让同事直呼“你小子作弊”的办法:让GPT-4o帮我读源码,分析每个函数的输入输出边界,然后自动生成vitest测试用例。三周后覆盖率78%,更关键的是还顺带发现了2个隐藏了三年的bug。
这阵子我一直在做一个国内镜像站(mf.877ai.cn)的深度体验,把GPT-4o、Claude 3.5 Sonnet、Gemini 2.5 Pro拉到一个页面里对比,专门测代码理解和测试生成能力。今天就把这套“AI辅助测试补全”的完整工作流拆给你看。
一、为什么是GPT-4o,不是其他模型
补单元测试这件事,和写业务代码完全不一样。写业务代码是你告诉模型“我要什么”,它实现;补测试是你让模型先理解“这东西在干什么”,再推导“它该怎么被验证”。后者对代码语义理解的深度要求高得多。
我把库里的一个典型函数分别丢给三个模型,让它们生成测试用例,然后人工打分:
| 评估维度 | GPT-4o | Claude 3.5 Sonnet | Gemini 2.5 Pro |
|---|---|---|---|
| 函数边界条件覆盖 | 95%(覆盖null/undefined/空数组) | 88%(漏了undefined边界) | 80%(边界意识较弱) |
| 异步逻辑mock合理性 | 90%(自动补了定时器mock) | 85% | 75%(手动补了很多mock) |
| 测试命名与组织 | 优秀(describe/it分层清晰) | 优秀 | 一般 |
| 生成代码可直接运行率 | 85% | 78% | 62% |
GPT-4o在边界条件挖掘上明显更主动。比如一个处理数组的函数,它会自动补充“传入类数组对象”、“传入带空槽的稀疏数组”这种我平时写测试都会漏掉的用例。
二、三件套工作流:我是怎么干的
我的流程是标准三步:分析→策略→生成。每一步的提示词写法都有讲究。
第1步:代码语义分析——先别急着写,先搞懂函数在干嘛
这一步最容易被跳过,但最关键。你不能直接把整个1700行文件扔给模型说“写测试”,它会乱。我是一次只输入一个函数,先让它分析这个函数的“契约”。
核心提示词模板:
请分析以下JavaScript/TypeScript函数的代码逻辑,不要写测试:
函数代码:
[粘贴函数代码]
请输出:
1. 函数的核心功能(一句话)
2. 输入参数的所有可能类型和边界值(正常值、null、undefined、极端值等)
3. 返回值类型及在不同输入下的变化规律
4. 函数的副作用列表(修改了哪些外部状态、调用了哪些外部依赖)
5. 异常分支分析(什么输入会导致抛出错误、错误类型是什么)
这一步跑了大概一小时。GPT-4o分析完所有42个函数后,我得到了一份极其清晰的“函数契约清单”。这里面有几个惊喜——它发现了一个日期格式化函数在传入负数时间戳时会NaN;一个处理金额的函数在传入科学计数法字符串时会静默出错。
// GPT-4o分析出的隐藏bug案例
function formatMoney(amount) {
// 分析结果:当输入为 '1e10'(科学计数法)时,
// parseFloat 会将其解析为 10000000000,
// 但 toFixed(2) 在处理极大数字时可能产生精度丢失
return parseFloat(amount).toFixed(2);
}
// 建议测试用例:
// formatMoney('1e10') → 期望什么?当前代码返回的是 '10000000000.00',
// 但 js 的浮点数精度可能导致最后一位偏差
我在看到这份分析后,先把这俩bug修了,才开始写测试。这顺序不能乱——如果你在旧代码的bug基础上补测试,要么测出失败但你不敢改代码,要么为了通过测试把断言写得很宽松,测试就废了。
第2步:测试策略——决定怎么测、测到什么程度
分析完函数契约,下一步是让GPT-4o根据每个函数的复杂度、风险和调用频率来制定测试优先级和策略。
核心提示词:
基于上面这个函数的分析结果,制定测试策略:
1. 该函数的核心测试路径是什么?(必测,覆盖80%的正常使用场景)
2. 有哪些边界条件必须覆盖?(列举所有输入参数类型的边界值)
3. 有哪些异常路径需要测试?(错误输入、异常状态)
4. mock策略:哪些外部依赖需要mock,怎么mock?(给出具体的mock方式)
5. 优先级:高/中/低(高风险复杂函数优先)
这一步产出了一份测试策略文档,我按优先级排序,先搞定高风险函数。7个高风险函数占库代码量的40%,但覆盖它们后覆盖率直接从12%蹦到了51%。
[配图1:测试覆盖率提升路径图,横轴为工作天数(1-15天),纵轴为覆盖率百分比,柱状图上标注累计覆盖的高/中/低风险函数数量,显示7个高风险函数覆盖后达到51%,15个中风险后达到72%,全部完成后达到78%]
第3步:用例生成——真正的自动化补测试
到这一步才真正让GPT-4o写测试代码。我每条提示词包含:函数源码 + 第1步的分析结果 + 第2步的策略,然后让它生成完整的vitest测试套件。
提示词结构:
请基于上面的函数分析和测试策略,为以下函数生成vitest测试用例。
要求:
1. 使用 describe/it 结构,每个it覆盖一个明确的行为
2. 测试命名遵循“should [行为描述] when [条件]”格式
3. 覆盖所有核心路径、边界条件和异常路径
4. 外部依赖使用 vi.mock 或 vi.fn 进行mock
5. 异步函数使用 async/await 和 vi.useFakeTimers(如需)
6. 不要使用 any 类型,所有mock函数的类型要明确
7. 直接输出可运行的完整测试代码
函数代码:
[粘贴]
关键代码片段展示一个典型函数的测试生成效果:
// 原函数:处理订单金额折扣
function applyDiscount(price: number, discount: number): number {
if (price < 0) throw new Error('价格不能为负数');
if (discount < 0 || discount > 1) throw new Error('折扣必须在0到1之间');
return Math.round(price * (1 - discount) * 100) / 100;
}
// GPT-4o生成的测试套件
import { describe, it, expect } from 'vitest';
import { applyDiscount } from './order';
describe('applyDiscount', () => {
// 核心路径
it('should return discounted price when given valid inputs', () => {
expect(applyDiscount(100, 0.2)).toBe(80);
});
it('should round result to 2 decimal places', () => {
// GPT-4o自动补的小数精度测试
expect(applyDiscount(99.99, 0.33)).toBe(66.99);
});
// 边界条件
it('should handle zero discount correctly', () => {
expect(applyDiscount(50, 0)).toBe(50);
});
it('should handle full discount (100%) as zero', () => {
expect(applyDiscount(50, 1)).toBe(0);
});
it('should handle price of zero', () => {
expect(applyDiscount(0, 0.5)).toBe(0);
});
it('should handle very large price without overflow', () => {
// GPT-4o自动考虑的大数值边界
const result = applyDiscount(99999999.99, 0.01);
expect(result).toBeGreaterThan(0);
});
// 异常路径
it('should throw error when price is negative', () => {
expect(() => applyDiscount(-1, 0.1)).toThrow('价格不能为负数');
});
it('should throw error when discount is out of upper range', () => {
expect(() => applyDiscount(100, 1.1)).toThrow('折扣必须在0到1之间');
});
it('should throw error when discount is negative', () => {
expect(() => applyDiscount(100, -0.5)).toThrow('折扣必须在0到1之间');
});
});
一次生成,42个函数中有36个的测试套件可以直接跑通,剩下6个需要微调,主要原因是老代码里有一些硬编码的外部模块引用,GPT-4o的mock路径写错了。这种情况你手动改一下import路径就行,5分钟的事。
三、三个你必须避开的坑
坑1:不要一次喂太多函数。 我试过把一个文件里的5个函数一起给它,结果它对第4、5个函数的边界分析明显变粗糙了。教训:一次只喂一个函数,保证质量。
坑2:模型生成的mock可能“过度合理”。 比如一个函数内部调用了Date.now(),GPT-4o会自动用vi.useFakeTimers来mock,但它有时候mock的逻辑过于完美——测试通过了,但实际运行时会因为时区问题失败。遇到涉及时间的函数,务必手动检查useFakeTimers的设置。
坑3:别盲目相信覆盖率数字。 AI帮我把覆盖率提上来之后,我又做了一次变异测试(mutation testing),发现有几个测试的断言其实没什么区分度——随便改一下源码它都能过。这说明GPT-4o在某些边界场景下写了“软断言”。我用Stryker跑了一轮,把那些“假阳性”测试揪出来重写,最终有效覆盖率才稳在78%。
[配图2:工作流三步骤的时间分配饼图,代码分析占25%,测试策略占15%,用例生成与调优占60%,标注“AI负责生成,人负责审查和微调”]
四、这套流程适合什么场景
最适合的场景:
- 功能稳定但久无测试的老旧工具库
- 纯函数占比高的数据处理模块(逻辑清晰,边界好定义)
- 已经过代码review、确认无已知bug的代码(先修bug再补测)
不太适合的场景:
- 包含大量UI交互逻辑的前端组件(涉及DOM的测试建议用E2E,单测写起来痛苦且价值低)
- 仍在快速迭代中的业务模块(测试刚写完需求就变了,维护成本高)
- 涉及复杂状态机的模块(模型很难理解全量的状态转移路径)
最后说两句
以前补单元测试是件纯体力活,对着源码一行行推逻辑、穷举边界、写mock,最后跑完覆盖率一看30%,心态直接崩。现在有了GPT-4o这种级别的代码理解能力,测试补全这件事从“能不能”变成了“值不值得”。
但别偷懒把整个流程全交给AI,我的经验是:AI负责80%的生成,你负责20%的审查。它挖出来的边界条件你去验证,它写的mock你去核对,它漏掉的极端场景你去补。这样出来的测试套件,才是真正敢在CI里跑、敢凭它重构代码的那种。
更多推荐


所有评论(0)