Ginkgo
从官网的介绍了解,Ginkgo是BDD(行为驱动)的测试框架,他最好和Gomega匹配器的库搭配使用,Ginkgo是设计成与匹配器无关。
Bootstrap
生成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")
}
这会生成一个books_suite_test.go文件,这个是以package的维度生成一个test的入口类
RegisterFailHandler 会注册失败函数,注册给gomega使用
RunSpecs就是运行相应的case
包名:
生成的test文件的包名是books_test,提供了更好的封装性,在test文件中需要手动import原包books来进行使用,这个也是可选的,可以手动修改包名去掉_test后缀
Adding Specs to a Suite
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() {
})
case
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"))
})
})
})
})
使用 BeforeEach
设置specs的状态,使用 It
来描述一个单一的测试用例
- 在闭包的最顶层定义变量,实现状态,变量共享
- 使用Gomega的
Expect
语法来实现比较判断
Fail
直接将case标记为失败
Fail("Failure reason")
在goroutine 中进行测试的时候需要注意使用GinkgoRecover()函数
It("panics in a goroutine", func(done Done) {
go func() {
defer GinkgoRecover()
Ω(doSomething()).Should(BeTrue())
close(done)
}()
})
Logging Output
Ginkgo使用了一个GinkgoWriter 用于输出,GinkgoWriter会聚合input,只有在test失败的时候才会将结果进行输出。当使用ginkgo -v 或者 go test -ginkgo.v 。进行详细模式跑测试的时候,会将结果都输出
Spec语法
It
使用It来描述单一的spec
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))
})
})
Specify
Describe("The foobar service", func() {
Context("when calling Foo()", func() {
Context("when no ID is provided", func() {
Specify("an ErrNoID error is returned", func() {
})
})
})
})
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"))
})
})
Describe&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"))
})
})
})
在使用多层嵌套的时候,每一个BeforEach是从外到内一次执行
JustBeforeEach
**
在It的函数执行前,在所有的BeforeEach函数执行完成后执行
BeforeSuite AfterSuite
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()
})
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输出内容,在case成功的情况下,不会输出,失败时会输出。By 还可以接收一个 函数,这个函数会被立即执行
跳过测试
编译期跳过测试,Ps: PDescribe 和 XDescribe 函数的效果是一样的
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")
运行时通过Skip函数跳过测试
It("should do something, if it can", func() {
if !someCondition {
Skip("special condition wasn't met")
}
// assertions go here
})
Focused Specs
声明后只会跑这些case了
FDescribe("some behavior", func() { ... })
FContext("some scenario", func() { ... })
FIt("some assertion", func() { ... })
Gomega
和Ginkgo结合,只需要注册相应的失败函数即可,通过bootstrap创建的suite类会直接注册好
gomega.RegisterFailHandler(ginkgo.Fail)
有两种语法
Ω(ACTUAL).Should(Equal(EXPECTED))
Ω(ACTUAL).ShouldNot(Equal(EXPECTED))
Expect(ACTUAL).To(Equal(EXPECTED))
Expect(ACTUAL).NotTo(Equal(EXPECTED))
Expect(ACTUAL).ToNot(Equal(EXPECTED))
单独使用gomega
func TestFarmHasCow(t *testing.T) {
g := NewGomegaWithT(t)
f := farm.New([]string{"Cow", "Horse"})
g.Expect(f.HasCow()).To(BeTrue(), "Farm should have cow")
}
常见用法
测试panic
panic测试的函数需要是一个没有返回值的函数
Expect(func() {
conf.Image.GetStringFromConf(emptyMap, true)
}).Should(Panic())
文档参考
http://onsi.github.io/ginkgo/
http://onsi.github.io/gomega/
TestMain的使用
https://medium.com/goingogo/why-use-testmain-for-testing-in-go-dafb52b406bc