Ginkgo 入门
在本节中,我们将介绍安装Ginkgo、Gomega和ginkgo CLI。我们启动一个Ginkgo套件,编写我们的第一个规格,并运行它。
安装Ginkgo
Ginkgo使用go模块。将Ginkgo添加到项目中,假设有一个go.mod文件设置,只需go install即可::
go install github.com/onsi/ginkgo/v2/ginkgo
go get github.com/onsi/gomega/...
这将获取Ginkgo并在$GOBIN下安装ginkgo可执行文件——您会希望在 $PATH上安装它。它还获取了核心Gomega匹配器库及其支持库集。请注意,当前支持的Ginkgo主版本是v2。
现在,应该能够在命令行运行ginkgo version,并看到ginkgo CLI发出版本号。
注意: 必须确保您安装的ginkgo命令行版本与您的go.mod文件中的ginkgo版本相同。你可以通过从包中运行go install github.com/onsi/ginkgo/v2/ginkgo来做到这一点。
升级Ginkgo
要升级Ginkgo,请运行:
go get github.com/onsi/ginkgo/v2
go install github.com/onsi/ginkgo/v2/ginkgo
选择一个特定的版本:
go get github.com/onsi/ginkgo/v2@v2.m.p
go install github.com/onsi/ginkgo/v2/ginkgo
支持策略
Ginkgo坚持语义版本控制——其目的是在'2.m.p'线上没有突破性的变化,新功能作为次要版本发布,bug修复作为补丁发布(修复永远不会被移植)。我们努力维护这一政策,但例外情况(虽然罕见,通常很小)是可能的,特别是对于全新/新兴的功能。
Ginkgo的当前版本保证与Go发布政策中注明的当前支持的Go版本兼容,即N和N-1主要版本。
第一个Ginkgo套件
Ginkgo连接到Go现有的testing基础设施。这意味着Ginkgo 规格存在于*_test.go文件中,就像标准的go测试一样。但是,不使用func TestX(t*testing.t){}编写测试,而是使用Ginkgo和Gomega DSL。
我们将给定包中的Ginkgo规格集合称为Ginkgo套件;我们使用spec这个词来谈论套件中包含的单个Ginkgo测试。尽管它们在功能上可以互换,但我们将使用“spec”而不是“test”来区分Ginkgo测试和传统的testing测试。
在大多数Ginkgo套件中,只有一个TestX函数——Ginkgo的入口点。让我们启动一个Ginkgo套件,看看它是什么样子的。
启动套件
假设有一个名为books的包,想在其中添加一个Ginkgo套件。要启动套件运行:
# cd path/to/books
# ginkgo bootstrap
Generating ginkgo test suite bootstrap for books in:
books_suite_test.go
这将生成一个名为books_suite_test.go的文件。进入 books 目录,包含:
package books_test
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"testing"
)
func TestBooks(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Books Suite")
}
让我们来分析一下:
首先,ginkgo bootstrap生成一个新的测试文件,并将其放在 books_test 包中。这个小细节实际上是非常重要的,所以让我们简单地讨论一下Go是如何组织代码的,特别是测试包。
心理模型:Go模块、包和测试
Go代码被组织成模块。模块通常与版本控制的存储库相关联,并由一系列版本控制的包组成。每个包通常与模块文件树中的单个目录相关联,该目录包含一系列源代码文件。当测试Go代码时,包的单元测试通常位于与包相同的目录中,并命名为*_test.go。Ginkgo遵循这一惯例。也可以构造仅由*_test.go文件组成的仅测试包。例如,模块级集成测试通常位于它们自己的仅测试包目录中,并将模块的各种包作为一个整体进行测试。由于Ginkgo只是建立在Go现有的测试基础设施之上,所以这个用例也得到了支持和鼓励。
通常,Go只允许一个包驻留在给定的目录中(在我们的例子中,它将是一个名为books的包)。然而,这条规则有一个例外:以 _test结尾的包被允许与被测试的包位于同一目录中。这样做指示Go将包的测试套件编译为一个独立的包。这意味着测试套件将不能访问books包的内部,并且需要import books包来访问它的外部接口。Ginkgo默认将套件设置为*_test包,以鼓励您只测试包的外部行为,而不是其内部实现细节。
好的,回到我们的启动文件。在package books_test声明之后,我们通过执行.导入将ginkgo和gomega包导入测试的顶级命名空间点导入。由于Ginkgo和Gomega是DSL,这使得测试更容易阅读。如果你愿意,可以通过ginkgo bootstrap --nodot来避免点导入。在本文档中,我们将假设点导入。
接下来我们定义一个单独的testing测试:func TestBooks(t *testing.T)。这是Ginkgo的入口点——当你运行go test或ginkgo时,go测试运行器会运行这个函数。
在TestBooks函数中有两行:
RegisterFailHandler(Fail) 是连接Ginkgo和Gomega的一行胶水代码。如果我们要避免点导入,这将是
作为gomega.RegisterFailHandler(ginkgo.Fail)-我们在这里做的是告诉我们的匹配器库(Gomega)在检测到失败时调用哪个函数(Ginkgo的Fail)。
最后,RunSpecs()调用告诉Ginkgo启动测试套件,并将*testing.T实例和套件的描述传递给它。应该只调用一次RunSpecs,可以让Ginkgo为你担心调用*testing.T。
有了bootstrap文件,现在可以使用ginkgo命令运行你的套件:
ginkgo
Running Suite: Books Suite - path/to/books
==========================================================
Random Seed: 1634745148
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 ran 1 suite in Xs
Test Suite Passed
在幕后,ginkgo只是在调用go test。虽然可以运行go test而不是ginkgo”CLI,但Ginkgo有几个只能通过ginkgo访问的功能。通常建议用户接受ginkgo CLI,并将其视为测试工具链中的一流成员。
好的,我们已经成功设置并运行了我们的第一个套件。当然,那个套件是空的,这不太有趣。让我们添加一些规格。
向套件添加规格
虽然可以直接将所有规格添加到books_suite_test.go中,但通常更喜欢将规格放在单独的文件中。如果有需要测试的多个文件的包,则尤其如此。假设我们的book包包括一个book.go模型,我们想测试它的行为。我们可以生成一个测试文件,如下所示:
# ginkgo generate book
Generating ginkgo test for Book in:
book_test.go
这将生成一个名为book_test.go的测试文件,其中包含:
package books_test
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"path/to/books"
)
var _ = Describe("Books", func() {
})
与bootstrap套件文件一样,该测试文件位于单独的books_test包中,并使用点导入ginkgo和gomega。由于我们正在测试books的外部接口,因此Ginkgo添加了一个import语句来将books包拉入测试中。
Ginkgo然后添加一个空的顶级Describe容器节点。Describe是Ginkgo DSL的一部分,它具有描述和闭包函数Description(“book”,func(){})生成一个容器,其中包含描述Books行为的规格。
默认情况下,Go不允许您在文件的顶级调用裸函数。Ginkgo通过让其节点DSL函数返回一个要丢弃的值来解决这个问题。这允许我们在顶层编写
var _ = Describe(...),以满足Go的顶层策略。
现在,让我们添加一些规格来描述我们的“图书”模型对“图书”进行分类的能力:
var _ = Describe("Books", func() {
var foxInSocks, lesMis *books.Book
BeforeEach(func() {
lesMis = &books.Book{
Title: "Les Miserables",
Author: "Victor Hugo",
Pages: 2783,
}
foxInSocks = &books.Book{
Title: "Fox In Socks",
Author: "Dr. Seuss",
Pages: 24,
}
})
Describe("Categorizing books", func() {
Context("with more than 300 pages", func() {
It("should be a novel", func() {
Expect(lesMis.Category()).To(Equal(books.CategoryNovel))
})
})
Context("with fewer than 300 pages", func() {
It("should be a short story", func() {
Expect(foxInSocks.Category()).To(Equal(books.CategoryShortStory))
})
})
})
})
这里发生了很多事情,我们来分析一下。
Ginkgo广泛使用闭包,使我们能够构建一个描述性的规格层次结构。此层次结构使用三种节点构建:
我们使用容器节点,如Describe和Contextt,来组织我们正在分层测试的代码的不同方面。在本例中,我们描述了我们的图书模型在两种不同背景下对图书进行分类的能力——大书"With more than 300 pages" 和小书 "With fewer than 300 pages"的行为。
我们使用设置节点,如BeforeEach ,来设置我们的规格状态。在本例中,我们实例化了两个新书模型:lesMis 和foxInSocks。
最后,我们使用像It 这样的主题节点来编写一个规格,该规格对被测主题做出断言。在本例中,我们确保book.Category() 根据书的长度返回正确的类别 enum 。我们使用Gomega的Equal匹配器和Expect语法来做这些断言。可以在这里了解更多关于Gomega-),这些文档中使用的示例应该是不言自明的。
容器、设置和主题节点形成一个规格树。Ginkgo使用树来构建一个扁平的规格列表,其中每个规格可以有多个设置节点,但只有一个主题节点。
因为有两个主题节点,Ginkgo将确定要运行的两个规格。对于每个规格,Ginkgo将运行附加到任何相关设置节点的闭包,然后运行附加到主题节点的闭包。为了在设置节点和主题节点之间共享状态,我们定义了像lesMis和foxInSocks这样的闭包变量。这是一种常见的模式,也是 Ginkgo组织测试的主要方式。
假设一个具有此行为的book.Book模型,我们可以运行测试:
# ginkgo
Running Suite: Books Suite - path/to/books
==========================================================
Random Seed: 1634748172
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
Ginkgo ran 1 suite in Xs
Test Suite Passed
成功!我们已经编写并运行了第一个Ginkgo套件。从这里开始,我们可以在迭代代码时扩展我们的测试套件。
接下来的部分将深入探讨Ginkgo为编写和运行规格提供的许多机制。

浙公网安备 33010602011771号