文章目录

  • 测试文件的命名
  • 单元测试
  • 子测试集
  • 子测试组
  • 性能测试
  • 简单测试
  • 并发性能
  • 性能对比
  • 设置 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.go
package 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.go
package 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)
			}
		})
	}
}


阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: go