测试
Go 内置了强大的测试框架,本章将详细介绍 Go 的测试方法。
基本测试
测试函数
go
package math
import "testing"
func Add(a, b int) int {
return a + b
}
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) = %d; want %d", result, expected)
}
}
运行测试
bash
# 运行所有测试
go test
# 运行指定包的测试
go test ./math
# 详细输出
go test -v
# 运行指定测试函数
go test -run TestAdd
表格驱动测试
go
func TestAddTable(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"正数", 2, 3, 5},
{"负数", -2, -3, -5},
{"零", 0, 0, 0},
{"混合", -2, 3, 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
}
})
}
}
子测试
go
func TestSubtests(t *testing.T) {
t.Run("Group1", func(t *testing.T) {
t.Run("Test1", func(t *testing.T) {
if Add(1, 2) != 3 {
t.Error("Test1 failed")
}
})
t.Run("Test2", func(t *testing.T) {
if Add(2, 3) != 5 {
t.Error("Test2 failed")
}
})
})
}
基准测试
基本基准测试
go
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}
运行基准测试
bash
# 运行基准测试
go test -bench=.
# 运行指定基准测试
go test -bench=BenchmarkAdd
# 指定运行时间
go test -bench=. -benchtime=5s
# 内存分析
go test -bench=. -benchmem
基准测试示例
go
func BenchmarkStringConcat(b *testing.B) {
b.Run("Plus", func(b *testing.B) {
for i := 0; i < b.N; i++ {
s := "hello" + "world"
_ = s
}
})
b.Run("Sprintf", func(b *testing.B) {
for i := 0; i < b.N; i++ {
s := fmt.Sprintf("%s%s", "hello", "world")
_ = s
}
})
}
示例测试
基本示例
go
func ExampleAdd() {
result := Add(2, 3)
fmt.Println(result)
// Output: 5
}
运行示例测试
bash
go test -run ExampleAdd
测试辅助函数
Skip
go
func TestSkip(t *testing.T) {
if testing.Short() {
t.Skip("跳过长时间测试")
}
// 长时间测试...
}
Parallel
go
func TestParallel(t *testing.T) {
t.Run("Group", func(t *testing.T) {
tests := []struct {
name string
fn func(t *testing.T)
}{
{"Test1", func(t *testing.T) { t.Parallel() }},
{"Test2", func(t *testing.T) { t.Parallel() }},
{"Test3", func(t *testing.T) { t.Parallel() }},
}
for _, tt := range tests {
t.Run(tt.name, tt.fn)
}
})
}
Cleanup
go
func TestCleanup(t *testing.T) {
file, err := os.Create("test.txt")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
file.Close()
os.Remove("test.txt")
})
// 测试代码...
}
Mock 和 Stub
接口 Mock
go
type Database interface {
Get(id int) (string, error)
Save(id int, data string) error
}
type MockDatabase struct {
data map[int]string
}
func (m *MockDatabase) Get(id int) (string, error) {
return m.data[id], nil
}
func (m *MockDatabase) Save(id int, data string) error {
m.data[id] = data
return nil
}
func TestWithMock(t *testing.T) {
mock := &MockDatabase{data: make(map[int]string)}
mock.Save(1, "test")
result, err := mock.Get(1)
if err != nil {
t.Fatal(err)
}
if result != "test" {
t.Errorf("expected 'test', got '%s'", result)
}
}
测试覆盖率
生成覆盖率报告
bash
# 生成覆盖率
go test -cover
# 生成覆盖率文件
go test -coverprofile=coverage.out
# 查看覆盖率报告
go tool cover -html=coverage.out
# 输出到文件
go tool cover -html=coverage.out -o coverage.html
设置覆盖率阈值
bash
# 覆盖率低于 80% 失败
go test -coverprofile=coverage.out -covermode=atomic
go tool cover -func=coverage.out | grep total
测试最佳实践
1. 保持测试独立
go
// 推荐:每个测试独立
func TestA(t *testing.T) {
// 测试 A
}
func TestB(t *testing.T) {
// 测试 B
}
// 不推荐:测试依赖
var sharedVar int
func TestA(t *testing.T) {
sharedVar = 1
}
func TestB(t *testing.T) {
// 依赖 TestA 的结果
}
2. 使用表驱动测试
go
// 推荐
func TestAdd(t *testing.T) {
tests := []struct {
a, b, expected int
}{
{1, 2, 3},
{2, 3, 5},
}
for _, tt := range tests {
if got := Add(tt.a, tt.b); got != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.expected)
}
}
}
3. 测试边界条件
go
func TestAdd(t *testing.T) {
tests := []struct {
a, b, expected int
}{
{0, 0, 0}, // 零
{1, -1, 0}, // 负数
{1<<30, 1<<30, 1<<31}, // 大数
}
// ...
}
测试工具
testify
go
import "github.com/stretchr/testify/assert"
func TestWithTestify(t *testing.T) {
assert.Equal(t, 5, Add(2, 3))
assert.NotNil(t, "hello")
assert.True(t, true)
}
gomock
go
import "github.com/golang/mock/gomock"
func TestWithGomock(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mock := NewMockDatabase(ctrl)
mock.EXPECT().Get(1).Return("test", nil)
result, err := mock.Get(1)
assert.NoError(t, err)
assert.Equal(t, "test", result)
}
练习
练习 1:编写测试
为以下函数编写测试:
go
func Max(a, b int) int {
if a > b {
return a
}
return b
}
答案
go
func TestMax(t *testing.T) {
tests := []struct {
a, b, expected int
}{
{1, 2, 2},
{2, 1, 2},
{1, 1, 1},
{-1, 1, 1},
}
for _, tt := range tests {
result := Max(tt.a, tt.b)
if result != tt.expected {
t.Errorf("Max(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
}
}
}
总结
- 使用 testing 包编写测试
- 表格驱动测试提高可读性
- 基准测试测量性能
- 使用 Mock 隔离依赖
- 测试覆盖率保证质量
- 遵循测试最佳实践
下一章:性能优化