每日一Go-23、Go语言实战-质量保证:编写单元测试

0 阅读2分钟

图片

    在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、源码地址

pan.baidu.com/s/1B6pgLWfS…


如果您喜欢这篇文章,请点赞、推荐+分享给更多朋友,万分感谢!