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万多次),每次操作耗时(单位纳秒),每次操作分配内存大小,每次操作内存分配次数。