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 说明单元测试通过

https://image.iswbm.com/image-20220326130634024.png

要是加一个 -v 就可以查看显示每个测试用例的测试结果

https://image.iswbm.com/image-20220326130601941.png

3. 子测试用例

如果有很多测试用例,可以用 -run 指定某个某个测试用例

https://image.iswbm.com/image-20220326131019313.png

若一个测试用例还可以分为多个子测试用例,比如下边的测试用例分为 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 主用例/子用例 就可以执行对应的子用例

https://image.iswbm.com/image-20220326133200586.png