8.1 测试技巧:单元测试(Unit Test)¶
单元测试(Unit Tests, UT) 是一个优秀项目不可或缺的一部分,特别是在一些频繁变动和多人合作开发的项目中尤为重要。
写单元测试代码是一件短期没什么用,但却能长期收益的事情,特别是在人比较多的大团队里。
很多初级开发者不愿意花时间写测试代码,因为写测试代码比功能代码少了一些创造性,没有个人成就感,况且迭代快、排期紧导致没有时间去安排写单元测试。
在以下这些场景中,没有养成写单元测试习惯的话,就是一个灾难
同事修改了某个之前由你编写的函数,但由于同事对这块函数理解上的不足,影响了某个异常场景的处理,你的同事没有测试到,把 bug 流到线上去
某个函数的逻辑比较复杂,该函数的改动也很频繁,每一次的改过都要测试非常多的场景,费时费力
1. 如何写单元测试¶
在开始之前,先初始化项目
go mod init github.com/iswbm/fuzz
然后在该项目中添加 main.go,内容如下
package main
import "fmt"
func Reverse(s string) string {
b := [] byte(s)
for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
return string(b)
}
func main() {
input := "The quick brown fox jumped over the lazy dog"
rev := Reverse(input)
doubleRev := Reverse(rev)
fmt.Printf("original: %q\n", input)
fmt.Printf("reversed: %q\n", rev)
fmt.Printf("reversed again: %q\n", doubleRev)
}
现在我们要为 Reverse 函数编写单元测试代码,放在 reverse_test.go,Test 函数如下
给定了三组数据
遍历这几组数据,将 tc.in 做为 Reverses 函数的入参执行函数,其返回值跟预期的 tc.want 做对比
若不相等,则测试不通过~
package main
import (
"testing"
)
func TestReverse(t *testing.T) {
testcases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{" ", " "},
{"!12345", "54321!"},
}
for _, tc := range testcases {
rev := Reverse(tc.in)
if rev != tc.want {
t.Errorf("Reverse: %q, want %q", rev, tc.want)
}
}
}
对于单元测试函数来说,它的编写有一些格式,需要提一下,不然上面的函数,你可能会有疑问:
单元测试,要导入 testing 包
承载测试用例的测试文件,固定以 xxx_test.go(xxx 是原文件名)
测试用例函数名称一般命名为
Test
加上待测试的方法名。测试用例函数的参数有且只有一个,在这里是
t *testing.T
2. 执行测试用例¶
现在我们执行 go test 即是普通的单元测试,即执行该 package 下的所有函数的测试用例,输出 PASS 说明单元测试通过
要是加一个 -v
就可以查看显示每个测试用例的测试结果
3. 子测试用例¶
如果有很多测试用例,可以用 -run 指定某个某个测试用例
若一个测试用例还可以分为多个子测试用例,比如下边的测试用例分为 foo 和 bar 两个子测试用例
package main
import (
"testing"
)
func TestReverse(t *testing.T) {
t.Run("foo", func(t *testing.T) {
testcases := []struct {
in, want string
}{
{"Hello, foo", "oof ,olleH"},
}
for _, tc := range testcases {
rev := Reverse(tc.in)
if rev != tc.want {
t.Errorf("[foo test]Reverse: %q, want %q", rev, tc.want)
}
}
})
t.Run("bar", func(t *testing.T) {
testcases := []struct {
in, want string
}{
{"Hello, bar", "rab ,olleH"},
}
for _, tc := range testcases {
rev := Reverse(tc.in)
if rev != tc.want {
t.Errorf("[bar test] Reverse: %q, want %q", rev, tc.want)
}
}
})
}
使用 -run 主用例/子用例
就可以执行对应的子用例