Golang - 测试


下面展示的所有示例中代码都在这里


测试文件的命名

  • 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)
    			}
    		})
    	}
    }
    
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值