单元测试
Go 语言测试框架可以让我们很容易地进行单元测试,但是需要遵循下面的规则:
含有单元测试代码的 go 文件必须以
_test.go
结尾,Go 语言测试工具只认符合这个规则的文件。-
单元测试的函数名必须以
Test
开头,是可导出的、公开的函数,测试函数的签名必须接收一个指向testing.T
类型的指针,并且不能返回任何值。func TestFibonacci(t *testing.T) { // ... }
单元测试文件名
_test.go
前面的部分最好是被测试的函数所在的 go 文件的文件名,比如下面示例中单元测试文件叫 main_test.go,因为测试的 Fibonacci 函数在 main.go 文件里。 函数名最好是 Test + 要测试的函数名,比如例子中是 TestFibonacci,表示测试的是 Fibonacci 这个函数。
-
ch/fibonacci.go
package main func Fibonacci(n int) int { if n < 0 { return 0 } if n == 0 { return 0 } if n == 1 { return 1 } return Fibonacci(n-1) + Fibonacci(n-2) }
-
ch/fibonacci_test.go
package main import "testing" func TestFibonacci(t *testing.T) { // 预先定义的一组斐波那契数列作为测试用例(表驱动) caseMap := map[int]int{ 0: 0, 1: 1, 2: 1, 3: 2, 4: 3, 5: 5, 6: 8, 7: 13, 8: 21, } for k, v := range caseMap { fib := Fibonacci(k) if v == fib { t.Logf("结果正确:n为%d,值为%d", k, fib) } else { t.Errorf("结果错误:期望%d,但是计算的值是%d", v, fib) } } }
t.Error = Log + Fail
t.Errorf = Logf + Fail
t.Fatal = Log + FailNow
t.Fatalf = Logf + FailNow
运行单元测试:
# go test -v .
=== RUN TestFibonacci
fibonacci_test.go:22: 结果正确:n为0,值为0
fibonacci_test.go:22: 结果正确:n为2,值为1
fibonacci_test.go:22: 结果正确:n为5,值为5
fibonacci_test.go:22: 结果正确:n为6,值为8
fibonacci_test.go:22: 结果正确:n为8,值为21
fibonacci_test.go:22: 结果正确:n为9,值为34
fibonacci_test.go:22: 结果正确:n为1,值为1
fibonacci_test.go:22: 结果正确:n为3,值为2
fibonacci_test.go:22: 结果正确:n为4,值为3
fibonacci_test.go:22: 结果正确:n为7,值为13
--- PASS: TestFibonacci (0.00s)
PASS
ok ch18/main 0.002s
-v
参数会显示每个用例的测试结果。
断言
Go 语言官方库并没有提供断言的功能,官方认为这可能导致用户懒于思考良好的错误处理和汇报。但没有断言实在是不方便,所以我们可以使用第三方库 testify 来实现:
package main
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestFibonacci(t *testing.T) {
// 预先定义的一组斐波那契数列作为测试用例(表驱动)
caseMap := map[int]int{
0: 0,
1: 1,
2: 1,
3: 2,
4: 3,
5: 5,
6: 8,
7: 13,
8: 21,
}
for k, v := range caseMap {
fib := Fibonacci(k)
assert.Equal(t, fib, v)
}
}
常用的 assert 有:
- assert.Equal
- assert.NotEqual
- assert.Zero
- assert.NotZero
- assert.Nil
- assert.NotNil
setup 和 teardown
如果在同一个测试文件中,每一个测试用例运行前后的逻辑是相同的,一般会写在 setup
和 teardown
函数中。例如执行前需要实例化待测试的对象,如果这个对象比较复杂,很适合将这一部分逻辑提取出来;执行后,可能会做一些资源回收类的工作,例如关闭网络连接,释放文件等
package main
func setup() {
fmt.Println("Before all tests")
}
func teardown() {
fmt.Println("After all tests")
}
func Test1(t *testing.T) {
fmt.Println("I'm test1")
}
func Test2(t *testing.T) {
fmt.Println("I'm test2")
}
func TestMain(m *testing.M) {
setup()
code := m.Run()
teardown()
os.Exit(code)
}
注意:如果测试文件中包含函数 TestMain
,那么生成的测试将调用 TestMain(m)
,而不是直接运行测试,并且一个包中只能有一个TestMain
。
子测试
表驱动
对于多个子测试的场景,更推荐表驱动的写法:
cases := []struct {
Name string
A, B, Expected int
}{
{"pos", 2, 3, 6},
{"neg", 2, -3, -6},
{"zero", 2, 0, 0},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
if ans := Mul(c.A, c.B); ans != c.Expected {
t.Fatalf("%d * %d expected %d, but %d got",
c.A, c.B, c.Expected, ans)
}
})
}
运行项目下的所有测试用例
在项目根目录下执行go test ./...
指定运行部分测试
如果只想运行一部分测试,我们可以在命令时指定:
# go test -run TestFibonacci
并且支持部分通配符*
,和部分正则表达式,例如^
,$
测试覆盖率以及发布报告
-cover
参数可以查看覆盖率。
如果想要测试覆盖率的报告,那么首先:
go test test.go -coverprofile=covprofile
接着再使用工具解析上面的报告,将其转换为html结果网页
go tool cover -html=covprofile -o coverage.html
编译测试为可执行文件
有时,我们编写的测试文件,需要在不同的测试机器上运行,那么可以使用
go test -c
将测试编译为可执行文件。
如何调试测试
- 方法一
# dlv test
- 方法二
使用go test -c
将测试编译为可执行文件,然后再使用 dlv 调试。