Ginkgo

从官网的介绍了解,Ginkgo是BDD(行为驱动)的测试框架,他最好和Gomega匹配器的库搭配使用,Ginkgo是设计成与匹配器无关。

Bootstrap

生成Suite件

  1. cd path/to/books
  2. $ ginkgo bootstrap

生成

  1. package books_test
  2. import (
  3. . "github.com/onsi/ginkgo"
  4. . "github.com/onsi/gomega"
  5. "testing"
  6. )
  7. func TestBooks(t *testing.T) {
  8. RegisterFailHandler(Fail)
  9. RunSpecs(t, "Books Suite")
  10. }

这会生成一个books_suite_test.go文件,这个是以package的维度生成一个test的入口类
RegisterFailHandler 会注册失败函数,注册给gomega使用
RunSpecs就是运行相应的case

包名:
生成的test文件的包名是books_test,提供了更好的封装性,在test文件中需要手动import原包books来进行使用,这个也是可选的,可以手动修改包名去掉_test后缀

Adding Specs to a Suite

  1. ginkgo generate book

生成一个book_test.go 文件

  1. package books_test
  2. import (
  3. "/path/to/books"
  4. . "github.com/onsi/ginkgo"
  5. . "github.com/onsi/gomega"
  6. )
  7. var _ = Describe("Book", func() {
  8. })

case

  1. var _ = Describe("Book", func() {
  2. var (
  3. longBook Book
  4. shortBook Book
  5. )
  6. BeforeEach(func() {
  7. longBook = Book{
  8. Title: "Les Miserables",
  9. Author: "Victor Hugo",
  10. Pages: 1488,
  11. }
  12. shortBook = Book{
  13. Title: "Fox In Socks",
  14. Author: "Dr. Seuss",
  15. Pages: 24,
  16. }
  17. })
  18. Describe("Categorizing book length", func() {
  19. Context("With more than 300 pages", func() {
  20. It("should be a novel", func() {
  21. Expect(longBook.CategoryByLength()).To(Equal("NOVEL"))
  22. })
  23. })
  24. Context("With fewer than 300 pages", func() {
  25. It("should be a short story", func() {
  26. Expect(shortBook.CategoryByLength()).To(Equal("SHORT STORY"))
  27. })
  28. })
  29. })
  30. })

使用 BeforeEach 设置specs的状态,使用 It 来描述一个单一的测试用例

  • 在闭包的最顶层定义变量,实现状态,变量共享
  • 使用Gomega的 Expect 语法来实现比较判断

Fail

直接将case标记为失败

  1. Fail("Failure reason")

在goroutine 中进行测试的时候需要注意使用GinkgoRecover()函数

  1. It("panics in a goroutine", func(done Done) {
  2. go func() {
  3. defer GinkgoRecover()
  4. Ω(doSomething()).Should(BeTrue())
  5. close(done)
  6. }()
  7. })

Logging Output

Ginkgo使用了一个GinkgoWriter 用于输出,GinkgoWriter会聚合input,只有在test失败的时候才会将结果进行输出。当使用ginkgo -v 或者 go test -ginkgo.v 。进行详细模式跑测试的时候,会将结果都输出

Spec语法

It

使用It来描述单一的spec

  1. var _ = Describe("Book", func() {
  2. It("can be loaded from JSON", func() {
  3. book := NewBookFromJSON(`{
  4. "title":"Les Miserables",
  5. "author":"Victor Hugo",
  6. "pages":1488
  7. }`)
  8. Expect(book.Title).To(Equal("Les Miserables"))
  9. Expect(book.Author).To(Equal("Victor Hugo"))
  10. Expect(book.Pages).To(Equal(1488))
  11. })
  12. })

Specify

  1. Describe("The foobar service", func() {
  2. Context("when calling Foo()", func() {
  3. Context("when no ID is provided", func() {
  4. Specify("an ErrNoID error is returned", func() {
  5. })
  6. })
  7. })
  8. })

BeforeEach

  1. var _ = Describe("Book", func() {
  2. var book Book
  3. BeforeEach(func() {
  4. book = NewBookFromJSON(`{
  5. "title":"Les Miserables",
  6. "author":"Victor Hugo",
  7. "pages":1488
  8. }`)
  9. })
  10. It("can be loaded from JSON", func() {
  11. Expect(book.Title).To(Equal("Les Miserables"))
  12. Expect(book.Author).To(Equal("Victor Hugo"))
  13. Expect(book.Pages).To(Equal(1488))
  14. })
  15. It("can extract the author's last name", func() {
  16. Expect(book.AuthorLastName()).To(Equal("Hugo"))
  17. })
  18. })

