09 September 2014

目录

1. Go testing包

go标准库:testing提供简单单元测试功能。

文档:http://golang.org/pkg/testing/

测试命令行参数说明:http://golang.org/cmd/go/#hdr-Description_of_testing_flags

go测试用例分为3种:

  1. Test
  2. Benchmark
  3. Example

测试用例必须以以上三个单词开头,函数原型类似:

// 普通单元测试用例
func TestFoo(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping test in short mode.")
    }
    // ...
}
 
// 性能测试用例
func BenchmarkBar(b *testing.B) {
    for i := 0; i < b.N; i++ {
        // ...
    }
}
 
// 示例
func ExampleBaz() {
    fmt.Println("hello")
    // Output: hello
}
 
func ExampleQux() {
    fmt.Println("hello, and")
    fmt.Println("goodbye")
    // Output:
    // hello, and
    // goodbye
}

1.1 Example用例

在Example用例中,如下// Output:开头的为输出注释:

// Output:
// hello, and
// goodbye

在运行时会比较标准输出与输出注释的内容是否一致(忽略开头结尾的空白字符),如果Example没有输出注释,该用例只会被编译而不会运行。

Example用例的名字约定:F为函数名,T为类型,M为类型T的方法:

func Example() { ... }
func ExampleF() { ... }
func ExampleT() { ... }
func ExampleT_M() { ... }

同一个方法、类型、函数的不同Example用例可以在函数名加后缀来区分,后缀必须以小写字母结尾:

func Example_suffix() { ... }
func ExampleF_suffix() { ... }
func ExampleT_suffix() { ... }
func ExampleT_M_suffix() { ... }

当一个文件只包含一个Example用例,至少包含一个其他函数、类型、申明,而且没有其他Test、Benchmark用例时,整个文件作为一个被展示的示例。

完整的Example使用:

package demo
 
import "testing"
import "fmt"
 
var _ = &testing.T{}
 
func Foo() {
    fmt.Println("Hello");
}
 
func ExampleFoo() {
    Foo()
    // Output:
    // Hello
    //
}

1.2 运行测试

测试用例只要保存在xxx_test.go文件中,然后:

运行当前路径下的测试用例:

go test

运行从当前路径开始所有子目录中的测试用例:

go test ./...

有时可能需要获得测试用例的可执行文件,用于调试等目的,使用-c参数可以只编译测试用例而不运行,并获得一个名为xxx.test的可执行文件。

go test -c

go test命令只会运行TestXXXExampleXXX用例。

xxx_test.go文件只会在测试时被编译。

1.3 testing包的不足之处

  1. 没有提供任何assert、expect比较机制,测试变量值会很不方便。比如,要测试函数Foo的返回值,可能需要写作:

     func TestFoo(t *testing.T) {
     	if rv := Foo(); rv != 0 {
     		t.Fatalf("Test fail:\n expected: 0, but is: %v", rv)
     	}
     }
    
  2. 没有提供任何setup, teardown机制,初始化、清理测试数据会很不方便。

  3. 没有提供exception测试机制,不方便测试defer-panic-recover。

  4. 测试输出内容太少,不方便生成测试报告。

针对这些问题,一些第三方库在go原有的测试设施基础上被开发出来。

2. 第三方测试库

2.1 对变量检查功能的增强

  • gocheck:增加变量检查机制、setup/teardown机制。
  • gunit:提供assert/expect变量检查机制、针对函数多返回值提供批量检查机制、提供setup/teardown机制、提供panic测试机制,更友好测试报告输出。

2.2 BDD测试框架

  • Ginkgo:基于BDD(行为驱动)的测试框架,功能很全面。

3. Gunit

gunit拥有更友好的输出:

Rich Output

完整的文档见此处:https://github.com/emptyland/akino/blob/master/gunit/README.md

4. Ginkgo

这是个BDD风格的测试框架,功能很完善。

Ginkgo要求Golang v1.2+

4.1 获取Ginkgo

只需要使用go get命令:

$ go get github.com/onsi/ginkgo/ginkgo
$ go get github.com/onsi/gomega

这会获取ginkgo包,并安装ginkgo命令到$GOPATH/bin中。

4.2 编写第一个测试

ginkgo hook了testing包,可以使用go test命令运行ginkgo测试用例。这也表示可以使用go testginkgo两个命令来运行测试ginkgo测试用例。

自举一个测试套件(Suite)

要编写ginkgo测试用例必须先自举一个测试套件,假设需要测试book包:

$ cd path/to/books
$ ginkgo bootstrap

这会生成一个名为books_suite_test.go的文件,包含以下内容:

package books_test
 
import (
    . "github.com/onsi/ginkgo"
    . "github.com/onsi/gomega"
 
    "testing"
)
 
func TestBooks(t *testing.T) {
    RegisterFailHandler(Fail)
    RunSpecs(t, "Books Suite")
}

尝试运行测试:

$ ginkgo #or go test

输出类似:

Ginkgo Output

添加一个规范(Specs)到套件中

为book.go增加一个规范:

$ ginkgo generate book

这会生成一个名为book_test.go的文件,包含以下内容:

package books_test
 
import (
    . "/path/to/books"
    . "github.com/onsi/ginkgo"
    . "github.com/onsi/gomega"
)
 
var _ = Describe("Book", func() {
 
})

在生成的测试文件中编写测试:

var _ = Describe("Book", func() {
    var (
        longBook  Book
        shortBook Book
    )
 
    BeforeEach(func() {
        longBook = Book{
            Title:  "Les Miserables",
            Author: "Victor Hugo",
            Pages:  1488,
        }
 
        shortBook = Book{
            Title:  "Fox In Socks",
            Author: "Dr. Seuss",
            Pages:  24,
        }
    })
 
    Describe("Categorizing book length", func() {
        Context("With more than 300 pages", func() {
            It("should be a novel", func() {
                Expect(longBook.CategoryByLength()).To(Equal("NOVEL"))
            })
        })
 
        Context("With fewer than 300 pages", func() {
            It("should be a short story", func() {
                Expect(shortBook.CategoryByLength()).To(Equal("SHORT STORY"))
            })
        })
    })
})

由此可见:

  • Ginkgo广泛使用闭包使得你可以构建描述性的测试套件。
  • 应该使用DescribeContext来有意义地组织代码。
  • 应该使用BeforeEach为规范设置状态(SetUp机制),并用It指定一个单一规范。
  • 为了使得BeforeEachIt可以访问到闭包变量,通常需要将闭包变量和BeforeEach定义到包含DescribeContext的最顶层。


blog comments powered by Disqus