Ginkgo 测试框架学习笔记
一、安装Ginkgo
$ go get github.com/onsi/ginkgo/ginkgo
$ go get github.com/onsi/gomega/...
然后会有一个ginkgo.exe安装在GOPATH路径的bin里

二、框架拆解
Ginkgo 提供了一些example
1、Bootstrapping a Suite
$ cd path/to/books
$ ginkgo bootstrap
package books_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestBooks(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Books Suite")
}
- 新建两个package books_test 进行测试,一个文件用于写测试用例,一个文件放启动测试套件的函数.
- 导入Ginkgo和Gomega包时在包名前加一个[.],这样可以直接使用包中的方法函数,不用再【包名.函数名】
- 执行文件的时候,可以用go test方法也可以用ginkgo
- RegisterFailHandler(Fail): ginkgo通过调用Fail(description string)函数来发出fail信号。我们用RegisterFailHandler() 将Fail函数传递给Gomega。RegisterFailHandler()是连接ginkgo和gomega的唯一途径。
- RunSpecs(t *testing.T, suiteDescription string):用于启动测试套件,如果任何一个specs失败,该套件则自动返回失败。
执行结果
$ 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
2、Adding Specs to a Suite
$ ginkgo generate book
package books_test
import (
"/path/to/books"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
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的【Describe(text string, body func() ) bool 】函数。使用【var _ = …】允许我们直接描述填充顶层的Describe() 不需要用【func init(){}】初始化包装它。
- 【Describe() {}】中的函数包含了我们的使用规范。
- 为了共享BeforeEach和It之间的状态,Ginkgo使用闭包函数变量来构建顶层的Describe()和Context()
- Ginkgo中使用【Descirbe()】和【Context()】来描述代码的测试行为,将一个或多个测试用例It归类。
- Ginkgo中使用【BeforceEach()】来为specs设置状态,执行每个测试用例It前执行该段代码,使用【It()】来指定单个spec,是测试用例的基本单位,即It中包含的代码就算一个测试用例。
- 使用Gomega中的【Expect()】函数来设置期望。
执行结果
$ 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
3、Marking Specs as Failed
虽然通常使用匹配器库(如Gomega)在规范中进行断言,但Ginkgo提供了一个简单的、全局的失败函数,它允许您将一个spec标记为失败。
Fail("Failure reason")
① Fail() 标志该测试用例运行结果为失败,并打印里面的信息。
② Fail和Gomega(Gomega也调用了Fail)将会把当前的Space和panic记录为失败。同时Ginkgo会停止当前进度中的spec,包括后续所有与该时间相关的断言和代码。
③ 通常Ginkgo会自动挽救这种panic,并进入下一段测试。
④ 如果测试启动的一个线程调用了Fail,或调用了Gomega的失败断言,那么Ginkgo没有办法挽救错误引起的panic。这会导致后续的测试内容中断。为了避免这种情况,需要使用【GinkgoRecover】来挽救panic。
Example:
It("panics in a goroutine", func(done Done) {
go func() {
defer GinkgoRecover()
Ω(doSomething()).Should(BeTrue())
close(done)
}()
})
4、Logging Output
Ginkgo提供了一个全局的io.Writter 叫作GinkgoWtiter。当测试运行时可以向GinkgoWriter进行输入,只有测试失败时,才会将它输出stdout。当运行在详细模式(ginkgo -v或go test -ginkgo.v) GinkgoWriter总是立即重定向它的输入到stdout。
当Ginkgo测试套件中断(通过 ^C),Ginkgo会将所有的内容写入GinkgoWriter。便于调试测试用例。
三、构建Specs
1、独立Specs:It()
可以向Describe() 或 Context() 中添加It 模块。
var _ = Describe("Book", func() {
It("can be loaded from JSON", func() {
book := NewBookFromJSON(`{
"title":"Les Miserables",
"author":"Victor Hugo",
"pages":1488
}`)
Expect(book.Title).To(Equal("Les Miserables"))
Expect(book.Author).To(Equal("Victor Hugo"))
Expect(book.Pages).To(Equal(1488))
})
})
2、Specify 别名
Specify和It功能完全一样,It属于其简写。 为了能够让测试用例Specs更容易阅读,Specify, PSpecify, XSpecify, 和 FSpecify 作为别名使用,应对It 不能作为自然语言读取的情况。
Describe("The foobar service", func() {
Context("when calling Foo()", func() {
Context("when no ID is provided", func() {
Specify("an ErrNoID error is returned", func() {
})
})
})
})
3、合并共用设置:BeforeEach()
使用BeforeEach() 共享模块的共用设置:
var _ = Describe("Book", func() {
var book Book
BeforeEach(func() {
book = NewBookFromJSON(`{
"title":"Les Miserables",
"author":"Victor Hugo",
"pages":1488
}`)
})
It("can be loaded from JSON", func() {
Expect(book.Title).To(Equal("Les Miserables"))
Expect(book.Author).To(Equal("Victor Hugo"))
Expect(book.Pages).To(Equal(1488))
})
It("can extract the author's last name", func() {
Expect(book.AuthorLastName()).To(Equal("Hugo"))
})
})
① BeforeEach在每个Specs之前运行,从而确保每个Specs都有原始的State 副本。使用闭包的函数变量(在本例中为var book book)共享测试用例的初始状态。您还可以通过AfterEach模块执行清理。
② Tips:当有多层嵌套关系时,BeforeEach会在包含该BeforeEach的 Describe下 所有的It执行前执行:
Describe("test",func(){
Context("test_c",func(){
BeforEarch(func(){
fmt.Println("this is a beforeEach")
})
It("test_I",func(){
fmt.Println("第一个IT")
})
})
Context("test_c2",func(){
It("test_I2",func(){
fmt.Println("第二个IT")
})
})
})
//输入结果是:
this is a beforeEach
第一个IT
this is a beforeEach
第二个IT
不管这个BeforeEach是在哪个Context下面,只要是在该Describe下的It(),都会执行该BeforeEach ,所以BeforeEach不要放在Context中,声明时要放在Describe下面.
通常我们还会在BeforeEach和AfterEach中加入断言。 这些断言可以判断在为测试用例准备State的时候有无发生错误。
4、通过容器整理测试用例Spec:Describe() and Context()
var _ = Describe("Book", func() {
var (
book Book
err error
)
BeforeEach(func() {
book, err = NewBookFromJSON(`{
"title":"Les Miserables",
"author":"Victor Hugo",
"pages":1488
}`)
})
Describe("loading from JSON", func() {
Context("when the JSON parses succesfully", func() {
It("should populate the fields correctly", func() {
Expect(book.Title).To(Equal("Les Miserables"))
Expect(book.Author).To(Equal("Victor Hugo"))
Expect(book.Pages).To(Equal(1488))
})
It("should not error", func() {
Expect(err).NotTo(HaveOccurred())
})
})
Context("when the JSON fails to parse", func() {
BeforeEach(func() {
book, err = NewBookFromJSON(`{
"title":"Les Miserables",
"author":"Victor Hugo",
"pages":1488oops
}`)
})
It("should return the zero-value for the book", func() {
Expect(book).To(BeZero())
})
It("should error", func() {
Expect(err).To(HaveOccurred())
})
})
})
Describe("Extracting the author's last name", func() {
It("should correctly identify and return the last name", func() {
Expect(book.AuthorLastName()).To(Equal("Hugo"))
})
})
})
① 使用Describe()来描述这段代码的行为,使用Context()来描述表达该行为在不同的环境下执行。在例子中,第一个测试行为Describe()是要导入json,测试用例有两种情景,一种是导入成功,一种是导入失败。
② 当Describe/Context 嵌套BeforeEach和AfterEach时,执行每一个It模块,都会执行一次BeforeEach和AfterEach,以确保每个Specs都处于原始状态。
③ 不要写嵌套写Context,只有在最后写测试用例It()时,才再前面写Context, 因为Context没有分割测试用例的能力,Describe可以分割,确定BeforeEach和AfterEach的执行范围.
通常,容器块中的代码应该是It块或BeforeEach/JustBeforeEach/JustAfterEach/AfterEach块,
或闭包变量声明。在容器块中断言通常是错误的。
在容器块中初始化闭包变量也是错误的。如果您的Its中的一个改变了该变量,随后的Its将接收改变后的值。
这是一个测试污染的案例,很难追踪。总是在每个块之前初始化变量。
③ 如果你想在运行时获得关于当前测试的信息,你可以使用CurrentGinkgoTestDescription()从任何It或BeforeEach/JustBeforeEach/JustAfterEach/AfterEach块。这个调用返回的CurrentGinkgoTestDescription具有关于当前运行的测试的各种信息,包括文件名、行号、It块中的文本以及周围容器块中的文本。
5、分离创建和配置:JustBeforeEach
JustBeforeEach() 模块在所有BeforeEach模块执行之后,It模块执行之前运行。所以,可以在BeforeEach()中创建配置参数,在JustBeforeEach()中进行导入和创建。
这样可以将It 执行前的条件配置关系更加多元化,减少重复代码。
# 未加入BeforeEach
BeforeEach(func() {
book, err = NewBookFromJSON(`{
"title":"Les Miserables",
"author":"Victor Hugo",
"pages":1488
}`)
})
# 加入JustBeforeEach
BeforeEach(func() {
json = `{
"title":"Les Miserables",
"author":"Victor Hugo",
"pages":1488
}`
})
JustBeforeEach(func() {
book, err = NewBookFromJSON(json)
})
Describe("loading from JSON", func() {
Context("when the JSON fails to parse", func() {
BeforeEach(func() {
json = `{
"title":"Les Miserables",
"author":"Victor Hugo",
"pages":1488oops
}`
})
It("should return the zero-value for the book", func() {
Expect(book).To(BeZero())
})
It("should error", func() {
Expect(err).To(HaveOccurred())
})
})
})
在不同的嵌套级别上,可以有多个JustBeforeEaches。
Ginkgo会先从外跑所有的BeforeEach,然后它会从外跑所有的JustBeforeEach。
虽然这个功能强大,但这可能会导致测试套件的混乱,所以要明智地嵌套使用JustBeforeEach。
6、全局设置和卸载:BeforeSuite 和AfterSuite
有时您希望在执行整个测试套件之前运行一些代码,或者完成整个测试套件之后运行一些代码。例如,可能需要启动和关闭外部数据库。
Ginkgo提供了BeforeSuite 和AfterSuite来实现这一点。
package books_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"your/db"
"testing"
)
var dbRunner *db.Runner
var dbClient *db.Client
func TestBooks(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Books Suite")
}
var _ = BeforeSuite(func() {
dbRunner = db.NewRunner()
err := dbRunner.Start()
Expect(err).NotTo(HaveOccurred())
dbClient = db.NewClient()
err = dbClient.Connect(dbRunner.Address())
Expect(err).NotTo(HaveOccurred())
})
var _ = AfterSuite(func() {
dbClient.Cleanup()
dbRunner.Stop()
})
① BeforeSuite函数在所有Specs运行前执行,如果BeforeSuite报错,则后续测试套件不会执行。
② AfterSuite函数在所有Specs运行后执行,不论测试是否失败。因为AfterSuite通常包含清理现有状态的代码,所以当你向运行中的测试套件发送一个中断信号(^C)时,Ginkgo也会执行AfterSuite。要中止AfterSuite,需要发送另一个中断信号。
③ BeforeSuite和AfterSuite都只能创建一次。
④ 并行运行时,运行每个进程的前后都会执行一次BeforeSuite和AfterSuite。
7、记录复杂的It :By()
作为一个测试规则,尽量让It() 、BeforeEach() 等描述内容简短且切中要点。在复杂测试流程中,使用By来提供辅助描述:
var _ = Describe("Browsing the library", func() {
BeforeEach(func() {
By("Fetching a token and logging in")
authToken, err := authClient.GetToken("gopher", "literati")
Exepect(err).NotTo(HaveOccurred())
err := libraryClient.Login(authToken)
Exepect(err).NotTo(HaveOccurred())
})
It("should be a pleasant experience", func() {
By("Entering an aisle")
aisle, err := libraryClient.EnterAisle()
Expect(err).NotTo(HaveOccurred())
By("Browsing for books")
books, err := aisle.GetBooks()
Expect(err).NotTo(HaveOccurred())
Expect(books).To(HaveLen(7))
By("Finding a particular book")
book, err := books.FindByTitle("Les Miserables")
Expect(err).NotTo(HaveOccurred())
Expect(book.Title).To(Equal("Les Miserables"))
By("Check the book out")
err := libraryClient.CheckOut(book)
Expect(err).NotTo(HaveOccurred())
books, err := aisle.GetBooks()
Expect(books).To(HaveLen(6))
Expect(books).NotTo(ContainElement(book))
})
})
By()中的字符串会传递给GinkgoWriter。如果测试成功,则不会有显示。测试失败时,可以打印出每个步骤直到失败前。使用ginkgo -v时,则无论成功失败都会打印详细信息。
四、The Spec Runner
1、 Pending Specs
您可以将单个Spec或容器标记为pending。这将阻止spec(或容器内的spec)运行。在Describe, Context, It, and Measure前面加一个P或X:
PDescribe("some behavior", func() { ... })
PContext("some scenario", func() { ... })
PIt("some assertion")
PMeasure("some measurement")
XDescribe("some behavior", func() { ... })
XContext("some scenario", func() { ... })
XIt("some assertion")
XMeasure("some measurement")
你不需要移除func(){…..} 当您将一个It或Measure标记为pending时。
Ginkgo会忽略字符串后面的任何参数。
默认情况下,Ginkgo会为每个未完成的Spec打印一个描述,
你可以通过设置--noisyPendings=false 来阻止它。
默认情况下,Ginkgo不会因为有待定Specs而失败。
您可以传递--failOnPending 来反转此行为。
2、focus Specs
F含义Focus,使用后表示只执行该模块包含的测试:
FDescribe("some behavior", func() { ... })
FContext("some scenario", func() { ... })
FIt("some assertion", func() { ... })
当里层和外层都存在Focus时,外层的无效,即下面代码只会执行B测试用例:
FDescribe("outer describe", func() {
It("A", func() { ... })
FIt("B", func() { ... })
})
本文是Ginkgo测试框架的学习笔记,涵盖了安装、框架解析、Specs构建以及Spec Runner等内容。讲解了如何使用Ginkgo进行Bootstrapping、Adding Specs、Before/BeforeEach等操作,并介绍了如何标记失败的测试用例和记录输出。
Ginkgo测试框架&spm=1001.2101.3001.5002&articleId=108666360&d=1&t=3&u=42a981a0dd6a40f4adcdd74c1fcde08f)
317

被折叠的 条评论
为什么被折叠?



