【Go语言学习】——测试

单元测试


参考博客

Go语言中的测试依赖go test命令,并在后面加上-v后可以显示详细信息。在包目录内,所有以_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中。在*_test.go文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。

类型 格式 作用
测试函数 函数名前缀为Test 测试程序的一些逻辑行为是否正确
基准函数 函数名前缀为Benchmark 测试函数的性能
示例函数 函数名前缀为Example 为文档提供示例文档
  • 单元测试

    通过设置测试函数对函数功能进行测试,将输出和预期结果进行对比,如果不正确则设置对应的错误级别和错误信息。

    package split_string
    
    import "strings"
    
    // 待测函数,切割字符串
    
    func Split(str string, sep string) []string {
    	var ret []string
    	index := strings.Index(str, sep)
    	for index >= 0 {
    		if index != 0 {
    			ret = append(ret, str[:index])
    		}
    		str = str[index+len(sep):]
    		index = strings.Index(str, sep)
    	}
    	ret = append(ret, str)
    	return ret
    }
    
    
    package split_string
    
    // 单元测试函数
    
    import (
    	"reflect"
    	"testing"
    )
    
    func TestSplit(t *testing.T) {
    	// 程序输出的结果
    	got := Split("babcbef", "b")
    	// 程序期望的结果
    	want := []string{"a", "c", "ef"}
    	// 通过反射来帮助对比切片的结果
    	if !reflect.DeepEqual(got, want) {
    		// 测试失败则给出错误提示
    		t.Errorf("want:%v but got: %v", want, got)
    	}
    }
    
    func Test2Split(t *testing.T) {
    	// 程序输出的结果
    	got := Split("a:b:c", ":")
    	// 程序期望的结果
    	want := []string{"a", "b", "c"}
    	// 通过反射来帮助对比切片的结果
    	if !reflect.DeepEqual(got, want) {
    		// 测试失败则给出错误提示
    		t.Errorf("want:%v but got: %v", want, got)
    	}
    }
    
    func Test3Split(t *testing.T) {
    	// 程序输出的结果
    	got := Split("abcef", "bc")
    	// 程序期望的结果
    	want := []string{"a", "ef"}
    	// 通过反射来帮助对比切片的结果
    	if !reflect.DeepEqual(got, want) {
    		// 测试失败则给出错误提示
    		t.Errorf("want:%v but got: %v", want, got)
    	}
    }
    
  • 测试组

    通过建立测试用例切片,然后循环执行所有的测试用例,减少代码的重复量。

    package split_string
    
    // 单元测试组函数
    
    import (
    	"reflect"
    	"testing"
    )
    
    func TestSplitGroup(t *testing.T) {
    	type testCase struct {
    		str  string
    		sep  string
    		want []string
    	}
    	testGroup := []testCase{
    		{"babcbef", "b", []string{"a", "c", "ef"}},
    		{"a:b:c", ":", []string{"a", "b", "c"}},
    		{"abcef", "bc", []string{"a", "ef"}},
    		{"沙河有沙又有河", "沙", []string{"河有", "又有河"}},
    	}
    
    	for _, tc := range testGroup {
    		got := Split(tc.str, tc.sep)
    		if !reflect.DeepEqual(got, tc.want) {
    			t.Fatalf("want:%v got:%#v", tc.want, got)
    		}
    	}
    }
    
    
  • 子测试

    利用子测试可以查看所有测试用例对应的测试信息,并且可以通过在后面加上-run=测试用例名(与输出中测试用例名一致)只测试其中一个测试用例

    package split_string
    
    import (
    	"reflect"
    	"testing"
    )
    
    func TestSplitSub(t *testing.T) {
    	type testCase struct {
    		str  string
    		sep  string
    		want []string
    	}
    	testGroup := map[string]testCase{
    		"case_1": {"babcbef", "b", []string{"a", "c", "ef"}},
    		"case_2": {"a:b:c", ":", []string{"a", "b", "c"}},
    		"case_3": {"abcef", "bc", []string{"a", "ef"}},
    		"case_4": {"沙河有沙又有河", "沙", []string{"河有", "又有河"}},
    	}
    
    	for name, tc := range testGroup {
    		// 利用t.run执行子测试,可以看到每个测试用例对应的测试结果
    		t.Run(name, func(t *testing.T) {
    			got := Split(tc.str, tc.sep)
    			if !reflect.DeepEqual(got, tc.want) {
    				t.Fatalf("want:%v got:%#v", tc.want, got)
    			}
    		})
    	}
    }
    
  • 测试覆盖率

    通过go test -cover可以查看代码测试的覆盖的百分比,Go还提供了一个额外的-coverprofile参数,用来将覆盖率相关的记录信息输出到一个文件,例如:

    split $ go test -cover -coverprofile=c.out
    PASS
    coverage: 100.0% of statements
    ok      github.com/Q1mi/studygo/code_demo/test_demo/split       0.005s
    

    上面的命令会将覆盖率相关的信息输出到当前文件夹下面的c.out文件中,然后我们执行go tool cover -html=c.out,使用cover工具来处理生成的记录信息,该命令会打开本地的浏览器窗口生成一个HTML报告。通常测试函数覆盖率要求100%,测试覆盖率60%

  • 基准测试

    基准测试就是在一定的工作负载之下检测程序性能的一种方法,它的定义是Benchmark为前缀,需要一个*testing.B类型的参数b,基准测试必须要执行b.N次。基准测试并不会默认执行,需要增加-bench参数。针对不同的优化方法比较输出结果来改进。

    package split_string
    
    import "testing"
    
    // 基准测试
    
    // 执行N遍测试性能
    func BenchmarkSplit(b *testing.B) {
    	// 这里的N是个未知数,尽量去跑到最大值
    	for i := 0; i < b.N; i++ {
    		Split("a:b:c", ":")
    	}
    }
    

    运行go test -bench=Split命令执行基准测试,输出结果如下。其中数字12表示GOMAXPROCS(CPU内核数)的值,这个对于并发基准测试很重要。14801511 78.45 ns/op表示每次调用Split函数耗时78.45ns

    PS E:\GoWorkPlace\src\github.com\studygo\split_string> go test -bench=Split
    goos: windows
    goarch: amd64
    pkg: github.com/studygo/split_string
    cpu: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz
    BenchmarkSplit-12       14801511                78.45 ns/op
    PASS
    ok      github.com/studygo/split_string 1.412s
    

    通过基准测试添加-benchmem参数,来获得内存分配的统计数据。其中48 B/op表示每次操作内存分配了48字节,1 allocs/op则表示每次操作进行了1次内存分配

    PS E:\GoWorkPlace\src\github.com\studygo\split_string> go test -bench=Split -benchmem
    goos: windows
    goarch: amd64
    pkg: github.com/studygo/split_string
    cpu: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz
    BenchmarkSplit-12       15303565                75.82 ns/op           48 B/op          1 allocs/op
    PASS
    ok      github.com/studygo/split_string 1.407s
    
  • 性能比较函数

    性能比较函数通常是一个带有参数的函数,被多个不同的Benchmark函数传入不同的值来调用。

    // 测试斐波那契数列性能
    func benchmarkFib(b *testing.B, n int) {
    	for i := 0; i < b.N; i++ {
    		Fib(n)
    	}
    }
    func BenchmarkFib1(b *testing.B)  { benchmarkFib(b, 1) }
    func BenchmarkFib2(b *testing.B)  { benchmarkFib(b, 2) }
    func BenchmarkFib3(b *testing.B)  { benchmarkFib(b, 3) }
    func BenchmarkFib10(b *testing.B) { benchmarkFib(b, 10) }
    func BenchmarkFib20(b *testing.B) { benchmarkFib(b, 20) }
    func BenchmarkFib40(b *testing.B) { benchmarkFib(b, 40) }
    

    运行测试时可以在-bench=后写上公共函数名称前缀就可以调用所有对比的函数

    PS E:\GoWorkPlace\src\github.com\studygo\split_string> go test -bench=Fib
    goarch: amd64
    pkg: github.com/studygo/split_string
    cpu: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz
    BenchmarkFib1-12        844466212                1.386 ns/op
    BenchmarkFib2-12        260821142                4.507 ns/op
    BenchmarkFib3-12        168101977                7.047 ns/op
    BenchmarkFib10-12        4449705               266.5 ns/op
    BenchmarkFib20-12          35829             33102 ns/op
    BenchmarkFib40-12              2         503109500 ns/op
    PASS
    ok      github.com/studygo/split_string 9.411s
    

    默认情况下,每个基准测试至少运行1秒。如果在Benchmark函数返回时没有到1秒,则b.N的值会按1,2,5,10,20,50,…增加,并且函数再次运行。因此针对一次运行时间较长的函数可以-benchtime标志增加最小基准时间,以产生更准确的结果。

  • 时间重置

    b.ResetTimer之前的处理不会放到执行时间里,也不会输出到报告中,所以可以在之前做一些不计划作为测试报告的操作.

    func BenchmarkSplit(b *testing.B) {
    	time.Sleep(5 * time.Second) // 假设需要做一些耗时的无关操作
    	b.ResetTimer()              // 重置计时器
    	for i := 0; i < b.N; i++ {
    		Split("沙河有沙又有河", "沙")
    	}
    }
    
  • 并行测试

    func (b *B) RunParallel(body func(*PB))会以并行的方式执行给定的基准测试。

    RunParallel会创建出多个goroutine,并将b.N分配给这些goroutine执行, 其中goroutine数量的默认值为GOMAXPROCS。用户如果想要增加非CPU受限(non-CPU-bound)基准测试的并行性, 那么可以在RunParallel之前调用SetParallelismRunParallel通常会与-cpu标志一同使用。

    func BenchmarkSplitParallel(b *testing.B) {
    	// b.SetParallelism(1) // 设置使用的CPU数
    	b.RunParallel(func(pb *testing.PB) {
    		for pb.Next() {
    			Split("沙河有沙又有河", "沙")
    		}
    	})
    }
    

    还可以通过在测试命令后添加-cpu参数如go test -bench=. -cpu 1来指定使用的CPU数量。

你可能感兴趣的:(学习,单元测试,压力测试)