Go 语言测试框架 GinkGo 的使用方法

学习 ingress-nginx 时,想修改它的代码并将代码提交到社区,了解 e2e 测试时,遇到了 GinkGo

学习环境

这里使用的 go 版本是 go 1.13.3。

创建工作目录,安装 ginkgo:

$ go get github.com/onsi/ginkgo/ginkgo
$ go get github.com/onsi/gomega/...
$ ginkgo version
Ginkgo Version 1.10.2

准备一个待测的 package:

$ mkdir study-Ginkgo
$ cd study-Ginkgo
$ go mod init studyginkgo

在文件 target_funcs/target_funcs.go 中写几个待测的函数:

package target_funcs

func ReturnTrue() bool {
    return true
}

func ReturnFalse() bool {
    return false
}

func ReturnInt(v int) int {
    return v
}

ginkgo 测试代码的运行

用 ginkgo 命令生成测试模板:

$ cd target_funcs/
$ ginkgo bootstrap
Generating ginkgo test suite bootstrap for target_funcs in:
    target_funcs_suite_test.go

刚生成的 target_funcs_suite_test.go 中现在还没有任何测试代码,运行方法:

方法1,用 ginkgo:

$ ginkgo
Running Suite: TargetFuncs Suite
================================
Random Seed: 1571986734
Will run 0 of 0 specs


Ran 0 of 0 Specs in 0.001 seconds
SUCCESS! -- 0 Passed | 0 Failed | 0 Pending | 0 Skipped
PASS

Ginkgo ran 1 suite in 5.353367082s
Test Suite Passed

方法2,用 go test:

go test
Running Suite: TargetFuncs Suite
================================
Random Seed: 1571986747
Will run 0 of 0 specs


Ran 0 of 0 Specs in 0.000 seconds
SUCCESS! -- 0 Passed | 0 Failed | 0 Pending | 0 Skipped
PASS
ok      studyginkgo/target_funcs    0.017s

方法3,生成二进制文件,ginkgo 可以测试代码编译到一个二进制文件中:

$ ginkgo build
Compiling target_funcs...
    compiled target_funcs.test

生成的二进制文件 target_funcs.test 可以直接运行:

./target_funcs.test
Running Suite: TargetFuncs Suite
================================
Random Seed: 1571986954
Will run 0 of 0 specs


Ran 0 of 0 Specs in 0.000 seconds
SUCCESS! -- 0 Passed | 0 Failed | 0 Pending | 0 Skipped
PASS

ginkgo 测试代码的编写

为 target_funcs_suite_test.go 生成对应的测试代码文件:

$ ginkgo generate target_funcs

上面的命令生成测试 target_funcs_test.go。如果不在 $GOPATH 目录下,可能会遇到下面的错误,ginkgo 大概还没有支持 gomod(2019-10-25 15:23:23)。这个错误不影响使用,将 target_funcs_test.go 中的 UNKNOWN_PACKAGE_PATH 修改成所在的 package 就可以了。

Couldn't identify package import path.

    ginkgo generate

Must be run within a package directory under $GOPATH/src/...
You're going to have to change UNKNOWN_PACKAGE_PATH in the generated file...

Generating ginkgo test for TargetFuncs in:
  target_funcs_test.go

target_funcs_test.go 内容如下,在 Describe 中添加测试函数:

package target_funcs_test

import (
    . "github.com/onsi/ginkgo"
    . "github.com/onsi/gomega"

    . "studyginkgo/target_funcs"
)

var _ = Describe("TargetFuncs", func() {

})

ginkgo 提供的 block

ginkgo 提供了多个类型的 block(函数),上面的 Describe() 就是一个 block。

在 XX_suite_test.go (这里是 target_funcs_suite_test.go)中使用的 block:

BeforeSuite()          :在整个测试开始之前执行的操作
AfterSuite()           :在整个测试完成之后执行的操作

在 XX_test.go(这里是 target_funcs_test.go)中使用的 block:

BeforeEach()           :每个测试用例运行前执行的操作,位于 Describe 中,可以有多个
JustBeforeEach()       :和BeforeEach()类似,在所有的 BeforeEach()之后和It()之前执行
AfterEach()            :每个测试用例运行后执行的操作
JustAfterEach()        :紧跟在It()之后执行