Describe&Context

  1. var _ = Describe("Book", func() {
  2. var (
  3. book Book
  4. err error
  5. )
  6. BeforeEach(func() {
  7. book, err = NewBookFromJSON(`{
  8. "title":"Les Miserables",
  9. "author":"Victor Hugo",
  10. "pages":1488
  11. }`)
  12. })
  13. Describe("loading from JSON", func() {
  14. Context("when the JSON parses succesfully", func() {
  15. It("should populate the fields correctly", func() {
  16. Expect(book.Title).To(Equal("Les Miserables"))
  17. Expect(book.Author).To(Equal("Victor Hugo"))
  18. Expect(book.Pages).To(Equal(1488))
  19. })
  20. It("should not error", func() {
  21. Expect(err).NotTo(HaveOccurred())
  22. })
  23. })
  24. Context("when the JSON fails to parse", func() {
  25. BeforeEach(func() {
  26. book, err = NewBookFromJSON(`{
  27. "title":"Les Miserables",
  28. "author":"Victor Hugo",
  29. "pages":1488oops
  30. }`)
  31. })
  32. It("should return the zero-value for the book", func() {
  33. Expect(book).To(BeZero())
  34. })
  35. It("should error", func() {
  36. Expect(err).To(HaveOccurred())
  37. })
  38. })
  39. })
  40. Describe("Extracting the author's last name", func() {
  41. It("should correctly identify and return the last name", func() {
  42. Expect(book.AuthorLastName()).To(Equal("Hugo"))
  43. })
  44. })
  45. })

在使用多层嵌套的时候,每一个BeforEach是从外到内一次执行

JustBeforeEach

**
在It的函数执行前,在所有的BeforeEach函数执行完成后执行

BeforeSuite AfterSuite

  1. var _ = BeforeSuite(func() {
  2. dbRunner = db.NewRunner()
  3. err := dbRunner.Start()
  4. Expect(err).NotTo(HaveOccurred())
  5. dbClient = db.NewClient()
  6. err = dbClient.Connect(dbRunner.Address())
  7. Expect(err).NotTo(HaveOccurred())
  8. })
  9. var _ = AfterSuite(func() {
  10. dbClient.Cleanup()
  11. dbRunner.Stop()
  12. })

By

  1. var _ = Describe("Browsing the library", func() {
  2. BeforeEach(func() {
  3. By("Fetching a token and logging in")
  4. authToken, err := authClient.GetToken("gopher", "literati")
  5. Exepect(err).NotTo(HaveOccurred())
  6. err := libraryClient.Login(authToken)
  7. Exepect(err).NotTo(HaveOccurred())
  8. })
  9. It("should be a pleasant experience", func() {
  10. By("Entering an aisle")
  11. aisle, err := libraryClient.EnterAisle()
  12. Expect(err).NotTo(HaveOccurred())
  13. By("Browsing for books")
  14. books, err := aisle.GetBooks()
  15. Expect(err).NotTo(HaveOccurred())
  16. Expect(books).To(HaveLen(7))
  17. By("Finding a particular book")
  18. book, err := books.FindByTitle("Les Miserables")
  19. Expect(err).NotTo(HaveOccurred())
  20. Expect(book.Title).To(Equal("Les Miserables"))
  21. By("Check the book out")
  22. err := libraryClient.CheckOut(book)
  23. Expect(err).NotTo(HaveOccurred())
  24. books, err := aisle.GetBooks()
  25. Expect(books).To(HaveLen(6))
  26. Expect(books).NotTo(ContainElement(book))
  27. })
  28. })

By 会通过GinkgoWriter输出内容,在case成功的情况下,不会输出,失败时会输出。By 还可以接收一个 函数,这个函数会被立即执行

跳过测试

编译期跳过测试,Ps: PDescribe 和 XDescribe 函数的效果是一样的

  1. PDescribe("some behavior", func() { ... })
  2. PContext("some scenario", func() { ... })
  3. PIt("some assertion")
  4. PMeasure("some measurement")
  5. XDescribe("some behavior", func() { ... })
  6. XContext("some scenario", func() { ... })
  7. XIt("some assertion")
  8. XMeasure("some measurement")

运行时通过Skip函数跳过测试

  1. It("should do something, if it can", func() {
  2. if !someCondition {
  3. Skip("special condition wasn't met")
  4. }
  5. // assertions go here
  6. })

Focused Specs

声明后只会跑这些case了

  1. FDescribe("some behavior", func() { ... })
  2. FContext("some scenario", func() { ... })
  3. FIt("some assertion", func() { ... })

Gomega

和Ginkgo结合,只需要注册相应的失败函数即可,通过bootstrap创建的suite类会直接注册好

  1. gomega.RegisterFailHandler(ginkgo.Fail)

有两种语法

  1. Ω(ACTUAL).Should(Equal(EXPECTED))
  2. Ω(ACTUAL).ShouldNot(Equal(EXPECTED))
  1. Expect(ACTUAL).To(Equal(EXPECTED))
  2. Expect(ACTUAL).NotTo(Equal(EXPECTED))
  3. Expect(ACTUAL).ToNot(Equal(EXPECTED))

单独使用gomega

  1. func TestFarmHasCow(t *testing.T) {
  2. g := NewGomegaWithT(t)
  3. f := farm.New([]string{"Cow", "Horse"})
  4. g.Expect(f.HasCow()).To(BeTrue(), "Farm should have cow")
  5. }

常见用法

测试panic

panic测试的函数需要是一个没有返回值的函数

  1. Expect(func() {
  2. conf.Image.GetStringFromConf(emptyMap, true)
  3. }).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