1. 一个简单的例子
测试文件以 _test.go 结尾
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)
}
}
- 测试用例名称一般命名为 Test 加上待测试的方法名。
- 测试用的参数有且只有一个,在这里是 t *testing.T。
- 基准测试(benchmark)的参数是 *testing.B,TestMain 的参数是 *testing.M 类型。
运行 go test,该 package 下所有的测试用例都会被执行。
$ go test
ok example 0.009s
或 go test -v,-v 参数会显示每个用例的测试结果,另外 -cover 参数可以查看覆盖率。
$ go test -v
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
=== RUN TestMul
--- PASS: TestMul (0.00s)
PASS
ok example 0.007s
如果只想运行其中的一个用例,例如 TestAdd,可以用 -run 参数指定,该参数支持通配符 *,和部分正则表达式,例如 ^、$。
$ go test -run TestAdd -v
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok example 0.007s
2. 子测试(subtests)
子测试是 Go 语言内置支持的,可以在某个测试用例中,根据测试场景使用 t.Run创建不同的子测试用例:
// calc_test.go
func TestMul(t *testing.T) {
t.Run("pos", func(t *testing.T) {
if Mul(2, 3) != 6 {
t.Fatal("fail")
}
})
t.Run("neg", func(t *testing.T) {
if Mul(2, -3) != -6 {
t.Fatal("fail")
}
})
}
-
t.Error/t.Errorf
遇错就停 -
t.Fatal/t.Fatalf
遇错不停
运行某个测试用例的子测试:
$ go test -run TestMul/pos -v
=== RUN TestMul
=== RUN TestMul/pos
--- PASS: TestMul (0.00s)
--- PASS: TestMul/pos (0.00s)
PASS
ok example 0.008s
对于多个子测试的场景,更推荐如下的写法(table-driven tests):
// calc_test.go
func TestMul(t *testing.T) {
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)
}
})
}
}
所有用例的数据组织在切片 cases 中,看起来就像一张表,借助循环创建子测试。这样写的好处有:
- 新增用例非常简单,只需给 cases 新增一条测试数据即可。
- 测试代码可读性好,直观地能够看到每个子测试的参数和期待的返回值。
- 用例失败时,报错信息的格式比较统一,测试报告易于阅读。
帮助函数(helpers)
对一些重复的逻辑,抽取出来作为公共的帮助函数(helpers),可以增加测试代码的可读性和可维护性。 借助帮助函数,可以让测试用例的主逻辑看起来更清晰。
例如,我们可以将创建子测试的逻辑抽取出来:
// calc_test.go
package main
import "testing"
type calcCase struct{ A, B, Expected int }
func createMulTestCase(t *testing.T, c *calcCase) {
// t.Helper()
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)
}
}
func TestMul(t *testing.T) {
createMulTestCase(t, &calcCase{2, 3, 6})
createMulTestCase(t, &calcCase{2, -3, -6})
createMulTestCase(t, &calcCase{2, 0, 1}) // wrong case
}
在这里,我们故意创建了一个错误的测试用例,运行 go test,用例失败,会报告错误发生的文件和行号信息:
$ go test
--- FAIL: TestMul (0.00s)
calc_test.go:11: 2 * 0 expected 1, but 0 got
FAIL
exit status 1
FAIL example 0.007s
可以看到,错误发生在第11行,也就是帮助函数 createMulTestCase 内部。18, 19, 20行都调用了该方法,我们第一时间并不能够确定是哪一行发生了错误。有些帮助函数还可能在不同的函数中被调用,报错信息都在同一处,不方便问题定位。因此,Go 语言在 1.9 版本中引入了 t.Helper(),用于标注该函数是帮助函数,报错时将输出帮助函数调用者的信息,而不是帮助函数的内部信息。
修改 createMulTestCase,调用 t.Helper()
func createMulTestCase(c *calcCase, t *testing.T) {
t.Helper()
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,报错信息如下,可以非常清晰地知道,错误发生在第 20 行。
$ go test
--- FAIL: TestMul (0.00s)
calc_test.go:20: 2 * 0 expected 1, but 0 got
FAIL
exit status 1
FAIL example 0.006s
关于 helper 函数的 2 个建议:
-
不要返回错误, 帮助函数内部直接使用 t.Error 或 t.Fatal 即可,在用例主逻辑中不会因为太多的错误处理代码,影响可读性。
-
调用 t.Helper() 让报错信息更准确,有助于定位
-
参考:文章来源:https://www.toymoban.com/news/detail-703011.html
-
Go Test 单元测试简明教程
文章来源地址https://www.toymoban.com/news/detail-703011.html
到了这里,关于Go单元测试入门的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!