在前面的例子中,我们需要依靠在 main 函数中调用包内的函数,根据 fmt.Println 来确定函数是不是按照预期的工作,这非常的不方便,因为函数可能是包内小写的私有函数,它并不需要暴露到外面,而且函数可能压根不需要输出任何东西,它只要把数据处理好就行了,所以得学会怎么测试代码的正确性。
把上个章节的 incUpdateScore 函数复制一下,放到 varpointer 包下面取名 memaddr.go,内容如下:
func incUpdateScore(ptrScore *int) {
*ptrScore += 2
}
同样的目录下新建一个 memaddr_test.go 的文件,注意以 _test 结尾,输入以下内容:
func Test_incUpdateScore(t *testing.T) {
score := 10
expected := 11
incUpdateScore(&score)
if score != expected {
t.Errorf("有问题, 期望 %d, 实际是 %d", expected, score)
}
}
注意函数名称以 Test 开头,参数类型是 *testing.T,在函数名称上出现了一排工具栏,点 run test 后,输出如下:
--- FAIL: Test_incUpdateScore (0.00s)
/Users/wangbo/Desktop/go-demo/07-code-testing/varpointer/memaddr_test.go:11: 有问题, 期望 11, 实际是 12
FAIL
FAIL 07-code-testing/varpointer 0.465s
FAIL
测试失败了,期待加上 1,结果加了2,修复代码:
func incUpdateScore(ptrScore *int) {
*ptrScore++
}
现在结果显示:
ok 07-code-testing/varpointer 0.785s
说明函数测试通过了,在终端 shell 使用 go test -v ./… 依然显示测试通过:
07-code-testing[master*] go test -v ./...
? 07-code-testing[no test files]
=== RUN Test_incUpdateScore
11
--- PASS: Test_incUpdateScore (0.00s)
PASS
ok 07-code-testing/varpointer0.341s
小结一下,测试包内的函数步骤:
1. 新建一个 xx_test.go 的文件
2. 新建函数 Test_funcName,它的参数 t *testing.T
3. 编写测试代码
4. 使用 go test 命令来运行测试文件
现在我们来对 incUpdateScore 进行重构,我们希望递增的分数是以参数的形式传进去:
func incUpdateScore(ptrScore *int, increment int) {
*ptrScore += increment
}
保存文件后发现 vscode 自动运行了测试,并且报告了测试失败:
# 07-code-testing/varpointer [07-code-testing/varpointer.test]
/Users/wangbo/Desktop/go-demo/07-code-testing/varpointer/memaddr_test.go:8:16: not enough arguments in call to incUpdateScore
have (*int)
want (*int, int)
FAIL 07-code-testing/varpointer [build failed]
FAIL
编译都没有通过,因为我们修改了函数签名,但是还没有修改测试代码,重构测试代码如下:
func Test_incUpdateScore(t *testing.T) {
score := 10
expected := 11
incUpdateScore(&score, 1)
if score != expected {
t.Errorf("有问题, 期望 %d, 实际是 %d", expected, score)
}
}
保存后看到测试通过了,传入正数函数工作正常,我们想测试一下负数的情况,使用 t.Run 来包装 2 个测试场景,继续重构为 2 个子测试代码如下:
func Test_incUpdateScore(t *testing.T) {
t.Run("should increment score by + number", func(t *testing.T) {
score := 10
expected := 11
incUpdateScore(&score, 1)
if score != expected {
t.Errorf("有问题, 期望 %d, 实际是 %d", expected, score)
}
})
t.Run("should increment score by - number", func(t *testing.T) {
score := 10
expected := 9
incUpdateScore(&score, -1)
if score != expected {
t.Errorf("有问题, 期望 %d, 实际是 %d", expected, score)
}
})
}
再次运行测试也通过了,说明函数 incUpdateScore 具有预期的行为,这个过程我没有没有借助 main 函数和 fmt 包就完成了测试和重构。
07-code-testing[master*] go test -v ./...
? 07-code-testing[no test files]
=== RUN Test_incUpdateScore
=== RUN Test_incUpdateScore/should_increment_score_by_+_number
=== RUN Test_incUpdateScore/should_increment_score_by_-_number
--- PASS: Test_incUpdateScore (0.00s)
--- PASS: Test_incUpdateScore/should_increment_score_by_+_number (0.00s)
--- PASS: Test_incUpdateScore/should_increment_score_by_-_number (0.00s)
PASS
ok 07-code-testing/varpointer0.559s
但是这两个测试场景有一些重复性的代码,有点让人不爽对不对,函数就是消除重复代码的好办法,我们再次重构一下。
func Test_incUpdateScore(t *testing.T) {
checkEqual := func(t *testing.T, expected, got int) {
t.Helper() // 出错的时候报告准确位置
if expected != got {
t.Errorf("有问题, 期望 %d, 实际是 %d", expected, got)
}
}
t.Run("should increment score by + number", func(t *testing.T) {
score := 10
incUpdateScore(&score, 1)
checkEqual(t, 11, score)
})
t.Run("should increment score by - number", func(t *testing.T) {
score := 10
incUpdateScore(&score, -1)
checkEqual(t, 9, score)
})
}
刚才创建的测试是单元测试 (*testing.T),go test 还支持基准测试 (*testing.B),自定义入口测试 (*testing.M),以后的章节会经常用到测试,需要掌握它。
本章节的代码