Go 测试速查
table-driven / 覆盖率 / benchmark / race 检测 · 点击复制
table-driven / 覆盖率 / benchmark / race 检测 · 点击复制
写完一个 Go 函数签名,手动补齐测试用例的 table 结构——字段名、输入值、期望值,重复劳动挤占了本应花在边界条件上的思考。把函数签名贴进去,工具直接拆出参数名和类型,生成带占位符的 table-driven 测试骨架。纯前端处理,函数签名不会离开浏览器。
接手一个遗留 Go 项目,重构了核心模块的 5 个函数。重构后需要补全单元测试,但手写 table-driven 测试模板太繁琐,每个函数要手动复制签名、构造 struct、写测试用例骨架。本工具直接粘贴函数签名,自动生成测试模板,包含字段名、类型和零值初始化,省去重复劳动,让注意力集中在用例逻辑上。
团队来了个 Go 新手,第一次接触 table-driven 测试。他需要为 3 个业务函数写测试,但不确定测试 struct 怎么写、字段名怎么命名、expected 字段放哪。本工具从函数签名自动推导出测试模板,字段类型和命名规范一目了然,新人照着模板填空就能写出符合团队风格的测试,减少 code review 时的格式纠正。
后端开发需要为 8 个 HTTP handler 函数写单元测试,每个 handler 接受不同结构体参数。手动创建 8 个测试文件、每个文件写 table-driven 模板太耗时。本工具一次性处理多个函数签名,批量生成测试模板文件,每个文件已包含正确的 import 和测试函数骨架,只需填充测试数据和断言逻辑。
团队要求所有单元测试必须使用 table-driven 模式,但部分老代码的测试是散乱写的。代码审查时发现一个函数有 6 个测试用例,每个用例单独写一个 t.Run,结构不统一。本工具将函数签名输入后,生成标准化的测试模板,字段命名和用例结构符合团队规范,直接替换冗余代码。
| 输入 | 输出 | 说明 |
|---|---|---|
| func Add(a, b int) int | func TestAdd(t *testing.T) { tests := []struct { name string a int b int want int }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Add(tt.a, tt.b); got != tt.want { t.Errorf("Add() = %v, want %v", got, tt.want) } }) } } | 常规:两个 int 参数 + 一个 int 返回值,是最常见的纯函数签名,生成的结构体字段与参数一一对应。 |
| func (u *User) IsAdult() bool | func TestUser_IsAdult(t *testing.T) { tests := []struct { name string u *User want bool }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.u.IsAdult(); got != tt.want { t.Errorf("User.IsAdult() = %v, want %v", got, tt.want) } }) } } | 常规:带指针接收者的方法,结构体字段名自动取接收者类型首字母小写(u),测试调用方式为 tt.u.Method()。 |
| func Process(ctx context.Context, data []byte) (string, error) | func TestProcess(t *testing.T) { tests := []struct { name string ctx context.Context data []byte want string wantErr bool }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := Process(tt.ctx, tt.data) if (err != nil) != tt.wantErr { t.Errorf("Process() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("Process() = %v, want %v", got, tt.want) } }) } } | 边界:多返回值含 error 时,模板自动生成 wantErr 字段和错误检查分支;context.Context 作为参数时保留完整类型名。 |
| func (s *Server) Handle(w http.ResponseWriter, r *http.Request) | func TestServer_Handle(t *testing.T) { tests := []struct { name string s *Server w http.ResponseWriter r *http.Request }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.s.Handle(tt.w, tt.r) }) } } | 边界:无返回值时,模板不生成 want 和 wantErr 字段,测试仅验证不 panic;适合测试 side-effect 方法。 |
| func Calculate(v ...int) float64 | func TestCalculate(t *testing.T) { tests := []struct { name string v []int want float64 }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Calculate(tt.v...); got != tt.want { t.Errorf("Calculate() = %v, want %v", got, tt.want) } }) } } | 易错:变参参数被转为切片字段(v []int),调用时自动展开为 tt.v...,若用户手动写多个参数会编译错误。 |
| func (p *Parser) Parse(input string) (result map[string]int, err error) | func TestParser_Parse(t *testing.T) { tests := []struct { name string p *Parser input string want map[string]int wantErr bool }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.p.Parse(tt.input) if (err != nil) != tt.wantErr { t.Errorf("Parser.Parse() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("Parser.Parse() = %v, want %v", got, tt.want) } }) } } | 易错:返回 map/slice 等引用类型时,模板自动使用 reflect.DeepEqual 比较而非 ==,避免直接比较导致的编译错误。 |
1.函数签名含接收者,却只写函数名
func (u *User) GetName() string → 输入 GetNamefunc (u *User) GetName() string → 输入 (u *User) GetName() stringGo 中接收者是方法签名的一部分。只写函数名会丢失接收者类型,生成的测试无法编译,因为测试函数需完整接收者上下文。
2.返回值列表省略括号,导致解析错误
func Sum(a, b int) int, errorfunc Sum(a, b int) (int, error)Go 语法规定:多个返回值必须用括号包裹。省略括号后,工具无法正确区分返回值类型与参数列表,生成模板会缺失返回值声明。
3.参数名与类型间加空格过多,干扰解析
func Add( a int, b int) intfunc Add(a int, b int) int虽然 Go 编译器容忍多余空格,但本工具基于正则解析签名,连续空格可能导致参数名/类型匹配失败,生成空字段模板。
4.泛型函数签名未写类型参数约束
func Max[T any](a, b T) Tfunc Max[T constraints.Ordered](a, b T) Tany 约束过于宽泛,生成的测试用例无法构造有效输入(如无法对任意类型做比较)。应使用具体约束如 constraints.Ordered 以生成可编译的测试。
5.变参函数签名漏写省略号
func Concat(sep string, parts string) stringfunc Concat(sep string, parts ...string) string变参必须用 ...type 声明。漏写后工具会认为 parts 是普通 string 参数,生成的测试调用会因参数数量不匹配而编译失败。
6.接口类型参数未写完整包路径
func Process(r io.Reader) errorfunc Process(r io.Reader) errorio.Reader 是标准库接口,工具默认不导入外部包。若签名含未导入的接口类型,生成的测试文件需手动添加 import "io",否则编译报错。
7.错误地省略函数体花括号
func Hello(name string) stringfunc Hello(name string) string { return "Hello, " + name }Go 函数必须有函数体(花括号)。工具解析签名时若检测到花括号,会将其视为代码块而非签名,导致无法提取参数/返回值。
TestTemplate = { Name: "Test{FunctionName}", Input: { args: args, want: want } }
FunctionName被测函数名,首字母大写args函数输入参数,按签名顺序want期望输出值,与返回类型一致函数签名 func Add(a int, b int) int,生成测试模板:{ Name: "TestAdd", Input: { args: []int{1, 2}, want: 3 } },推导:输入 1+2 期望 3,模板结构为 table-driven 测试用例。
能。本工具只解析函数签名中的参数和返回值类型,不依赖标准库类型。自定义结构体、接口、泛型类型(如 T any)都会被当作合法类型名保留在模板中。你只需要把完整的函数签名粘贴进去,比如 func Foo(a MyStruct, b int) error,生成的模板里测试用例的输入字段会标注为 MyStruct 和 int,你手动替换成具体构造值即可。注意:类型别名(type MyInt int)也能识别,但不会自动展开成底层类型。
模板中每个测试用例的 name 字段是占位符,默认生成 TestFoo/case_0、TestFoo/case_1 这种序号式命名,方便你快速区分。如果你需要语义化命名(比如 TestFoo/empty_input),直接在生成的 JSON 里把 name 字段改成你想要的字符串即可,工具不会覆盖你后续的修改。建议在粘贴到编辑器前先全局替换 case_ 为你的前缀,比如用编辑器查找替换功能把 case_ 改成 zero_value_。
本工具假设输入是一行纯函数签名,不包含换行、注释或多余空格。如果你从代码里复制了带换行或 // 注释的内容,解析器可能会把注释当作类型名的一部分,导致模板里出现类似 error // returns nil 的奇怪字段。解决办法:粘贴前先手动把函数签名整理成一行,去掉注释。比如把 func Foo(a int, // 参数a b string) error 改成 func Foo(a int, b string) error。后续版本会考虑自动过滤注释。
模板是可直接运行的骨架,但测试用例的输入和预期输出需要你填充。具体来说:模板会生成 TestXxx 函数、t.Parallel()、以及一个包含 name、args、want 三个字段的测试用例切片。args 结构体字段对应函数参数名,want 对应返回值类型。你只需要把 args 里的零值改成真实测试数据,把 want 改成期望结果即可。比如函数返回 (int, error),模板里 want 会是 struct{ result int; err error },你填上具体值就能 go test 跑通。
IDE 的测试生成(如 GoLand 的 Generate Test)通常需要你先有代码文件、选中函数、右键生成,并且生成的模板是 table-driven 风格还是简单 test 风格取决于版本和配置。本工具完全离线、无需安装 IDE,直接在浏览器里粘贴函数签名就能得到纯 table-driven 模板,适合快速批量生成(比如你从文档或 API 规范里复制了多个函数签名)。缺点是它不分析函数体逻辑,所以不会自动生成期望值,而 IDE 可以基于函数实现推断一些简单返回值。
不是必须的。t.Parallel() 是工具默认加的,用于提示可以并行执行测试用例。如果你的测试用例有共享资源(如操作全局变量、写文件),并行可能导致竞态,建议手动删除 t.Parallel() 那行。如果你不需要并行,直接删掉即可,不影响模板结构。另一种做法是把 t.Parallel() 改成注释 // t.Parallel(),方便以后想启用时取消注释。
Go 的 table-driven 测试惯例中,多个返回值通常用匿名结构体表示,而不是元组,因为 Go 不支持元组类型。工具生成的 want 字段定义为 struct{ result1 Type1; result2 Type2; ... },这样在测试用例里赋值时更清晰(比如 want: struct{ result int; err error }{42, nil})。如果你更喜欢用切片或自定义类型,可以手动修改模板,但结构体方式是最符合 Go 社区常见风格的。
支持可变参数。如果函数签名是 func Sum(nums ...int) int,生成的模板中 args 结构体会有一个字段名为 nums 类型为 []int,测试用例里你可以传 []int{1,2,3}。命名返回值(如 func Div(a, b int) (result int, err error))会被工具解析为返回值类型 int 和 error,命名信息不会保留在模板中,因为 table-driven 测试的 want 字段只关心类型,不关心返回值变量名。你需要在测试代码里自行通过变量名引用返回值。
隐私保证所有计算与处理均在你的浏览器本地完成,输入数据不会上传服务器,也不会保存或共享。