8.1 测试技巧:单元测试(Unit Test) =================================== 单元测试(Unit Tests, UT) 是一个优秀项目不可或缺的一部分,特别是在一些频繁变动和多人合作开发的项目中尤为重要。 写单元测试代码是一件短期没什么用,但却能长期收益的事情,特别是在人比较多的大团队里。 很多初级开发者不愿意花时间写测试代码,因为写测试代码比功能代码少了一些创造性,没有个人成就感,况且迭代快、排期紧导致没有时间去安排写单元测试。 在以下这些场景中,没有养成写单元测试习惯的话,就是一个灾难 - 同事修改了某个之前由你编写的函数,但由于同事对这块函数理解上的不足,影响了某个异常场景的处理,你的同事没有测试到,把 bug 流到线上去 - 某个函数的逻辑比较复杂,该函数的改动也很频繁,每一次的改过都要测试非常多的场景,费时费力 1. 如何写单元测试 ----------------- 在开始之前,先初始化项目 .. code:: bash go mod init github.com/iswbm/fuzz 然后在该项目中添加 main.go,内容如下 .. code:: 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 做对比 - 若不相等,则测试不通过~ .. code:: go 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 说明单元测试通过 .. image:: https://image.iswbm.com/image-20220326130634024.png 要是加一个 ``-v`` 就可以查看显示每个测试用例的测试结果 .. image:: https://image.iswbm.com/image-20220326130601941.png 3. 子测试用例 ------------- 如果有很多测试用例,可以用 -run 指定某个某个测试用例 .. image:: https://image.iswbm.com/image-20220326131019313.png 若一个测试用例还可以分为多个子测试用例,比如下边的测试用例分为 foo 和 bar 两个子测试用例 .. code:: go 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 主用例/子用例`` 就可以执行对应的子用例 .. image:: https://image.iswbm.com/image-20220326133200586.png