go语言学习(第十一章,测试)(go 语言学习笔记)

11.1 单元测试

单元测试(unit test)除用来测试逻辑算法是否符合预期外,还承担着监控代码质量的责任。任何时候都可用简单的命令来验证全部功能,找出未完成任务(验收)和任何因修改而造成的错误。它与性能测试、代码覆盖率等一起保障了代码总是在可控范围内,这远比形式化的人工检查有用的多。
单元测试并非要取代人工代码审核,实际上它也无法切入到代码实现层面。但可通过测试结果为审查提供筛选依据,避免因烦琐导致代码审查沦为形式主义。单元测试可自动化进行,能持之以恒。但测试毕竟只是手段,而非目的,如何合理安排测试就需要开发人员呢因地制宜。

testing

工具链和标准库自带单元测试框架,这让测试工作变得相对容易。

  • 测试代码须放在当前包以“_test.go”结尾的文件中。
  • 测试函数以Test为名称前缀
  • 测试命令go test 忽略以“_”或“.”开头的测试文件
  • 正常编译操作会忽略测试文件。

main_test.go

package main

import "testing"

func add(x, y int) int {
	return x + y
}

func TestAdd(t *testing.T){
	if add(1,2) != 3{
		t.FailNow()
	}
}
结果
PS D:\go_learn\5\test> go test -v
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok      _/D_/go_learn/5/test    0.189s

标准库testing提供了专用类型T来控制测试结果和行为

方法说明相关
Fail失败:继续执行当前函数
FailNow失败:立即终止终止当前测试函数Faild
SkipNow跳过:停止执行当前函数Skip,Skipf,Skipped
Log输出错误信息。仅失败或-v时输出Logf
Parallel与同样设置的测试函数并行执行
ErrorFail + LogErrorf
FatalFailNow+LogFatalf

使用Parallel可有效利用多核优势,缩短测试时间。

func TestAdd(t *testing.T) {
	t.Parallel()
	t.Fail()
	time.Sleep(time.Second * 2)
}

func TestAdd2(t *testing.T) {
	t.Parallel()
	time.Sleep(time.Second * 2)
}
结果
PS D:\go_learn\5\test> go test -v -args "b"
=== RUN   TestAdd
=== PAUSE TestAdd
=== RUN   TestAdd2
=== PAUSE TestAdd2
=== CONT  TestAdd
=== CONT  TestAdd2
--- PASS: TestAdd2 (2.00s)
--- FAIL: TestAdd (2.00s)
FAIL
exit status 1
FAIL    _/D_/go_learn/5/test    2.210s
PS D:\go_learn\5\test>

从测试总耗时可以看出并行执行的结果只有2s
只有一个测试函数调用Parallel方法并没有效果,且go test 执行参数patallel必须大于1
常用测试参数

参数说明示例
-args命令行参数
-v输出详细信息
-parallel并发执行,默认值为GOMAXPROCS-parallel 2
-run指定测试函数,正则表达式-run “Add"
-timeout全部测试累计时间超时将引发panic,默认值为10ms-timeout 1m30s
-count重复测试次数,默认值为1
table driven

单元测试代码一样要写得简洁优雅,好多时候,我们可以用一种类似数据表的模式来批量输入条件并依次比对结果。

func TestAdd(t *testing.T) {
	var tests = []struct {
		x      int
		y      int
		except int
	}{
		{1, 1, 2},
		{2, 2, 4},
		{2, 2, 5},
	}
	for _, tt := range tests {
		actual := add(tt.x, tt.y)
		if actual != tt.except {
			t.Errorf("add(%d,%d):except %d,actual %d", tt.x, tt.y, tt.except, actual)
		}
	}
}
结果
PS D:\go_learn\5\test> go test -v -args "b"
=== RUN   TestAdd
--- FAIL: TestAdd (0.00s)
    main_test.go:27: add(2,2):except 5,actual 4
FAIL
exit status 1
FAIL    _/D_/go_learn/5/test    0.194s

这种方式将测试数据和测试逻辑分离,更便于维护。另外,使用Error是为了让整个表全部完成测试,以便知道具体是哪组条件出现问题。

test main

某些时候,须为测试用例提供初始化和清理操作,但testing并没有setup/teardown机制。解决方法是自定义一个名为TestMain的函数,go test 会改为执行该函数,而不再是具体的测试用例。

func TestMain(m *testing.M) {
	println("start testing")
	code := m.Run()		// 调用测试用例函数
	println("exit.")
	os.Exit(code)
}
example

例代码最大的用途不是测试,而是导入GoDoc等工具生成的帮助文档中。它通过对比输出(stdout)结果和内部output注释是否一致判断是否成功。

func ExampleAdd() {
	fmt.Println(add(1, 2))
	fmt.Println(add(2, 2))

	// Output:
	// 3
	// 4
}
结果
=== RUN   ExampleAdd
--- PASS: ExampleAdd (0.00s)
FAIL
exit status 1
FAIL    _/D_/go_learn/5/test    0.283s

如果没有output注释,该示例函数就不会被执行。另外,不能使用内置函数print/println,因为它们输出到stderr。

11.2 性能测试。

性能测试函数以Benchmark为名称前缀,同样保存在“*_test.go”文件里。

func add(x, y int) int {
	return x + y
}

func BenchmarkAdd(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = add(1, 2)
	}
}
结果
PS D:\go_learn\5\test> go test -bench .
goos: windows
goarch: amd64
BenchmarkAdd-4          2000000000               0.34 ns/op
PASS
ok      _/D_/go_learn/5/test    0.898s

测试工具默认不会执行性能测试,须使用bench参数。它通过逐步调整B.N值,反复执行测试函数,直到获得准确的测量结果。

func BenchmarkAdd(b *testing.B) {
	println("B.N=",b.N)
	for i := 0; i < b.N; i++ {
		_ = add(1, 2)
	}
}
结果
PS D:\go_learn\5\test> go test -bench .
B.N= 1
goos: windows
goarch: amd64
BenchmarkAdd-4          B.N= 100
B.N= 10000
B.N= 1000000
B.N= 100000000
B.N= 2000000000
2000000000               0.35 ns/op
PASS
ok      _/D_/go_learn/5/test    0.933s

如果仅希望执行性能测试,那么可以用run=NONE 忽略所有单元测试用例。
默认就以并发方式执行测试,但可用cpu参数设定多个并发限制来观察结果。

PS D:\go_learn\5\test> go test -bench . -cpu 1,2,4
B.N= 1
goos: windows
goarch: amd64
BenchmarkAdd            B.N= 100
B.N= 10000
B.N= 1000000
B.N= 100000000
B.N= 2000000000
2000000000               0.34 ns/op
BenchmarkAdd-2          B.N= 1
B.N= 100
B.N= 10000
B.N= 1000000
B.N= 100000000
B.N= 2000000000
2000000000               0.34 ns/op
BenchmarkAdd-4          B.N= 1
B.N= 100
B.N= 10000
B.N= 1000000
B.N= 100000000
B.N= 2000000000
2000000000               0.38 ns/op
PASS
ok      _/D_/go_learn/5/test    2.413s

某些耗时的目标,默认循环次数过少,取平均值不足以准确计量性能。可用benchtime设定最小测试时间来增加循环次数,以便返回更准确的结果;

func sleep() {
	time.Sleep(time.Second)
}

func BenchmarkAdd(b *testing.B) {
	println("b.N",b.N)
	for i:=0;i<b.N;i++{
		sleep()
	}
}
结果
PS D:\go_learn\5\test> go test -bench . -benchtime 30s
b.N 1
goos: windows
goarch: amd64
BenchmarkAdd-4          b.N 50
      50        1003281622 ns/op
PASS
ok      _/D_/go_learn/5/test    51.370s
timer

如果要在测试函数中执行一些额外操作,那么应该阻止计时器工作。

func BenchmarkAdd(b *testing.B) {
	time.Sleep(time.Second)
	b.ResetTimer()

	for i:=0;i<b.N;i++{
		_ = add(1,2)
		if i == 1{
			b.StopTimer()
			time.Sleep(time.Second)
			b.StartTimer()
		}
	}
}
结果
PS D:\go_learn\5\test> go test -bench .
goos: windows
goarch: amd64
BenchmarkAdd-4          2000000000               0.70 ns/op
PASS
ok      _/D_/go_learn/5/test    12.772s
memorry

性能测试关心的不仅仅是执行时间,还包括在堆上的内存分配,因为内存分配和垃圾回收的相操作也应计入消耗成本。

func heap() []byte {
	return make([]byte, 1024*10)
}

func Benchmarkhead(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = heap()
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值