编写你的第一个测试

Ginkgo与Go现有的测试基础设施挂钩。这允许您使用 `go test` 运行Ginkgo套件。

这也意味着Ginkgo测试可以与传统的Go测试一起使用。 go test和ginkgo都可以在你的套件中运行所有测试。

启动一个套件

要为包编写Ginkgo测试,您必须先初始化(bootstrap )Ginkgo测试套件。假设您有一个名为 books 的包:

$ cd path/to/books
$ ginkgo bootstrap

例如:

$ cat path/to/books/books.go
package books

type Book struct {
    Title  string
    Author string
    Pages  int
}

func (b *Book) CategoryByLength() string {

    if b.Pages >= 300 {
        return "NOVEL"
    }

    return "SHORT STORY"
}

这将生成一个名为 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")
}

让我们分析一下:

  • Go允许我们在books包同目录下指定books_test包。使用books_test而不是books可以让我们不破坏books包的封装:你的测试需要导入books包并从外部访问它,就像导入其它任何包一样。这是进入包,测试其内部结构并进行更多行为测试的首选。当然,您可以选择不使用此功能 - 只需把package books_test改为package books即可。

  • 我们通过.ginkgogomega包导入了测试的顶级命名空间。如果您不想这样做,可以参考下面的Avoiding Dot Imports 部分。

  • TestBooks是一个testing测试。当您运行go testginkgo命令时,Go测试运行器将运行此功能。

  • RegisterFailHandler(Fail): Ginkgo 测试通过调用Fail(description string)功能来表示失败。我们使用RegisterFailHandler将此函数传递给Gomega。这是Ginkgo和Gomega之间的唯一连接点。

  • RunSpecs(t *testing.T, suiteDescription string)通知Ginkgo启动测试套件。如果您的任何specs失败,Ginkgo将自动使testing.T失败。

此时您可以运行您的套件:

$ ginkgo #or go test

=== RUN TestBootstrap

Running Suite: Books Suite
==========================
Random Seed: 1378936983

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: TestBootstrap (0.00 seconds)
PASS
ok      books   0.019s

给套件添加Specs

一个空的测试套件不是很有趣。虽然您可以开始直接将测试添加到books_suite_test.go中,但您可能更愿意将测试分成单独的文件(特别是对于包含多个文件的包)。让我们为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() {

})

让我们拆解分析一下:

  • 我们将ginkgogomega包导入顶级命名空间。虽然非常方便,但严格来说这并不是必需的。如果您想避免这种情况,请查看下面的Avoiding Dot Imports部分。
  • 类似地,我们导入books包,因为我们使用特殊的books_test包来将我们的测试与我们的代码隔离开来。

  • 方便起见,我们将books包导入命名空间。

  • 您可以通过编辑生成的测试文件来选择不使用这些选项。

  • 我们使用Ginkgo的Describe(text string, body func()) bool函数添加了顶级Describe容器。

  • var _ = ... 技巧允许我们在顶级评估Describe,而不必将其包装在func init() {}

Describe 中的功能将包含我们的Specs。现在让我们添加一些来测试从JSON中加载books

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为您的Specs设置状态。您可以使用It来指定单个Spec

  • 为了在BeforeEachIt之间共享状态,您使用闭包变量,通常在最相关的DescribeContext容器的顶部定义。

  • 我们使用Gomega的Expect语法来对CategoryByLength()方法产生期望值。

假设具有此行为的Book模型,运行测试将产生:

$ ginkgo # or go test
=== RUN TestBootstrap

Running Suite: Books Suite
==========================
Random Seed: 1378938274

Will run 2 of 2 specs

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

--- PASS: TestBootstrap (0.00 seconds)
PASS
ok      books   0.025s

成功!

将Specs标记为失败

虽然您通常希望使用匹配库(如Gomega)在您的Spec中进行断言,但Ginkgo提供了一个简单的全局Fail函数,允许您将Spec标记为Fail。只需调用:

Fail("Failure reason")

Ginkgo 将会处理其余的部分。

Fail(因此Gomega,因为它使用Fail)将为当前的spacepanic记录失败。这允许Ginkgo停止其轨道中的当前Spec - 没有后续的断言(或者任何代码)将被调用。通常情况下,Ginkgo将会补救这个Panic本身然后进行下一步测试。

然而,如果你的测试启用了goroutine调用Fail(或者,等效地,调用失败的Gomega断言),Ginkgo将没有办法补救由Fail引发的Panic.这将导致测试套件出现Panic,并且不会运行后续测试。要解决这个问题,你必须使用GinkgoRecover拯救Panic。这是一个例子:

It("panics in a goroutine", func(done Done) {
    go func() {
        defer GinkgoRecover()

        Ω(doSomething()).Should(BeTrue())

        close(done)
    }()
})

现在,如果doSomething返回false,Gomega将会调用Fail,这将会引起Panic,但是deferGinkgoRecover()将恢复所述Panic并防止测试套件爆炸。

有关Fail以及使用除Gomega之外的匹配器库的更多详细信息,请参阅使用其他匹配库部分。

Logging输出

Ginkgo提供了一个全局可用的io.Writer,名为GinkgoWriter,供您写入。GinkgoWriter在测试运行时聚合输入,并且只有在测试失败时才将其转储到stdout。当以详细模式运行时(ginkgo -vgo test -ginkgo.v),GinkgoWriter会立即将其输入重定向到stdout

当Ginkgo测试套件中断(通过^ C)时,Ginkgo将发出写入GinkgoWriter的任何内容。这样可以更轻松地调试卡住的测试。
当与--progress配对使用时将会特别有用,它指示Ginkgo在运行您的BeforeEachesItsAfterEaches等时向GinkgoWriter发出通知。

IDE 支持

Ginkgo用命令行运行最佳,ginkgo watch可以在检测到变化时轻松地在命令行上重新运行测试。

Sublime Text有一组Completions(仅使用Package Control来安装Ginkgo Completions)和VSCode(使用扩展安装程序并安装vscode-ginkgo)。
IDE作者可以将GINKGO_EDITOR_INTEGRATION环境变量设置为任何非空值,使专注的Spec能够显示覆盖范围。默认情况下,如果确定关注的spec不通过CI, Ginkgo 将会Fail,使用非零退出码。

results matching ""

    No results matching ""