Go基础学习-单元、基准、性能、覆盖率、示例测试

代码测试

  • 以_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中
  • *_test.go文件中有三种类型的函数,
    单元测试函数:前缀为Test(测试程序的一些逻辑行为是否正确)
    基准测试函数:前缀为Benchmark( 测试函数的性能)
    示例函数:前缀为Example( 为文档提供示例文档)
//测试用例函数
//Splittostring  测试testing 包
func Splittostring(s, sep string) (res []string) {
	res = make([]string, 0, strings.Count(s, sep)+1)
	i := strings.Index(s, sep)
	for i >= 0 {
		res = append(res, s[:i])
		s = s[i+len(sep):]
		i = strings.Index(s, sep)
	}
	res = append(res, s)
	return
}

单元测试函数

func TestSplittostring1(t *testing.T) { //测试函数名必须以Test开头,必须接收一个*testing.T类型参数
	got := Splittostring("a:b:c", ":") // 程序输出的结果
	want := []string{"a", "b", "c"}    // 期望的结果
	if !reflect.DeepEqual(want, got) { // 因为slice不能比较直接,借助反射包中的方法比较
		t.Errorf("excepted:%v, got:%v", want, got) // 测试失败输出错误提示
	}
}
//测试组(无法对单个case进行测试)
func TestSplittostring2(t *testing.T) {
	type testCase struct {
		str  string
		sep  string
		want []string
	}
	test := []testCase{ //切片存储
		{str: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
		{str: "adfgadsf", sep: "d", want: []string{"a", "fga", "sf"}},
		{str: "avddfddfddf", sep: "fd", want: []string{"avdd", "d", "df"}},
	}
	for _, v := range test {
		got := Splittostring(v.str, v.sep)
		if !reflect.DeepEqual(v.want, got) {
			t.Errorf("want:%#v,got:%#v\n", v.want, got)
		}
	}
}


//子测试(即可以组测试,也可以进行单个case的测试)

func TestSplittostring(t *testing.T) {
	type testCase struct {
		str  string
		sep  string
		want []string
	}
	testData := map[string]testCase{  //map存储
		"case 1": {str: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
		"case 2": {str: "adfgadsf", sep: "d", want: []string{"a", "fga", "sf"}},
		"case 3": {str: "avddfddfddf", sep: "fd", want: []string{"avdd", "d", "df"}},
	}
	for k, v := range testData {
		t.Run(k, func(t *testing.T) { // 使用t.Run()执行子测试
			got := Splittostring(v.str, v.sep)
			if !reflect.DeepEqual(got, v.want) {
				t.Errorf("name:%s,want:%#v,got:%#v\n", k, v.want, got)
			}
		})
	}
}

基准测试函数

  • 基准测试就是在一定的工作负载之下检测程序性能的一种方法
  • 前缀为Benchmark
  • 基准测试必须要执行b.N次,这样的测试才有对照性,b.N的值是系统根据实际情况去调整的,从而保证测试的稳定性
  • b.ResetTimer() 可以使用该函数重置时间,主要就是对函数中无关耗时的操作时间进行过滤
func BenchmarkSplittostring(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Splittostring("adfadfsfsaf", "f")
	}
}

//以斐波那契数的函数w为例
// Fib 是一个计算第n个斐波那契数的函数
func Fib(n int) int {
	if n < 2 {
		return n
	}
	return Fib(n-1) + Fib(n-2)
}

//性能比较测试
//比较同一个函数处理1000个元素的耗时与处理1万甚至100万个元素的耗时的差别;
//性能比较函数通常是一个带有参数的函数,被多个不同的Benchmark函数传入不同的值来调用
//每个基准测试至少运行1秒。如果在Benchmark函数返回时没有到1秒,则b.N的值会按1,2,5,10,20,50,…增加,并且函数再次运行。
//可以使用-benchtime标志增加最小基准时间,以产生更准确的结果 go test -bench=Fib40 -benchtime=20s

func benchmarkFib(b *testing.B, n int) {
	for i := 0; i < b.N; i++ {
		Fib(n)
	}
}
//使用go test -bench=Fib1 //或者go test -bench=.  来只当执行的测试函数
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) }

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

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

示例测试函数

  • 没有参数也没有返回值。
  • 必须包含// Output:才可以被go test识别
