当前位置: 首页 > 工具软件 > Ginkgo > 使用案例 >

Ginkgo使用指南

颜修为
2023-12-01

简介

Ginkgo

Ginkgo是一个基于Go语言的BDD测试框架,一般用于Go服务的集成测试。

说明

本文的内容部分来自Ginkgo网站,部分来自使用过程中的经验总结。
Ginkgo网址:http://onsi.github.io/ginkgo/
源码链接:https://github.com/onsi/ginkgo

实操

  • 1.安装Ginkgo

    go get github.com/onsi/ginkgo/ginkgo
    go get github.com/onsi/gomega/… 附加的库,配合使用,下一篇会详细讲解
    配置环境变量,加到$GOPATH/bin或$PATH

  • 2.新建

  • 创建一个测试文件夹如example,进入文件夹,执行命令ginkgo bootstrap生成模版文件,文件名是example_suite_test.go,里面有入口函数
  • 执行ginkgo generate example,example可以不写,默认是当前文件夹名称,生成测试例模板文件example_test.go
  • 加_test后缀是为了和当前文件夹内已有代码做区分
  • example_test.go代码中默认会import当前文件夹

  • 3.模块

    常用的10个:It、Context、Describe、BeforeEach、AfterEach、JustBeforeEach、BeforeSuite、AfterSuite、By、Fail

  • It是测试例的基本单位,即It包含的代码就算一个测试用例
  • Context和Describe的功能都是将一个或多个测试例归类
  • BeforeEach是每个测试例执行前执行该段代码
  • AfterEach是每个测试例执行后执行该段代码
  • JustBeforeEach是在BeforeEach执行之后,测试例执行之前执行
  • BeforeSuite是在该测试集执行前执行,即该文件夹内的测试例执行之前
  • AfterSuite是在该测试集执行后执行,即该文件夹内的测试例执行完后
  • By是打印信息,内容只能是字符串,只会在测试例失败后打印,一般用于调试和定位问题
  • Fail是标志该测试例运行结果为失败,并打印里面的信息
  • 还有一个Specify和It功能完全一样,It属于其简写

  • 4.使用

var _ = Describe("Book", func() {
    var (
        book Book
        err error
        json string
    )

    BeforeEach(func() {
        json = `{
            "title":"Les Miserables",
            "author":"Victor Hugo",
            "pages":1488
        }`
    })

    JustBeforeEach(func() {
        book, err = NewBookFromJSON(json)
    })

    AfterEach(func() {
        By("End One Test")
    })

    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() {
                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() {
                if err != nil {
                    Fail("This Case Failed")
                }
            })
        })
    })

    Describe("Extracting the author's last name", func() {
        It("should correctly identify and return the last name", func() {
            Expect(book.AuthorLastName()).To(Equal("Hugo"))
        })
    })
})
  • 可以看到,首先定义了全局变量book、err和json。
  • 五个测试例分成两大类,由两个Describe区分,第一类又分成两小类,用Context做区分。每个It包含的就是一个测试用例。
  • 由两个BeforeEach,每个BeforeEach只在当前域内起作用。执行顺序是同一层级的顺序执行,不同层级的从外层到里层以此执行。AfterEach该规则相反。
  • AfterEach一般用于测试例执行完成后进行数据清理,也可以用于结果判断
  • 尽量不要要var里面给变量赋值,因为每次执行测试用例都有可能改变全局变量的值,会对后面的测试例产生影响,写在BeforeEach中比较合适
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和AfterSuite写在_suite_test.go文件中,会在所有测试例执行之前和之后执行
  • 如果BeforeSuite执行失败,则这个测试集都不会被执行
  • Tip:使用^C中断执行时,AfterSuite仍然会被执行,需要再使用一次^C中断

  • 5.标志

    有三个:F、X和P,可以用在Describe、Context、It等任何包含测试例的模块

  • F含义Focus,使用后表示只执行该模块包含的测试
