开发者工具 · Go 全栈

Go 测试模板生成

函数签名→table-driven 测试模板

本地处理 · 不上传 免费 · 无需登录 无次数限制 累计 45 次使用

Go 测试速查

table-driven / 覆盖率 / benchmark / race 检测 · 点击复制

第一节

关于本工具

About

写完一个 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,结构不统一。本工具将函数签名输入后,生成标准化的测试模板,字段命名和用例结构符合团队规范,直接替换冗余代码。

第二节

使用指南

Getting Started

使用步骤

  1. 1在「函数签名」输入框粘贴 Go 函数声明(如 func Add(a, b int) int),下方自动解析参数名与类型
  2. 2在「测试用例」表格逐行填写输入值(如 a=1, b=2)与期望输出(如 want=3),新增行点「+」按钮
  3. 3点击「生成模板」按钮,右侧代码区立即输出 table-driven 测试代码,含 t.Run 循环与子测试名
  4. 4点代码区右上角「复制」图标,代码写入剪贴板,可直接粘贴到 _test.go 文件中

输入输出示例

输入输出说明
func Add(a, b int) intfunc 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() boolfunc 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) float64func 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 → 输入 GetName
✓ 修复func (u *User) GetName() string → 输入 (u *User) GetName() string

Go 中接收者是方法签名的一部分。只写函数名会丢失接收者类型,生成的测试无法编译,因为测试函数需完整接收者上下文。

2.返回值列表省略括号,导致解析错误

✗ 错误func Sum(a, b int) int, error
✓ 修复func Sum(a, b int) (int, error)

Go 语法规定:多个返回值必须用括号包裹。省略括号后,工具无法正确区分返回值类型与参数列表,生成模板会缺失返回值声明。

3.参数名与类型间加空格过多,干扰解析

✗ 错误func Add( a int, b int) int
✓ 修复func Add(a int, b int) int

虽然 Go 编译器容忍多余空格,但本工具基于正则解析签名,连续空格可能导致参数名/类型匹配失败,生成空字段模板。

4.泛型函数签名未写类型参数约束

✗ 错误func Max[T any](a, b T) T
✓ 修复func Max[T constraints.Ordered](a, b T) T

any 约束过于宽泛,生成的测试用例无法构造有效输入(如无法对任意类型做比较)。应使用具体约束如 constraints.Ordered 以生成可编译的测试。

5.变参函数签名漏写省略号

✗ 错误func Concat(sep string, parts string) string
✓ 修复func Concat(sep string, parts ...string) string

变参必须用 ...type 声明。漏写后工具会认为 parts 是普通 string 参数,生成的测试调用会因参数数量不匹配而编译失败。

6.接口类型参数未写完整包路径

✗ 错误func Process(r io.Reader) error
✓ 修复func Process(r io.Reader) error

io.Reader 是标准库接口,工具默认不导入外部包。若签名含未导入的接口类型,生成的测试文件需手动添加 import "io",否则编译报错。

7.错误地省略函数体花括号

✗ 错误func Hello(name string) string
✓ 修复func Hello(name string) string { return "Hello, " + name }

Go 函数必须有函数体(花括号)。工具解析签名时若检测到花括号,会将其视为代码块而非签名,导致无法提取参数/返回值。

第三节

工作原理

How It Works

核心公式

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 测试用例。

函数签名(参数/返回值)解析签名(提取类型/名称)生成测试结构(table-driven 骨架)输出模板
用户输入 本地处理 输出结果
第五节

常见问题

Q & A
我手头有个函数签名带自定义类型,比如 type MyStruct struct,这个工具能处理吗?

能。本工具只解析函数签名中的参数和返回值类型,不依赖标准库类型。自定义结构体、接口、泛型类型(如 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)自带的测试生成有什么区别?

IDE 的测试生成(如 GoLand 的 Generate Test)通常需要你先有代码文件、选中函数、右键生成,并且生成的模板是 table-driven 风格还是简单 test 风格取决于版本和配置。本工具完全离线、无需安装 IDE,直接在浏览器里粘贴函数签名就能得到纯 table-driven 模板,适合快速批量生成(比如你从文档或 API 规范里复制了多个函数签名)。缺点是它不分析函数体逻辑,所以不会自动生成期望值,而 IDE 可以基于函数实现推断一些简单返回值。

生成的模板里 t.Parallel() 是必须的吗?我想去掉它。

不是必须的。t.Parallel() 是工具默认加的,用于提示可以并行执行测试用例。如果你的测试用例有共享资源(如操作全局变量、写文件),并行可能导致竞态,建议手动删除 t.Parallel() 那行。如果你不需要并行,直接删掉即可,不影响模板结构。另一种做法是把 t.Parallel() 改成注释 // t.Parallel(),方便以后想启用时取消注释。

我输入的函数签名有多个返回值,生成的模板里 want 字段为什么是结构体而不是元组?

Go 的 table-driven 测试惯例中,多个返回值通常用匿名结构体表示,而不是元组,因为 Go 不支持元组类型。工具生成的 want 字段定义为 struct{ result1 Type1; result2 Type2; ... },这样在测试用例里赋值时更清晰(比如 want: struct{ result int; err error }{42, nil})。如果你更喜欢用切片或自定义类型,可以手动修改模板,但结构体方式是最符合 Go 社区常见风格的。

工具支持可变参数(...int)和命名返回值吗?

支持可变参数。如果函数签名是 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 字段只关心类型,不关心返回值变量名。你需要在测试代码里自行通过变量名引用返回值。

隐私保证所有计算与处理均在你的浏览器本地完成,输入数据不会上传服务器,也不会保存或共享。

选择 打开 +新窗口 esc关闭