Describe()             :最顶层的测试用例包裹容器,同一目标的测试用例,可以嵌套
Context()              :比 Describe 低一级的测试用例包裹容器,同一个条件下的测试用例
It()                   :单个测试用例,位于 Describe 或者 Context 中
Specify()              :It()的别名,用途和 It() 完全相同

专用于性能测试的 block,使用范围和 It() 相同:

Measure()              :用于性能测试的 block()

Describe、Context、It 和 Measure 支持 P 和 X 前缀,带有 P 或 X 前缀的这几个 block 不参与测试。

Describe、Context 和 It 支持 F 前缀,如果有带有 F 前缀的这些 block,测试时只会运行这些 block 中的测试用例。

结果判断

GinkGo 提供了多个用来进行数值判断的函数。

Expect(actual interface{}) 为传入参数创建一个断言 Assertion,Assertion 支持的以下的判断方法:

type Assertion interface {
    Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
    ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool

    To(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
    ToNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
    NotTo(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool
}

断言的第一个参数是 matcher,在 github.com/onsi/gomega/matcher.go 中定义,例如:

func BeNil() types.GomegaMatcher {
    return &matchers.BeNilMatcher{}
}

//BeTrue succeeds if actual is true
func BeTrue() types.GomegaMatcher {
    return &matchers.BeTrueMatcher{}
}

使用举例,分配检测 err 和 svc 的值:

Expect(err).To(BeNil(), "unexpected error obtaining ingress-nginx service")
Expect(svc).NotTo(BeNil(), "expected a service but none returned")

测试示例

target_funcs_suite_test.go 内容如下:

package target_funcs_test
import (
    "testing"

    . "github.com/onsi/ginkgo"
    . "github.com/onsi/gomega"
)
func TestTargetFuncs(t *testing.T) {
    RegisterFailHandler(Fail)
    RunSpecs(t, "TargetFuncs Suite")
}
var _ = BeforeSuite(func() {
    println("BeforeSuite")
})
var _ = AfterSuite(func() {
    println("AfterSuite")
})

target_funcs_test.go 的内容如下:

package target_funcs_test
import (
    . "github.com/onsi/ginkgo"
    . "github.com/onsi/gomega"

    . "studyginkgo/target_funcs"
)

var _ = Describe("TargetFuncs", func() {
    BeforeEach(func() {
        println("BeforeEach-2")
    })
    BeforeEach(func() {
        println("BeforeEach-1")
    })
    JustBeforeEach(func() {
        println("JustBeforeEach-1")
    })
    JustBeforeEach(func() {
        println("JustBeforeEach-2")
    })
    JustAfterEach(func() {
        println("JustAfterEach-1")
    })
    JustAfterEach(func() {
        println("JustAfterEach-2")
    })
    AfterEach(func() {
        println("AfterEach-1")
    })
    AfterEach(func() {
        println("AfterEach-2")
    })
    Describe("ReturnInt", func() {
        Context("default", func() {
            var (
                input  int
                result int
            )
            BeforeEach(func() {
                println("BeforeEach in Context")
                input = 1
                result = 1
            })
            AfterEach(func() {
                println("AfterEach in Context")
                input = 0
                result = 0
            })
            It("return value", func() {
                println("Exec Test Case")
                v := ReturnInt(input)
                Expect(v).To(Equal(result))
            })
        })
    })
})

执行结果:

$ go test
Running Suite: TargetFuncs Suite
================================
Random Seed: 1571998428
Will run 1 of 1 specs

BeforeSuite
BeforeEach-2
BeforeEach-1
BeforeEach in Context
JustBeforeEach-1
JustBeforeEach-2
Exec Test Case
JustAfterEach-1
JustAfterEach-2
AfterEach in Context
AfterEach-1
AfterEach-2
•AfterSuite

Ran 1 of 1 Specs in 0.000 seconds
SUCCESS! -- 1 Passed | 0 Failed | 0 Pending | 0 Skipped
PASS
ok      studyginkgo/target_funcs    0.018s

参考

  1. 李佶澳的博客