golang测试技术

chenglinhust · · 6612 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。
golang测试技术

一、基础测试技术

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) 
}


摘自:http://itindex.net/detail/51483-golang-%E6%B5%8B%E8%AF%95-%E6%8A%80%E6%9C%AF

有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:chenglinhust

查看原文:golang测试技术

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:1006366459

6612 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传