Golang测试技术
本篇文章内容来源于Golang核心开发组成员Andrew Gerrand在Google I/O 2014的一次主题分享“Testing Techniques”,即介绍使用Golang开发 时会使用到的测试技术(主要针对单元测试),包括基本技术、高级技术(并发测试、mock/fake、竞争条件测试、并发测试、内/外部测 试、vet工具等)等,感觉总结的很全面,这里整理记录下来,希望能给大家带来帮助。原Slide访问需要自己搭梯子。另外这里也要吐槽一 下:Golang官方站的slide都是以一种特有的golang artical的格式放出的(用这个工具http://go-talks.appspot.com/可以在线观看),没法像pdf那样下载,在国内使用和传播极其不便。
一、基础测试技术
1、测试Go代码
Go语言内置测试框架。
内置的测试框架通过testing包以及go test命令来提供测试功能。
下面是一个完整的测试strings.Index函数的完整测试文件:
//strings_test.go (这里样例代码放入strings_test.go文件中)
package strings_test
import (
"strings"
"testing"
)
func TestIndex(t *testing.T) {
const s, sep, want = "chicken", "ken", 4
got := strings.Index(s, sep)
if got != want {
t.Errorf("Index(%q,%q) = %v; want %v", s, sep, got, want)//注意原slide中的got和want写反了
}
}
$go test -v strings_test.go
=== RUN TestIndex
— PASS: TestIndex (0.00 seconds)
PASS
ok command-line-arguments 0.007s
go test的-v选项是表示输出详细的执行信息。
将代码中的want常量值修改为3,我们制造一个无法通过的测试:
$go test -v strings_test.go
=== RUN TestIndex
— FAIL: TestIndex (0.00 seconds)
strings_test.go:12: Index("chicken","ken") = 4; want 3
FAIL
exit status 1
FAIL command-line-arguments 0.008s
2、表驱动测试
Golang的struct字面值(struct literals)语法让我们可以轻松写出表驱动测试。
package strings_test
import (
"strings"
"testing"
)
func TestIndex(t *testing.T) {
var tests = []struct {
s string
sep string
out int
}{
{"", "", 0},
{"", "a", -1},
{"fo", "foo", -1},
{"foo", "foo", 0},
{"oofofoofooo", "f", 2},
// etc
}
for _, test := range tests {
actual := strings.Index(test.s, test.sep)
if actual != test.out {
t.Errorf("Index(%q,%q) = %v; want %v",
test.s, test.sep, actual, test.out)
}
}
}
$go test -v strings_test.go
=== RUN TestIndex
— PASS: TestIndex (0.00 seconds)
PASS
ok command-line-arguments 0.007s
3、T结构
*testing.T参数用于错误报告:
t.Errorf("got bar = %v, want %v", got, want)
t.Fatalf("Frobnicate(%v) returned error: %v", arg, err)
t.Logf("iteration %v", i)
也可以用于enable并行测试(parallet test):
t.Parallel()
控制一个测试是否运行:
if runtime.GOARCH == "arm" {
t.Skip("this doesn't work on ARM")
}
4、运行测试
我们用go test命令来运行特定包的测试。
默认执行当前路径下包的测试代码。
$ go test
PASS
$ go test -v
=== RUN TestIndex
— PASS: TestIndex (0.00 seconds)
PASS
要运行工程下的所有测试,我们执行如下命令:
$ go test github.com/nf/…
标准库的测试:
$ go test std
注:假设strings_test.go的当前目录为testgo,在testgo目录下执行go test都是OK的。但如果我们切换到testgo的上一级目录执行go test,我们会得到什么结果呢?
$go test testgo
can't load package: package testgo: cannot find package "testgo" in any of:
/usr/local/go/src/pkg/testgo (from $GOROOT)
/Users/tony/Test/GoToolsProjects/src/testgo (from $GOPATH)
提示找不到testgo这个包,go test后面接着的应该是一个包名,go test会在GOROOT和GOPATH下查找这个包并执行包的测试。
5、测试覆盖率
go tool命令可以报告测试覆盖率统计。
我们在testgo下执行go test -cover,结果如下:
go build _/Users/tony/Test/Go/testgo: no buildable Go source files in /Users/tony/Test/Go/testgo
FAIL _/Users/tony/Test/Go/testgo [build failed]
显然通过cover参数选项计算测试覆盖率不仅需要测试代码,还要有被测对象(一般是函数)的源码文件。
我们将目录切换到$GOROOT/src/pkg/strings下,执行go test -cover:
$go test -v -cover
=== RUN TestReader
— PASS: TestReader (0.00 seconds)
… …
=== RUN: ExampleTrimPrefix
— PASS: ExampleTrimPrefix (1.75us)
PASS
coverage: 96.9% of statements
ok strings 0.612s
go test可以生成覆盖率的profile文件,这个文件可以被go tool cover工具解析。
在$GOROOT/src/pkg/strings下面执行:
$ go test -coverprofile=cover.out
会再当前目录下生成cover.out文件。
查看cover.out文件,有两种方法:
a) cover -func=cover.out
$sudo go tool cover -func=cover.out
strings/reader.go:24: Len 66.7%
strings/reader.go:31: Read 100.0%
strings/reader.go:44: ReadAt 100.0%
strings/reader.go:59: ReadByte 100.0%
strings/reader.go:69: UnreadByte 100.0%
… …
strings/strings.go:638: Replace 100.0%
strings/strings.go:674: EqualFold 100.0%
total: (statements) 96.9%
b) 可视化查看
执行go tool cover -html=cover.out命令,会在/tmp目录下生成目录coverxxxxxxx,比如/tmp/cover404256298。目录下有一个 coverage.html文件。用浏览器打开coverage.html,即可以可视化的查看代码的测试覆盖情况。
关于go tool的cover命令,我的go version go1.3 darwin/amd64默认并不自带,需要通过go get下载。
$sudo GOPATH=/Users/tony/Test/GoToolsProjects go get code.google.com/p/go.tools/cmd/cover
下载后,cover安装在$GOROOT/pkg/tool/darwin_amd64下面。
二、高级测试技术
1、一个例子程序
outyet是一个web服务,用于宣告某个特定Go版本是否已经打标签发布了。其获取方法:
go get github.com/golang/example/outyet
注:
go get执行后,cd $GOPATH/src/github.com/golang/example/outyet下,执行go run main.go。然后用浏览器打开http://localhost:8080即可访问该Web服务了。
2、测试Http客户端和服务端
net/http/httptest包提供了许多帮助函数,用于测试那些发送或处理Http请求的代码。
3、httptest.Server
httptest.Server在本地回环网口的一个系统选择的端口上listen。它常用于端到端的HTTP测试。
type Server struct {
URL string // base URL of form http://ipaddr:port with no trailing slash
Listener net.Listener
// TLS is the optional TLS configuration, populated with a new config
// after TLS is started. If set on an unstarted server before StartTLS
// is called, existing fields are copied into the new config.
TLS *tls.Config
// Config may be changed after calling NewUnstartedServer and
// before Start or StartTLS.
Config *http.Server
}
func NewServer(handler http.Handler) *Server
func (*Server) Close() error
4、httptest.Server实战
下面代码创建了一个临时Http Server,返回简单的Hello应答:
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, client")
}))
defer ts.Close()
res, err := http.Get(ts.URL)
if err != nil {
log.Fatal(err)
}
greeting, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", greeting)
5、httptest.ResponseRecorder
httptest.ResponseRecorder是http.ResponseWriter的一个实现,用来记录变化,用在测试的后续检视中。
type ResponseRecorder struct {
Code int // the HTTP response code from WriteHeader
HeaderMap http.Header // the HTTP response headers
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
Flushed bool
}
6、httptest.ResponseRecorder实战
向一个HTTP handler中传入一个ResponseRecorder,通过它我们可以来检视生成的应答。
handler := func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "something failed", http.StatusInternalServerError)
}
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if err != nil {
log.Fatal(err)
}
w := httptest.NewRecorder()
handler(w, req)
fmt.Printf("%d – %s", w.Code, w.Body.String())
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)
}
Golang测试技术的更多相关文章
- golang测试
简述 Go语言中自带有一个轻量级的测试框架testing和自带的go test命令来实现单元测试和性能测试. go test [-c] [-i] [build flags] [packages] [f ...
- 应用安全测试技术DAST、SAST、IAST对比分析【转】
转自:https://blog.csdn.net/qq_29277155/article/details/92411079 一.全球面临软件安全危机 2010年,大型社交网站rockyou.com被曝 ...
- 漫谈SCA(软件成分分析)测试技术:原理、工具与准确性
摘要:本文介绍了SCA技术的基本原理.应用场景,业界TOP SCA商用工具的分析说明以及技术发展趋势:让读者对SCA技术有一个基本初步的了解,能更好的准确的应用SCA工具来发现应用软件中一些安全问题, ...
- 共享业务稳定性测试&技术创新组
本文其实是一篇招聘贴,不管你以前是做开发还是测试,都欢迎加入我们的小组. 2014年阿里巴巴的共享业务事业部有很大变化,共享的测试团队也做了一些调整,我不再担任共享业务的测试经理,但是仍然会留在共享测 ...
- web应用程序测试方法和测试技术详述
1.界面测试 现在一般人都有使用浏览器浏览网页的经历,用户虽然不是专业人员但是对界面效果的印象是很重要的.如果你注重这方面的测试,那么验证应用程序是否易于使用就非常重要了.很多人认为这是测试中最不重要 ...
- [亿能测试_www.gdtesting.com]测试技术资料网盘共享
[亿能测试_www.gdtesting.com]测试技术资料网盘共享: http://pan.baidu.com/share/home?uk=974090852
- 什么是AOI自动光学检测机测试技术
前言 当印刷电路板的价格以每年六到八个百分点的速度持续滑落,产品的生产良率就成为维持产业竞争力的要素.要获得高的良品率需要有良好的制造控制能力,而要获得良好的制程控制,似乎就必须要在各个制程中执行良好 ...
- 深入浅出解读 Java 虚拟机的差别测试技术
本文分享基于字节码种子生成有效.可执行的字节码文件变种,并用于 JVM 实现的差别测试.本文特别提出用于修改字节码语法的classfuzz技术和修改字节码语义的classming技术.上述变种技术系统 ...
- 测者的测试技术手册:AI的自动化单元测试
测者的测试技术手册:AI的自动化单元测试 谈新技术:AI的自动化单元测试
随机推荐
- .gitigore 相关
为什么要配置.gitigore 在我们使用git的过程当中,不是任何文件都需要commit到本地或者远程仓库的,比如一些三方库文件.那么作为一个git新手,很多人不知道如何配置.gitignore文件 ...
- myeclipse中常用的快捷键
存盘 Ctrl+s(肯定知道) 注释代码 Ctrl+/ 取消注释 Ctrl+\(Eclipse3已经都合并到Ctrl+/了) 代码辅助 Alt+/ 快速修复 Ctrl+1 代码格式化 Ctrl+Shi ...
- java断点
第一步: 用firefox运行程序,当点击保存,提示保存失败后,启动firebug 通过请求找到addNew.ezt出现错误,在eztnews.xml里通过ctrl+F查找找到请求执行的类和方法 找到 ...
- 在win7 64bit系统安装QC软件
当本机系统不再QC软件支持的系统上,可以在setup右键选择兼容性选择能支持安装的系统, 在JBOSS页面,选择该服务器,用户名输入本机的用户名和密码,如果没有配置域输入计算机名. 如果没有装IIS, ...
- AutoTile 自动拼接(一) 学习与实践
恩,大家好,这两天江苏冷空气袭击,下了今年 第一场第二场雪. 不过今天我要说的 ,和 上面的 屁关系都没有. 今天要说的是 2d无缝自动拼接.大家有没有玩过 RPG Maker VX Ace. 类似 ...
- Struts2语法--Ognl
OGNL: Object Graph Navigation Language index.jsp: <body> 访问属性 <a href="<%=contextPa ...
- css link import
link和@import的区别 页面中使用CSS的方式主要有3种:行内添加定义style属性值,页面头部内嵌调用和外面链接调用,其中外面引用有两种:link和@import.外部引用CSS两种方式 ...
- 用批处理文件删除n天前的文件
原文:http://blog.csdn.net/leehq/archive/2007/08/03/1723743.aspx 公司服务器用来备份数据的硬盘过段时间就会被备份文件占满,弄得我老是要登录到服 ...
- .net core 14
- CSS中绝对定位依据谁进行定位?
结论 绝对定位的top等的依据元素需满足3个条件: 已定位(position:relative/fixed/absolute) 最近的 祖辈元素(一定是祖辈元素不是同辈元素) 说明 一般会为body设 ...