在Go语言中,测试是“语言级别”的一等公民。官方内置testing包,让你不需要引入任何框架,就能编写:单元测试(Unit Test)、基准测试(Benchmark)、示例测试(Example Test)、简单Mock、表格驱动测试等结构体模式
文末有源码下载链接
1、testing 包基本规则
1.1 测试文件必须以"_test.go"结尾,例如:math_test.go
1.2 测试函数签名
1.2.1 单元测试
func
TestXxx
*
T
1.2.2 基准测试
func
BenchmarkXxx
*
B
1.2.3 示例测试
func
ExampleXxx
2、单元测试
// math.go
package math
// Add 返回两数之和
func Add(a, b int) int {
return a + b
}
// math_test.go
package math
import "testing"
func TestAdd(t *testing.T) {
res := Add(3, 7)
if res != 10 {
t.Errorf("应该是10,结果是%d", res)
}
}
运行测试命令:
$ go test
PASS
ok go-test/math 0.410s
3、表格驱动测试(Go最推荐的方式)
func TestTableAdd(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"正数", 3, 7, 10},
{"零值", 0, 8, 8},
{"负数", -6, 6, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.want {
t.Fatalf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want)
}
})
}
}
运行测试命令:
$ go test
PASS
ok go-test/math 0.390s
//测试失败的例子
$ go test
--- FAIL: TestTableAdd (0.00s)
--- FAIL: TestTableAdd/正数 (0.00s)
math_test.go:26: Add(3, 7) = 10; want 101
--- FAIL: TestTableAdd/零值 (0.00s)
math_test.go:26: Add(0, 8) = 8; want 81
--- FAIL: TestTableAdd/负数 (0.00s)
math_test.go:26: Add(-6, 6) = 0; want 1
FAIL
exit status 1
FAIL go-test/math 0.370s
好处:每条case独立运行;报错时输出清晰;特别适用于大量输入组合测试。
4、覆盖率(Coverage):测试覆盖率是衡量测试水平的重要指标
4.1 运行覆盖率
$ go test -cover
PASS
coverage: 100.0% of statements
ok go-test/math 1.579s
4.2 生成详细报告html
$ go test -coverprofile=cover.out
PASS
coverage: 100.0% of statements
ok go-test/math 0.382s
// 以html形式展现覆盖率,运行后会自动打开浏览器展示结果
$ go tool cover -html=cover.out
如上图,你将看到哪些代码被覆盖、哪些未执行。
5、基准测试(Benchmark)
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(3, 7)
}
}
运行基准测试命令:
$ go test -bench=.
goos: windows
goarch: amd64
pkg: go-test/math
cpu: AMD Ryzen 7 6800HS Creator Edition
BenchmarkAdd-16 1000000000 0.3485 ns/op
PASS
ok go-test/math 0.784s
加上内存分配:
$ go test -bench=. -benchmem
goos: windows
goarch: amd64
pkg: go-test/math
cpu: AMD Ryzen 7 6800HS Creator Edition
BenchmarkAdd-16 1000000000 0.3454 ns/op 0 B/op 0 allocs/op
PASS
ok go-test/math 0.772s
说明:
0.3454 ns/op :每次操作耗时
0 B/op : 每次操作分配的字节数
0 allocs/op : 每次操作分配次数
6、示例测试(Example)
func ExampleAdd() {
fmt.Println(Add(3, 7))
// Output: 10
}
运行测试命令:
$ go test
PASS
ok go-test/math 1.502s
//失败的示例
$ go test
--- FAIL: ExampleAdd (0.00s)
got:
10
want:
21
FAIL
exit status 1
FAIL go-test/math 1.401s
示例测试自动生成到文档里
// 安装godoc工具
$ go install golang.org/x/tools/cmd/godoc@latest
$ godoc -http=:6060
浏览器访问 http://localhost:6060/pkg/go-test/math/,就可以看见示例Example在文档里了,如下图
7、Mock技巧
Go不需要框架就能mock(依赖注入理念)
// mock/store.go
package mock
type Store interface {
Save(name string) error
}
func DoSave(st Store) error {
return st.Save(`Codee君`)
}
type mockStore struct {
called bool
}
func (m *mockStore) Save(name string) error {
m.called = true
return nil
}
// mock/store_test.go
package mock
import "testing"
func TestDoSave(t *testing.T) {
ms := &mockStore{}
DoSave(ms)
if !ms.called {
t.Fatal("方法Save()需要被调用")
}
}
运行测试命令:
$ go test
PASS
ok go-test/mock 1.484s
//失败的例子
$ go test
--- FAIL: TestDoSave (0.00s)
store_test.go:9: Save 方法需要被调用
FAIL
exit status 1
FAIL go-test/mock 1.339s
8、Go单元测试最佳实践
| 实践 | 描述 |
| 使用表格驱动测试 | 最清晰、扩展性最强的结构 |
| 测试只测试“行为”,不测实现细节 | 避免改一点实现就导致大量测试崩溃 |
| 小步测试 | 每个测试只验证一个行为 |
| 使用t.Run | 子用例,结构更清晰 |
| 使用 -race | 捕获数据竞争 |
| 基准测试必须避免IO、打印 | 避免干扰测试结果 |
| 保持测试独立 | 不依赖全局状态,不依赖真实外部系统 |
在团队协作和生产环境中,良好的单元测试可以:保证功能稳定、让重构更放心、使发布更可控
9、源码地址
如果您喜欢这篇文章,请点赞、推荐+分享给更多朋友,万分感谢!