Golang - 测试

    技术2023-07-01  108

    文章目录

    测试文件的命名单元测试子测试集子测试组 性能测试简单测试并发性能性能对比设置 cpu 数量, 清除耗时 测试前后加操作全局前后增加操作 - TestMain单一用例前后增加操作 - setup 和 teardown 运行测试单元测试基准测试 练习 - 回文判断


    下面展示的所有示例中代码都在这里


    测试文件的命名

    xxx_test.go比如测 split.go 文件, 命名就为 split_test.go

    单元测试

    子测试集

    package split // 导入本包 import ( "reflect" "testing" ) // 命名最好以 Test 开头, 参数必须是这个 func TestSplit(t *testing.T) { get := Split("a:b:c", ":") // 我们做测试的请求 want := []string{"a", "b", "c"} // 期望得到的结果 if ok := reflect.DeepEqual(get, want); !ok { // 这是一个深度对比, 可以对比引用类型 t.Fatalf("期望结果: %v, 实际结果: %v\n", want, get) } } func TestNoneSplit(t *testing.T) { get := Split("a:b:c", "|") want := []string{"a:b:c"} if ok := reflect.DeepEqual(get, want); !ok { t.Fatalf("期望结果: %v, 实际结果: %v\n", want, get) } } // 分隔符是多个字符 func TestMulitiSplit(t *testing.T) { get := Split("abcabcabca", "bc") want := []string{"a", "a", "a", "a"} if ok := reflect.DeepEqual(get, want); !ok { t.Fatalf("期望结果: %v, 实际结果: %v\n", want, get) } }

    子测试组

    package split import ( "reflect" "testing" ) // 测试组 func TestGroupSplit(t *testing.T) { // 存放测试数据的结构体 type test struct { str string sep string want []string } // 实例化各个测试用例 tests := map[string]test{ "normal": test{ str: "a:b:c", // 测试的字符串 sep: ":", // 测试的分隔符 want: []string{"a", "b", "c"}, // 想要得到的结果 }, "none": test{ str: "a:b:c", sep: "-", want: []string{"a:b:c"}, }, "multi": test{ str: "a::b::c", sep: "::", want: []string{"a", "b", "c"}, }, "multi2": test{ str: "1231", sep: "1", want: []string{"", "23", ""}, }, } // 测试 for k, v := range tests { t.Run(k, func(t *testing.T) { r := Split(v.str, v.sep) if !reflect.DeepEqual(r, v.want) { t.Errorf("excepted: %#v, get: %#v", v.want, r) } }) // v1.7 之前的写法 // r := Split(v.str, v.sep) // if ok := reflect.DeepEqual(v.want, r); !ok { // t.Fatalf("%v: 期望得到: %#v, 实际得到: %v", k, v.want, r) // } } }

    性能测试

    简单测试

    package split // 命名由 Benchmark 开头, 参数固定 func BenchmarkSplit(b *testing.B) { for i := 0; i < b.N; i++ { Split("a:b:c", ":") } }

    并发性能

    package split func BenchmarkSplitParallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { Split("1231", "1") } }) }

    性能对比

    package fib import "testing" // 当传入的数据量不同, 测试函数运算的耗时 // 这个 benchmark 是小写, 用于调用 func benchmarkFib(b *testing.B, n int) { for i := 0; i < b.N; i++ { Fib(n) } } func BenchmarkFib2(b *testing.B) { benchmarkFib(b, 2) } func BenchmarkFib20(b *testing.B) { benchmarkFib(b, 20) } func BenchmarkFib100(b *testing.B) { benchmarkFib(b, 200) }

    设置 cpu 数量, 清除耗时

    // 设置 cpu 数量 b.SetParallelism(1) // 清除上边代码的耗时 b.ResetTimer()

    测试前后加操作

    全局前后增加操作 - TestMain

    func TestMain(m *testing.M) { fmt.Println("start test...") r := m.Run() fmt.Println("end test...") os.Exit(r) }

    单一用例前后增加操作 - setup 和 teardown

    package split func TestMultiSplit(t *testing.T) { f := setupT(t) defer f(t) get := Split("a::b::c", "::") want := []string{"a", "b", "c"} if ok := reflect.DeepEqual(get, want); !ok { t.Fatalf("期望结果: %v, 实际结果: %v\n", want, get) } else { t.Log("pass test...") } } // 给单一测试用例加前后操作, 如果是性能测试, t 改 b 即可 func setupT(t *testing.T) func(t *testing.T) { t.Log("start test...") // 测试前执行 return func(t *testing.T) { // 返回函数名, 使用时 defer 调用 t.Log("end test...") } }

    运行测试

    单元测试

    go test -v # 输出详细信息 # 函数形式 子测试 go test -run=None -v # 函数名字包含 None 的测试用例 # 组形式 子测试 go test -run Split/None -v # 函数名/用例名, 如果有多个组, 需要声明函数名, 否则可以省略 # 测试覆盖率 go test -cover # 将覆盖率输出到文件 go test -cover -coverprofile=c.out # 将文件内容输出到浏览器 go tool cover -html=c.out # 如果遇到如下报错说明目录下没有 go 的二进制文件, 添加即可 cover: cannot run go list: fork/exec /usr/local/go/bin/go: no such file or directory

    基准测试

    # -bench 指定测试的函数, . 表示所有 go test -bench=. # 可以指定使用几个cpu, 输出内存信息等 go test -bench=Split -cpu=2 -benchmem """ 结果展示: goos: darwin goarch: amd64 pkg: code.oldboy.com/split # 使用了 4 个 cpu 测试次数 每次操作消耗时间 每次操作占用字节数 每次操作申请内存次数 BenchmarkSplit-4 3000000 420 ns/op 112 B/op 3 allocs/op PASS ok code.oldboy.com/split 1.710s """

    练习 - 回文判断

    palindrome.gopackage palindrome import ( "fmt" "unicode" ) // 需求: 判断是否为回文字符串 // 1. 需要去除标点 unicode.IsLetter 判断 // 2. 需要忽略大小写 unicode.ToLower 转换 func isPalindrome(s string) bool { // 用于存放"词语" var letters []rune // 遍历字符串 for _, l := range s { // 判断是否为"词语", 是的话处理大小写, 添加进letters if unicode.IsLetter(l) { // 处理大小写 newL := unicode.ToLower(l) // 加入切片 letters = append(letters, newL) } } // 遍历判断 l := len(letters) for i := 0; i < l/2; i++ { if letters[i] != letters[l-i-1] { return false } } return true } palindrome_test.gopackage palindrome import ( "reflect" "testing" ) func TestIsPalindrome(t *testing.T) { // 测试用例结构 type test struct { str string want bool } // 测试组 tests := map[string]test{ "normal": test{ str: "abcba", want: true, }, "withXx": test{ str: "a:ba", want: true, }, // 经测试发现字节索引方式判断会导致断码, 已经优化 "chinese": test{ str: "油灯少灯油", want: true, }, } // 执行用例 for name, v := range tests { t.Run(name, func(t *testing.T) { r := isPalindrome(v.str) if !reflect.DeepEqual(r, v.want) { t.Errorf("excepted: %#v, get: %#v", v.want, r) } }) } }
    Processed: 0.017, SQL: 10