Golang中的单元测试和基准测试实践

golang本身提供了简单但功能强大的单元测试和基准测试。本文用一个查找重复次数最多的字符的函数为测试对象,对单元测试和基准测试做一个简单的实践。

查找重复次数最多的字符

type charCounter struct {
	char    rune
	counter int
}

func FindMostRepeatedChar(strSlice []rune) (returnSlice []*charCounter, err error) {

	// 传入切片为nil时,返回异常
	if strSlice == nil {
		return nil, errors.New("parameter is nil")
	}

	// 传入切片长度为0时,返回异常
	if len(strSlice) == 0 {
		return nil, errors.New("parameter is a blank string")
	}

	// 创建一个统计用的map
	charMap := make(map[rune]int)

	// 遍历切片,统计每个字符的出现次数并将统计结果写入统计用的map
	for _, b := range strSlice {

		if value, exist := charMap[b]; exist {
			charMap[b] = value + 1
		} else {
			charMap[b] = 1
		}
	}

	// 创建一个用于排序的切片
	charSlice := make([]*charCounter, len(charMap))
	idx := 0

	// 将统计数据从map写入切片
	for key, value := range charMap {

		cc := &charCounter{
			char:    key,
			counter: value,
		}

		charSlice[idx] = cc
		idx++
	}

	// 对统计结果按降序排序
	sort.Slice(charSlice, func(i, j int) bool { return charSlice[i].counter > charSlice[j].counter })

	var value int
	idx = 0

	// 遍历切片,将前一个统计结果保存到value变量中。如果当前的统计值小于前一个统计值,则返回当前值的索引值。
	for i, cc := range charSlice {

		if i == 0 {
			value = cc.counter
		} else {

			if value > cc.counter {
				idx = i
				break
			}

			if value == cc.counter {
				value = cc.counter
			}
		}
	}

	// 创建一个返回用的切片
	returnSlice = make([]*charCounter, idx)

	// 拷贝切片
	copy(returnSlice, charSlice[:idx])

	return returnSlice, nil
}

 golang的单元测试和基准测试的原则其实非常简单,具体如下。

1,单元测试

    1.1 单元测试的代码与被测试函数的代码放在同一个包下面;

    1.2 单元测试的文件名的形式为Xxx_test.go;为了区分单元测试和基准测试的文件,个人比较偏向Xxx_unit_test.go和Xxx_benchmark_test.go的形式。

    1.3 单元测试的方法的形式为func TestXxx(t *testing.T)。Xxx是被测试函数的名称。但在实际编写单元测试代码时,方法名称是什么其实不重要,关键是测试方法的传入参数必须是t *testing.T。

2,基准测试

    2.1 测试文件名和测试方法名的规则与基准测试一致,参照1.1和1.2。

    2.2 单元测试的方法的形式为func TestXxx(t *testing.B)。测试方法的传入参数必须是t *testing.B。

规则简单讲完后就直接上测试代码。

单元测试代码


func TestFind1(t *testing.T) {

	str := "this is a test啊啊啊啊あああああいいい"
	temp, err := FindMostRepeatedChar([]rune(str))

	if err != nil {
		t.Errorf("error: %v", err.Error())
	} else {
		for i, cc := range temp {
			fmt.Printf("index: %d, char: %c, counter: %d\n", i, cc.char, cc.counter)
		}
	}
}

func TestFind2(t *testing.T) {

	temp, err := FindMostRepeatedChar(nil)

	if err != nil {
		t.Errorf("error: %v", err.Error())
	} else {
		for i, cc := range temp {
			fmt.Printf("index: %d, char: %c, counter: %d\n", i, cc.char, cc.counter)
		}
	}
}

func TestFind3(t *testing.T) {

	str := ""
	temp, err := FindMostRepeatedChar([]rune(str))

	if err != nil {
		t.Errorf("error: %v", err.Error())
	} else {
		for i, cc := range temp {
			fmt.Printf("index: %d, char: %c, counter: %d\n", i, cc.char, cc.counter)
		}
	}
}

func TestFind4(t *testing.T) {

	returnSlice := make([]*charCounter, 10)
	returnSlice[0] = &charCounter{char: 'あ', counter: 2}

	tableDriveData := []struct {
		in  []rune
		out []*charCounter
	}{
		{in: nil, out: nil},
		{in: []rune(""), out: nil},
		{in: []rune("this is a test啊啊啊啊あああああいいい"), out: returnSlice},
	}

	for _, td := range tableDriveData {

		temp, err := FindMostRepeatedChar(td.in)

		if temp == nil {
			t.Log(err.Error())
		} else {

			if !(temp[0].char == td.out[0].char && temp[0].counter == td.out[0].counter) {
				t.Fatal("not expected")
			}
		}
	}
}

上面的前三个测试方法,是对被测试函数传入参数的三种情况进行分别测试。第四个测试方法是采用的所谓表驱动测试的方法,一次性对传入参数的三种情况进行测试。因为我用的是vscode,的是代码实际显示像下面的样子,直接点左上角的“run test”就可以执行当前的这个测试方法。如果你想一次性执行所有测试方法,可以通过go test -v命令执行。

 基准测试代码

func BenchmarkFind1(t *testing.B) {

	t.ResetTimer()
	t.ReportAllocs()

	for i := 0; i < t.N; i++ {
		str := "this is a test啊啊啊啊あああああいいい"
		_, err := FindMostRepeatedChar([]rune(str))
		if err != nil {
			t.Errorf("error: %v", err.Error())
		}
	}
}

基准测试执行结果

Running tool: C:\Go\bin\go.exe test -benchmem -run=^$ -bench ^BenchmarkFind1$ test2/findmaxchar

goos: windows
goarch: amd64
pkg: test2/findmaxchar
BenchmarkFind1-12    	  598225	      1793 ns/op	     602 B/op	      17 allocs/op
PASS
ok  	test2/findmaxchar	1.432s

从执行结果可以获取执行次数(59万多次),每次操作耗时(单位纳秒),每次操作分配内存大小,每次操作内存分配次数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值