最近在leadhi.cn 这个AI模型聚合平台上切换了几个模型专门跑Go单元测试生成,发现不同模型对Go语言特性的理解差距还挺大。这篇文章是我用Gemini 3.5 Flash实际跑项目的复盘,踩了不少坑,也有真实收获。
 


概要

2026年6月,AI辅助编码已经不是新鲜事,但"AI写的测试能不能直接用"这个问题,大多数开发者心里还是没底。

我手上有一个Go微服务项目,测试覆盖率长期卡在47%左右。手动补测试太痛苦,正好Gemini 3.5 Flash刚发布不久,Terminal-Bench编程能力得分76.2%,直接越级超过了上一代3.1 Pro的70.3%。拿它跑了一轮,覆盖率拉到83%。

本文记录整个实操过程,包含prompt设计、踩坑修正、模型对比,以及一个核心判断:AI生成单元测试已经能用,但不能盲信。


整体架构流程

我的实操流程分四步,每一步都有明确的输入输出:

第一步:项目结构扫描

把项目目录和函数签名喂给模型。Gemini 3.5 Flash的上下文窗口有100万token,一个中等Go项目全量塞进去没问题。

第二步:分层生成测试

Go项目典型三层:handler → service → repository。每层单独给prompt,附上源码。一次丢太多,模型会把不同层的依赖关系搞混。

第三步:运行 + 覆盖率分析

Go 1.2起就内置了覆盖率工具,原理是重写源码添加插桩,编译运行后统计数据。命令很简单:

bash

bash
go test ./... -coverprofile=coverage.out go tool cover -html=coverage.out 

看HTML报告,把未覆盖的函数提取出来,再喂给模型补测。

第四步:人工审查

这一步不能跳。AI生成的测试有两类系统性问题,后面详细说。


技术名词解释

Gemini 3.5 Flash:Google发布的轻量级多模态模型,原生支持文本、图像、音频、视频输入,100万token上下文,输出速度约290 tokens/s。定价:输入1.50/百万token,输出1.50/百万token,输出9.00/百万token。

单元测试覆盖率:衡量测试执行了多少源代码的比例。Go用go test -cover输出百分比。行业一般要求50%-60%,资金型服务要求80%。

errors.Is / errors.As:Go 1.13引入的错误链判断工具。Is比较错误值是否相等,As做错误类型断言。这两个函数是Go错误处理的核心,也是AI最容易写错的地方。

Mock打桩:用函数A替换函数B,屏蔽外部依赖(数据库、文件等),保证测试稳定性和幂等性。Go中常用monkey库实现运行时函数替换。

Benchmark基准测试:Go内置的性能测试框架,以Benchmark开头,入参testing.B,通过b.N反复递增循环测试代码性能。


技术细节

1. Prompt设计:分层喂代码效果最好

一次把整个项目丢给模型,效果很差。我试过三次,生成的测试要么遗漏关键分支,要么把不同层的逻辑混在一起。

后来改成按层单独喂,prompt模板:

text

text
你是Go测试工程师。以下是[handler/service/repository]层代码: [粘贴代码] 请用标准库testing生成单元测试,覆盖:正常路径、边界条件、错误返回。 不使用第三方测试框架。 

这样命中率最高。测试文件以_test.go结尾,函数以Test开头,模型对这个命名规范倒是从来没有搞错过。

2. errors.Is和errors.As:模型会搞混

这是踩的最大的坑。

Go没有try-catch,错误靠返回值传递。Go 1.13之后用fmt.Errorf%w包装错误,配合errors.Is和errors.As判断错误链。

Gemini 3.5生成包装代码没问题:

go

go
err2 := fmt.Errorf("外层: %w", err1) 

但判断逻辑上,它偶尔把Is和As用混:

go

errors.Is(err, &AppError{}) // 这是错的 

errors.Is是值比较,errors.As才是类型断言。正确写法:

go

var target *AppError errors.As(err, &target) // 类型匹配 

而且As的第二个参数类型要和目标类型一致。如果函数返回的是FuncErr实例,target就声明为FuncErr;如果返回的是&FuncErr,target就声明为*FuncErr。这个细节模型偶尔会忽略。

3. Mock和外部依赖:模型生成的mock基本能用

真实项目中测试需要数据库、文件等外部依赖。单元测试要求稳定性和幂等性——能在任何环境运行,每次结果一致。

Gemini 3.5生成的mock代码风格偏保守,基本是用monkey库的Patch方法替换原函数:

go

monkey.Patch(ReadFileLine, func() string {  return "mock_data" }) defer monkey.Unpatch(ReadFileLine) 

原理是在运行时通过unsafe包替换内存中的函数地址。模型生成这类模式化代码质量稳定。

4. 覆盖率提升:分支覆盖是关键

Go的覆盖率工具以"基本块"为单位标注,通常由大括号界定。一个函数只有if-else两个分支,只测一边覆盖率就只有50%。

我的做法:把覆盖率报告里未覆盖的行号提取出来,针对性地喂给模型补测用例。比如一个判断是否及格的函数,模型默认只生成"输入70"的测试,覆盖率66.7%。加一个不及格的case,就能拉到100%。

循环跑三轮,47% → 71% → 79% → 83%。

5. 模型对比:Flash快但偶尔自信地错

同一批代码我也跑了Claude。结论:

维度 Gemini 3.5 Flash Claude
生成速度 ~290 tokens/s 约80-90 tokens/s
一次可用率 约70% 约80%
覆盖率提升速度 快,用例多 慢,但质量高
审查成本 较高 较低

Flash赢在速度和广度,Claude赢在精度。Flash有时会"自信地写错"——代码看着没问题,跑起来有隐蔽bug。


小结

实操结论:Gemini 3.5 Flash用于Go单元测试生成,覆盖率从47%拉到83%是可以实现的。关键在于分层喂prompt、循环补测、人工审查三步走。errors.Is/errors.As的混淆和recover调用位置是两个高频错误点,审查时重点盯。

趋势判断:Anthropic 2026编程趋势报告指出,AI正从"辅助写代码"进化到"Agent接管开发流程",任务跨度从分钟级扩展到周级。单元测试生成是这个趋势中最容易落地的场景——输入输出明确,结果可量化验证。

一个务实的建议:不同模型各有擅长,日常CRUD和单测用Flash足够,复杂重构和架构设计还是上Sonnet。多模型切换跑,比押注单个模型更稳。

AI是测试实习生,不是测试主管。审查这一步,省不了。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