FDescribe("outer describe", func() {
    It("A", func() { ... })
    It("B", func() { ... })
})

Tip:当里层和外层都存在Focus时,外层的无效,即下面代码只会执行B测试用例

FDescribe("outer describe", func() {
    It("A", func() { ... })
    FIt("B", func() { ... })
})
  • P的含义是Pending,即不执行,用法和F一样,规则的外层的生效
  • X和P的含义一样
    还有一个跳过测试例的方式是在代码中加Skip
It("should do something, if it can", func() {
    if !someCondition {
        Skip("special condition wasn't met")
    }

    // assertions go here
})
条件满足时,会跳过该测试用例
  • 6.并发

    ginkgo -p 使用默认并发数
    ginkgo -nodes=N 自己设定并发数

  • 默认并发数是用的参数runtime.NumCPU()值,即逻辑CPU个数,大于4时,用runtime.NumCPU()-1

  • 并发执行时打印的日志是汇总后经过合并处理再打印的,所以看起来比较规范,每个测试例的内容也都打印在一起,但时不实时,如果需要实时打印,加-stream参数,缺点是每个测试例日志交叉打印

  • 7.goroutine

It("should post to the channel, eventually", func(done Done) {
    c := make(chan string, 0)

    go DoSomething(c)
    Expect(<-c).To(ContainSubstring("Done!"))
    close(done)
}, 0.2)
  • Ginkgo检测到Done类型参数,就会自动设置超时时间,就是后面那个0.2,单位是秒

  • 8.DesctibeTable用法

    有时候很多测试例除了数据部分其他都是相同的,写很多类似的It会很繁琐,于是有Table格式出现

package table_test

import (
    . "github.com/onsi/ginkgo/extensions/table"

    . "github.com/onsi/ginkgo"
    . "github.com/onsi/gomega"
)

var _ = Describe("Math", func() {
    DescribeTable("the > inequality",
        func(x int, y int, expected bool) {
            Expect(x > y).To(Equal(expected))
        },
        Entry("x > y", 1, 0, true),
        Entry("x == y", 0, 0, false),
        Entry("x < y", 0, 1, false),
    )
})

等同于

package table_test

import (
    . "github.com/onsi/ginkgo"
    . "github.com/onsi/gomega"
)

var _ = Describe("Math", func() {
    Describe("the > inequality",
        It("x > y", func() {
            Expect(1 > 0).To(Equal(true))
        })

        It("x == y", func() {
            Expect(0 > 0).To(Equal(false))
        })

        It("x < y", func() {
            Expect(0 > 1).To(Equal(false))
        })
    )
})


  • 9.生成JUnit测试报告

一般生成Junit的XML测试报告
func TestFoo(t *testing.T) {
    RegisterFailHandler(Fail)
    junitReporter := reporters.NewJUnitReporter("junit.xml")
    RunSpecsWithDefaultAndCustomReporters(t, "Foo Suite", []Reporter{junitReporter})
}


  • 10.测试例性能

使用Measure模块
Measure("it should do something hard efficiently", func(b Benchmarker) {
    runtime := b.Time("runtime", func() {
        output := SomethingHard()
        Expect(output).To(Equal(17))
    })

    Ω(runtime.Seconds()).Should(BeNumerically("<", 0.2), "SomethingHard() shouldn't take too long.")

    b.RecordValue("disk usage (in MB)", HowMuchDiskSpaceDidYouUse())
}, 10)

该测试例会运行10次,并打印出执行性能数据

• [MEASUREMENT]
Suite
    it should do something hard efficiently

    Ran 10 samples:
    runtime:
      Fastest Time: 0.01s
      Slowest Time: 0.08s
      Average Time: 0.05s ± 0.02s

    disk usage (in MB):
      Smallest: 3.0
       Largest: 5.2
       Average: 3.9 ± 0.4
 类似资料: