目录

  • 第十一章 测试
  • go test
  • 测试函数
  • 随机测试
  • 白盒测试
  • 测试覆盖率
  • 基准测试
  • 示例函数

第十一章 测试

  • 这一章实践性非常强,笔记大多只是概念
  • 我们说测试的时候一般是指自动化测试,也就是写一些小的程序用来检测被测试代码(产品代码)的行为和预期的一样,这些通常都是精心设计的执行某些特定的功能或者是通过随机性的输入要验证边界的处理。

go test

  • go test命令是一个按照一定约定和组织的测试代码的驱动程序
  • 在包目录内,所有以_test.go为后缀名的源文件并不是通过go build来构建包的,而是go test测试的一部分
  • *_test.go文件,有三类函数
    1.测试函数:以Test为函数名前缀,用于测试程序的一些逻辑行为是否正确
    2.基准测试函数:以Benchmark为函数名前缀,用于衡量函数的性能
    3.示例函数:以Example为函数名前缀,提供一个由编译器保证正确性的示例文档
  • go test会遍历所有*_test.go文件中符合上述命名规则的函数,然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件

测试函数

  • 每个测试函数(相当于测试用例)必须导入testing包,其形式类似:
func TestSin(t *testing.T) { /* ... */ }
func TestCos(t *testing.T) { /* ... */ }
func TestLog(t *testing.T) { /* ... */ }
  • 使用t.Error或t.Errorf报告错误信息
  • go test命令如果没有参数指定包那么将默认采用当前目录对应的包
  • 先编写测试用例并观察到测试用例触发了和用户报告的错误相同的描述是一个好的测试习惯
  • 参数-v可用于打印每个测试函数的名字和运行时间
$ go test -v
=== RUN TestPalindrome
--- PASS: TestPalindrome (0.00s)
=== RUN TestNonPalindrome
--- PASS: TestNonPalindrome (0.00s)
=== RUN TestFrenchPalindrome
--- FAIL: TestFrenchPalindrome (0.00s)
word_test.go:28: IsPalindrome("été") = false
=== RUN TestCanalPalindrome
--- FAIL: TestCanalPalindrome (0.00s)
word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false
FAIL
exit status 1 
FAIL gopl.io/ch11/word1 0.017s
  • 参数 -run对应一个正则表达式,只有测试函数名被它正确匹配的测试函数才会被
    go test测试命令运行
$ go test -v -run="French|Canal"
=== RUN TestFrenchPalindrome
--- FAIL: TestFrenchPalindrome (0.00s)
word_test.go:28: IsPalindrome("été") = false
=== RUN TestCanalPalindrome
--- FAIL: TestCanalPalindrome (0.00s)
word_test.go:35: IsPalindrome("A man, a plan, a canal: Panama") = false
FAIL
exit status 1
FAIL gopl.io/ch11/word1 0.014s
  • 测试失败的信息一般的形式是“f(x) = y, want z”,其中f(x)解释了失败的操作和对应的输出,y是实际的运行结果,z是期望的正确的结果
func TestIsPalindrome(t *testing.T) {
	var tests = []struct {
		input string
		want bool
	}{
		{"", true},
		{"a", true},
		{"aa", true},
		{"ab", false},
		{"kayak", true},
		{"detartrated", true},
		{"A man, a plan, a canal: Panama", true},
		{"Evil I did dwell; lewd did I live.", true},
		{"Able was I ere I saw Elba", true},
		{"été", true},
		{"Et se resservir, ivresse reste.", true},
		{"palindrome", false}, // non-palindrome
		{"desserts", false}, // semi-palindrome
	}
	for _, test := range tests {
		if got := IsPalindrome(test.input); got != test.want {
			t.Errorf("IsPalindrome(%q) = %v", test.input, got)
		}
	}
}

随机测试

  • 格驱动的测试便于构造基于精心挑选的测试数据的测试用例。另一种测试思路是随机测试,也就是通过构造更广泛的随机输入来测试探索函数的行为。
import "math/rand"
// randomPalindrome returns a palindrome whose length and contents
// are derived from the pseudo-random number generator rng.
func randomPalindrome(rng *rand.Rand) string {
	n := rng.Intn(25) // random length up to 24
	runes := make([]rune, n)
	for i := 0; i < (n+1)/2; i++ {
		r := rune(rng.Intn(0x1000)) // random rune up to '\u0999'
		runes[i] = r
		runes[n-1-i] = r
	}
	return string(runes)
}
func TestRandomPalindromes(t *testing.T) {
	// Initialize a pseudo-random number generator.
	seed := time.Now().UTC().UnixNano()
	t.Logf("Random seed: %d", seed)
	rng := rand.New(rand.NewSource(seed))
	for i := 0; i < 1000; i++ {
		p := randomPalindrome(rng)
		if !IsPalindrome(p) {
			t.Errorf("IsPalindrome(%q) = false", p)
		}
	}
}

白盒测试

  • 一种测试分类的方法是基于测试者是否需要了解被测试对象的内部工作原理。黑盒测试只需要测试包公开的文档和API行为,内部实现对测试代码是透明的。相反,白盒测试有访问包内部函数和数据结构的权限,因此可以做到一下普通客户端无法实现的测试。

测试覆盖率

  • go tool cover

基准测试

  • 基本形式如下
import "testing"
func BenchmarkIsPalindrome(b *testing.B) {
	for i := 0; i < b.N; i++ {
		IsPalindrome("A man, a plan, a canal: Panama")
	}
}
  • 运行基准测试:
$ cd $GOPATH/src/gopl.io/ch11/word2
$ go test -bench=.
PASS
BenchmarkIsPalindrome-8 1000000  1035 ns/op
ok gopl.io/ch11/word2   2.179s

示例函数

  • 基本形式
func ExampleIsPalindrome() {
	fmt.Println(IsPalindrome("A man, a plan, a canal: Panama"))
	fmt.Println(IsPalindrome("palindrome"))
	// Output:
	// true
	// false
}