//示例函数
func ExampleSplittostring() {
	fmt.Println(Splittostring( "adfgadsf","d"))
	fmt.Println(Splittostring("avddfddfddf", "fd"))
	// Output:     //必须包含了// Output:也是可以通过go test -run Example运行的可执行测试。
	// [a fga sf]
	// [avdd d df]
}

setup、teardown

  • 在*_test.go文件中定义func TestMain(m *testing.M)函数来可以在测试之前进行额外的设置(setup)或在测试之后进行拆卸(teardown)操作。

TestMain运行在主goroutine中, 可以在调用 m.Run前后做任何设置(setup)和拆卸(teardown)。退出测试的时候应该使用m.Run的返回值作为参数调用os.Exit。

func TestMain(m *testing.M) {
	fmt.Println("write setup code here...") // 测试之前的做一些设置
	// 如果 TestMain 使用了 flags,这里应该加上flag.Parse()
	retCode := m.Run()                         // 执行测试
	fmt.Println("write teardown code here...") // 测试之后做一些拆卸工作
	os.Exit(retCode)                           // 退出测试
}

// 测试集的Setup与Teardown
func setupTestCase(t *testing.T) func(t *testing.T) {
	t.Log("如有需要在此执行:测试之前的setup")
	return func(t *testing.T) {
		t.Log("如有需要在此执行:测试之后的teardown")
	}
}

// 子测试的Setup与Teardown
func setupSubTest(t *testing.T) func(t *testing.T) {
	t.Log("如有需要在此执行:子测试之前的setup")
	return func(t *testing.T) {
		t.Log("如有需要在此执行:子测试之后的teardown")
	}
}

func TestSplittostring(t *testing.T) {
	type test struct { // 定义test结构体
		input string
		sep   string
		want  []string
	}
	testData := map[string]testCase{
		"case 1": {str: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
		"case 2": {str: "adfgadsf", sep: "d", want: []string{"a", "fga", "sf"}},
		"case 3": {str: "avddfddfddf", sep: "fd", want: []string{"avdd", "d", "df"}},
	}
	teardownTestCase := setupTestCase(t) // 测试之前执行setup操作
	defer teardownTestCase(t)            // 测试之后执行testdoen操作

	for name, tc := range testData {
		t.Run(name, func(t *testing.T) { // 使用t.Run()执行子测试
			teardownSubTest := setupSubTest(t) // 子测试之前执行setup操作
			defer teardownSubTest(t)           // 测试之后执行testdoen操作
			got := Split(tc.input, tc.sep)
			if !reflect.DeepEqual(got, tc.want) {
				t.Errorf("excepted:%#v, got:%#v", tc.want, got)
			}
		})
	}
}

测试命令

go test //会测试*_test.go文件中的三种类型函数
go test -v //查看测试函数名称和运行时间
go test -v -run="关键字" //正则匹配函数名中的关键字进行指定测试
go test -v -run=Split/simple //通过/来指定要运行的子测试用例;只会运行simple对应的子测试用例。
go test -cover //查看测试覆盖率;测试覆盖率是你的代码被测试套件覆盖的百分比。通常我们使用的都是语句的覆盖率,也就是在测试中至少被运行一次的代码占总代码的比例。
go test -cover -coverprofile=xx.out//将覆盖率相关的记录信息输出到一个文件 //
go tool cover -html=xx.out //根据上面生成的文件通过cover组件生成一个html的页面报告
go test -bench=funcName//查看基准测试结果;
//1.BenchmarkSplit-8表示对Split函数进行基准测试,数字8表示GOMAXPROCS的值
//2.10552743 和203ns/op表示每次调用Split函数耗时203ns(平均);共测试了10552743 次
D:\go_project\src>go test -bench=Splittostring
goos: windows
goarch: amd64
pkg: github.com/wzbwzt/studyGo/testing
BenchmarkSplittostring-8        10552743               114 ns/op  
PASS
ok      github.com/wzbwzt/studyGo/testing       1.488s

go test -bench=funcName -benchmem //-benchmem参数,来获得内存分配的统计数据。
//1.80 B/op:表示每次操作内存分配了80字节
//2.1 allocs/op:表示每次操作进行了1次内存分配
D:\go_project\src>go test -bench=Splittostring -benchmem
goos: windows
goarch: amd64
pkg: github.com/wzbwzt/studyGo/testing
BenchmarkSplittostring-8        10372520               113 ns/op              80 B/op          1 allocs/op
PASS
ok      github.com/wzbwzt/studyGo/testing       1.471s

猜你喜欢

转载自blog.csdn.net/wzb_wzt/article/details/107392257