7、竞争检测(race detection)
当两个goroutine并发访问同一个变量,且至少一个goroutine对变量进行写操作时,就会发生数据竞争(data race)。
为了协助诊断这种bug,Go提供了一个内置的数据竞争检测工具。
通过传入-race选项,go tool就可以启动竞争检测。
$ go test -race mypkg // to test the package
$ go run -race mysrc.go // to run the source file
$ go build -race mycmd // to build the command
$ go install -race mypkg // to install the package
注:一个数据竞争检测的例子
例子代码:
//testrace.go package main import "fmt" import "time" func main() { var i int = 0 go func() { for { i++ fmt.Println("subroutine: i = ", i) time.Sleep(1 * time.Second) } }() for { i++ fmt.Println("mainroutine: i = ", i) time.Sleep(1 * time.Second) } } $go run -race testrace.go mainroutine: i = 1 ================== WARNING: DATA RACE Read by goroutine 5: main.func·001() /Users/tony/Test/Go/testrace.go:10 +0×49 Previous write by main goroutine: main.main() /Users/tony/Test/Go/testrace.go:17 +0xd5 Goroutine 5 (running) created at: main.main() /Users/tony/Test/Go/testrace.go:14 +0xaf ================== subroutine: i = 2 mainroutine: i = 3 subroutine: i = 4 mainroutine: i = 5 subroutine: i = 6 mainroutine: i = 7 subroutine: i = 8 |
8、测试并发(testing with concurrency)
当测试并发代码时,总会有一种使用sleep的冲动。大多时间里,使用sleep既简单又有效。
但大多数时间不是”总是“。
我们可以使用Go的并发原语让那些奇怪不靠谱的sleep驱动的测试更加值得信赖。
9、使用静态分析工具vet查找错误
vet工具用于检测代码中程序员犯的常见错误:
– 错误的printf格式
– 错误的构建tag
– 在闭包中使用错误的range循环变量
– 无用的赋值操作
– 无法到达的代码
– 错误使用mutex
等等。
使用方法:
go vet [package]
10、从内部测试
golang中大多数测试代码都是被测试包的源码的一部分。这意味着测试代码可以访问包种未导出的符号以及内部逻辑。就像我们之前看到的那样。
注:比如$GOROOT/src/pkg/path/path_test.go与path.go都在path这个包下。
11、从外部测试
有些时候,你需要从被测包的外部对被测包进行测试,比如测试代码在package foo_test下,而不是在package foo下。
这样可以打破依赖循环,比如:
– testing包使用fmt
– fmt包的测试代码还必须导入testing包
– 于是,fmt包的测试代码放在fmt_test包下,这样既可以导入testing包,也可以同时导入fmt包。
12、Mocks和fakes
通过在代码中使用interface,Go可以避免使用mock和fake测试机制。
例如,如果你正在编写一个文件格式解析器,不要这样设计函数:
func Parser(f *os.File) error
作为替代,你可以编写一个接受interface类型的函数:
func Parser(r io.Reader) error
和bytes.Buffer、strings.Reader一样,*os.File也实现了io.Reader接口。
13、子进程测试
有些时候,你需要测试的是一个进程的行为,而不仅仅是一个函数。例如:
func Crasher() {
fmt.Println("Going down in flames!")
os.Exit(1)
}
为了测试上面的代码,我们将测试程序本身作为一个子进程进行测试:
func TestCrasher(t *testing.T) { if os.Getenv("BE_CRASHER") == "1" { Crasher() return } cmd := exec.Command(os.Args[0], "-test.run=TestCrasher") cmd.Env = append(os.Environ(), "BE_CRASHER=1") err := cmd.Run() if e, ok := err.(*exec.ExitError); ok && !e.Success() { return } t.Fatalf("process ran with err %v, want exit status 1", err) } |