go test - 单元测试
ddatsh
go test 常见做单测试、基准测试和 http 测试
go test 还有很多 flag 可以帮助我们做更多的分析,比如测试覆盖率,cpu 分析,内存分析,也有很多第三方的库支持 test,cpu 和内存分析输出结果要配合 pprof 和 go-torch 来进行可视化显示
命名
文件名 _test.go 结尾,前缀后的第一个字母必须大写
类型 | 函数名前缀 | 作用 |
---|---|---|
测试函数 | Test | 测试程序的一些逻辑行为是否正确 |
基准函数 | Benchmark | 测试函数的性能 |
模糊测试 | Fuzz | 测试程序的健壮性 |
示例函数 | Example | 为文档提供示例文档 |
main函数 | TestMain(m *testing.M) | 测试 main 函数 |
func TestAdd(t *testing.T){ ... }
func TestSum(t *testing.T){ ... }
func TestLog(t *testing.T){ ... }
func BenchmarkAdd(b *testing.B) { ... }
常用命令/参数
- -v:显示详细的测试输出,包括每个测试用例的名称和结果
- -run:指定要运行的测试函数的正则表达式
- -cover:同时进行代码覆盖率分析,显示代码被执行的情况
- -coverprofile:将代码覆盖率分析的结果输出到指定文件中
- -count:指定测试的运行次数,默认为 1 次
- -timeout:设置测试的运行超时时间
- -bench:运行与性能测试有关的基准测试
- -benchmem:在运行基准测试时显示内存分配的统计信息
- 运行当前 package 内的用例:
go test example
或go test .
- 运行子 package 内的用例:
go test example/<package name>
或go test ./<package name>
- 递归测试当前目录下的所有的 package:
go test ./...
或go test example/...
- 执行特定的测试用例
go test -v . -test.run 'Fib'
- 默认不输出 log,-v 输出
# 执行当前目录下的全部测试用例,不递归子目录中的测试用例
go test .
# 执行当前目录下的全部测试用例,递归子目录中的测试用例
go test ./...
# 执行当前目录下的全部测试用例并显示测试过程中的日志内容,不递归子目录中的测试用例
go test -v .
# 执行当前目录下的全部测试用例并显示测试过程中的日志内容,递归子目录中的测试用例
go test -v ./...
# 执行指定的测试用例
go test -v . -test.run '^TestValid$'
# 测试所有包含 user(不区分大小写) 的测试方法
go test -v -run="(?i)user"
# 测试多个方法,名称用 "|" 分开
go test -v -run="TestGetOrderList|TestNewUserInfo"
*testing.T
管理测试状态并支持格式化测试日志
方法:
func (c *T) Cleanup(func())
func (c *T) Error(args ...interface{})
func (c *T) Errorf(format string, args ...interface{})
func (c *T) Fail()
func (c *T) FailNow()
func (c *T) Failed() bool
func (c *T) Fatal(args ...interface{})
func (c *T) Fatalf(format string, args ...interface{})
func (c *T) Helper()
func (c *T) Log(args ...interface{})
func (c *T) Logf(format string, args ...interface{})
func (c *T) Name() string
func (c *T) Skip(args ...interface{})
func (c *T) SkipNow()
func (c *T) Skipf(format string, args ...interface{})
func (c *T) Skipped() bool
func (c *T) TempDir() string
Fail : 测试失败,测试继续,之后的代码依然会执行
FailNow : 测试失败,测试中断
go help test
go help testflag
go mod init example
单元测试
calc
example/
|--calc.go
|--calc_test.go
// calc.go
package main
func Add(a int, b int) int {
return a + b
}
func Mul(a int, b int) int {
return a * b
}
// calc_test.go
package main
import "testing"
func TestAdd(t *testing.T) {
if ans := Add(1, 2); ans != 3 {
t.Errorf("1 + 2 expected be 3, but %d got", ans)
}
if ans := Add(-10, -20); ans != -30 {
t.Errorf("-10 + -20 expected be -30, but %d got", ans)
}
}
example/
|--fib.go
|--fib_test.go
// fib.go
package main
func Fib(n int) int {
if n == 0 || n == 1 {
return n
}
return Fib(n-2) + Fib(n-1)
}
// fib_test.go
package main
import "testing"
func TestFib(t *testing.T) {
var (
in = 7
expected = 13
)
actual := Fib(in)
if actual != expected {
t.Errorf("Fib(%d) = %d; expected %d", in, actual, expected)
}
}
Table-Driven Test
func TestFib(t *testing.T) {
var fibTests = []struct {
in int // input
expected int // expected result
}{
{1, 1},
{2, 1},
{3, 2},
{4, 3},
{5, 5},
{6, 8},
{7, 13},
}
for _, tt := range fibTests {
actual := Fib(tt.in)
if actual != tt.expected {
t.Errorf("Fib(%d) = %d; expected %d", tt.in, actual, tt.expected)
}
}
}
t.Errorf
,即使其中某个 case 失败,也不会终止测试执行
Parallel
当前测试只会与其他带有 Parallel 方法的测试并行进行测试
// parallel.go
package main
import "sync"
var (
data = make(map[string]string)
locker sync.RWMutex
)
func WriteToMap(k, v string) {
locker.Lock()
defer locker.Unlock()
data[k] = v
}
func ReadFromMap(k string) string {
locker.RLock()
defer locker.RUnlock()
return data[k]
}
// parallel_test.go
package main
import "testing"
var pairs = []struct {
k string
v string
}{
{"go", " go语言"},
{"stdlib", "Go 语言标准库"},
}
// 注意 TestWriteToMap 需要在 TestReadFromMap 之前
func TestWriteToMap(t *testing.T) {
t.Parallel()
for _, tt := range pairs {
WriteToMap(tt.k, tt.v)
}
}
func TestReadFromMap(t *testing.T) {
t.Parallel()
for _, tt := range pairs {
actual := ReadFromMap(tt.k)
if actual != tt.v {
t.Errorf("the value of key(%s) is %s, expected: %s", tt.k, actual, tt.v)
}
}
}
步骤:
- 注释掉 WriteToMap 和 ReadFromMap 中 locker 保护的代码,同时注释掉测试代码中的 t.Parallel,执行测试,测试通过,即使加上
-race
,测试依然通过 - 只注释掉 WriteToMap 和 ReadFromMap 中 locker 保护的代码,执行测试,测试失败(如果未失败,加上
-race
一定会失败)
如果代码能够进行并行测试,在写测试时,尽量加上 Parallel,这样可以测试出一些可能的问题
TempDir
自动为测试创建临时目录并在测试完成时删除该文件夹的方法,无需编写额外清理逻辑
func TestFooerTempDir(t *testing.T) {
tmpDir := t.TempDir()
// your tests
}
Coverage
go test -cover
go test -v . -coverprofile=coverage.out -covermode=atomic
go tool cover -html=coverage.out -o cover.html
-
-coverprofile 指定覆盖测试结果文件
-
-covermode 指定覆盖测试的方式,set, count, atomic 三个值,默认值 set,-race 时默认 atomic,绝大多数情况下可以统一使用 atomic
set
:覆盖率基于语句count
:计数是一条语句运行的次数。 它可以显示代码的哪些部分仅被轻微覆盖atomic
:与计数类似,但用于并行测试
GoLand 默认使用
atomic
模式
-coverpkg
go test
覆盖率计算中只考虑带有测试文件的软件包。 -coverpkg
将所有软件包添加到覆盖率计算中
go test ./... -coverpkg=./...
https://github.com/asspirin12/go_cover
详细展示了您需要 -coverpkg
的原因
分析结果
perf/cmd
提供了软件包
benchstat
可用于分析结果
benchsave
可用于保存结果
Example
func ExampleFib() {
fmt.Println(Fib(1))
// Output: 1
}
套件
- testify 做 assert 判断
- testify 构建 单元测试集合,可以写 setup/teardown
- gomonkey 做 变量,函数,普通的成员方法(公有,私有)的 mock
- mockery 做接口层面 mock
- miniredis 做 redis 的 mock
ref
-
《持续交付 2.0》
-
《单元测试的艺术》
-
《代码大全》
-
github.com/stretchr/testify github.com/jarcoal/httpmock