什么是ginkgo:
ginkgo是一个用go写的BDD(Behavior Driven Development)的测试框架,一般用于Go服务的集成测试。
ginkgo的特点
BDD的代码风格
Describe("delete app api",func(){
It("should delete app permanently",func(){...})
It("should delete app failed if services existed", func(){...})
)
这段ginkgo的心得体会摘自七牛云测试团队,大卡尔的使用体会
Ginkgo定义的DSL语法(Describe/Context/It)可以非常方便的帮助大家组织和编排测试用例。
在BDD模式中,测试用例的标题书写,要非常注意表达,要能清晰的指明用例测试的业务场景。
只有这样才能极大的增强用例的可读性,降低使用和维护的心智负担。
可读性这一点,在自动化测试用例设计原则上,非常重要。因为测试用例不同于一般意义上的程序,
它在绝大部分场景下,看起来都像是一段段独立的方法,每个方法背后隐藏的业务逻辑也是细小的,不具通识性。
这个问题在用例量少的情况下,还不明显。但当用例数量上到一定量级,你会发现,如果能快速理解用例到底是能做什么的,真的非常重要。
而这正是BDD能补足的地方。
不过还是要强调,Ginkgo只是提供对BDD模式的支持,你的用例最终呈现的效果,还是依赖你自己的书写。
ginkgo安装
$ go get github.com/onsi/ginkgo/ginkgo
$ go get github.com/onsi/gomega/...
ginkgo常用模块
常用的10个:It、Context、Describe、BeforeEach、AfterEach、JustBeforeEach、BeforeSuite、AfterSuite、By、Fail
It模块
It是测试例的基本单位,即It包含的代码就算一个测试用例
It可以理解维测试代码的最小单元
Context和Describe 模块
Context和Describe的功能都是将一个或多个测试例归类
一个describe可以包含多个context,一个context可以包含多个IT模块
BeforeEach模块
BeforeEach是每个测试例执行前执行该段代码
比如创建数据库连接就可以使用BeforeEach ,每个BeforeEach只在当前域内起作用。
执行顺序是同一层级的顺序执行,不同层级的从外层到里层以此执行。类似与 全局变量和局部变量的区别
AfterEach模块
AfterEach是每个测试例执行后执行该段代码
比如销毁数据库连接,一般用于测试例执行完成后进行数据清理,也可以用于结果判断
JustBeforeEach,
JustBeforeEach是在BeforeEach执行之后,测试例执行之前执行
测试用例执行前的一些前置操作
BeforeSuite模块
BeforeSuite是在该测试集执行前执行,即该文件夹内的测试例执行之前
BeforeSuite和AfterSuite写在_suite_test.go文件中,会在所有测试例执行之前和之后执行
如果BeforeSuite执行失败,则这个测试集都不会被执行
Tip:使用C中断执行时,AfterSuite仍然会被执行,需要再使用一次C中断
AfterSuite模块
AfterSuite是在该测试集执行后执行,即该文件夹内的测试例执行完后
By模块
By是打印信息,内容只能是字符串,只会在测试例失败后打印,一般用于调试和定位问题
Fail模块
Fail是标志该测试例运行结果为失败,并打印里面的信息
Specify模块
Specify和It功能完全一样,It属于其简写
ginkgo的三个标志
F、X和P,可以用在Describe、Context、It等任何包含测试例的模块
F含义Focus,使用后表示只执行该模块包含的测试
Tip:当里层和外层都存在Focus时,外层的无效,即下面代码只会执行B测试用例
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
})
ginkgo 并发设置
ginkgo -p 使用默认并发数
ginkgo -nodes=N 自己设定并发数
默认并发数是用的参数runtime.NumCPU()值,即逻辑CPU个数,大于4时,用runtime.NumCPU()-1
如果需要显示实时日志,需要添加 -stream参数
并发执行时打印的日志是汇总后经过合并处理再打印的,所以看起来比较规范,
每个测试例的内容也都打印在一起,但时不实时,如果需要实时打印,加-stream参数,缺点是每个测试例日志交叉打印
ginkgo goroutine设置
在平时的代码中,我们经常会看到需要做异步处理的测试用例。但是这块的逻辑如果处理不好,用例可能会因为死锁或者未设置超时时间而异常卡住,非常的恼人。好在Ginkgo专门提供了原生的异步支持,能大大降低此类问题的风险。类似用法:
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)
0.2的单位是秒
ginkgo性能测试
使用这个模块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)
ginkgo命令行使用
打开电脑终点是使用 ginkgo help即可
比较常用的命令
ginkgo bootstrap <FLAGS> 创建测试关联文件
ginkgo generate <filename(s)> 生成测试代码模版
ginkgo nodot 更新测试套件中的nodot声明
ginkgo convert /path/to/package 转变包文件格式为ginkgo可识别的格式
ginkgo unfocus (or ginkgo blur) 递归地取消当前目录下所有集中测试的焦点
ginkgo version 输出版本信息
ginkgo初级实战
1.本地gopath/src下创建一个项目books
2. 创建 books.go 文件
package books
import (
"encoding/json"
"fmt"
)
type Book struct {
Title string
Author string
Pages int
}
func (b Book) AuthorLastName() (interface{}, interface{}) {
fmt.Println("AuthorLastName")
return b.Author,b.Title
}
func (b Book) CategoryByLength() string{
if b.Pages >300{
return "NOVEL"
} else if b.Pages<100 {
return "SMALL STORY"
} else {
return "SHORT STORY"
}
}
func NewBookFromJSON(json json.Decoder) error {
book:=json.Decode(json)
return book
}
- 如果go版本高于1.11 引入go moudle的原因 , 创建go.mod包管理文件 go mod init
当前文件夹下会生成两个文件 go.mod go.sum
->cat go.mod
module books
go 1.14
require (
github.com/onsi/ginkgo v1.12.0
github.com/onsi/gomega v1.9.0
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a // indirect
)
->cat go.sum
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
->
- 创建测试关联文件
进入项目当前路径下执行 ginkgo bootstrap 项目下会创建一个 books_suite_test.go文件,为测试用例入口
package books_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestBooks(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Books Suite")
}
- 创建测试模版文件 ginkgo generate books 当前的包名字叫 books所以 创建出来的测试模版文件叫
books_test.go
package books_test
import (
. "/path/to/books"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Book", func() {
})
这是一个通用的测试模版,我们需要根据自己的测试需求编写对应的测试规格
修改后的如下
package books_test
import (
. "books"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Book", func() {
var (
longBook Book
shortBook Book
smallBook Book
)
BeforeEach(func() {
longBook = Book{
Title: "Les Miserables",
Author: "Victor Hugo",
Pages: 1488,
}
shortBook = Book{
Title: "Fox In Socks",
Author: "Dr. Seuss",
Pages: 240,
}
smallBook = Book{
Title: "go program",
Author: "caochunhui",
Pages: 20,
}
})
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"))
})
})
Context("With fewer than 100 pages", func() {
It("should be a small story", func() {
Expect(smallBook.CategoryByLength()).To(Equal("SMALL STORY"))
})
})
})
})
当前这个测试代码中我们写了三个用例,分别测试books源代码中CategoryByLength功能所对应的三个逻辑,即根据书页的多少判断书本的类型,测试代码只需要覆盖所有的逻辑即可。
6. 执行测试代码
这块有三种执行办法
go test -v
ginkgo
还可以 ginko build 编译成二进制,执行二进制
可以看到输出了测试结果
7.输出JUnit测试报告
ginkgo支持输出junit的测试报告 ,这块需要修改一下套件文件books_suite_test.go 将原始的
RunSpecs(t, “Books Suite”) 直接运行,修改为生成report报告运行方式
func TestBooks(t *testing.T) {
RegisterFailHandler(Fail)
//RunSpecs(t, "Books Suite")
junitReporter := reporters.NewJUnitReporter("junit.xml")
RunSpecsWithDefaultAndCustomReporters(t, "Books Suite", []Reporter{junitReporter})
}
然后重新执行ginkgo ,当前项目路径下即可生成 junit.xml文件
文件内容记录了测试报告运行情况
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Books Suite" tests="3" failures="0" errors="0" time="0">
<testcase name="Book Categorizing book length With more than 300 pages should be a novel" classname="Books Suite" time="5.0482e-05"></testcase>
<testcase name="Book Categorizing book length With fewer than 300 pages should be a short story" classname="Books Suite" time="1.111e-06"></testcase>
<testcase name="Book Categorizing book length With fewer than 100 pages should be a small story" classname="Books Suite" time="7.7e-07"></testcase>
</testsuite>
一个简单的ginkgo测试框架体验到此结束,后续更新高级玩法。
参考文档
http://onsi.github.io/ginkgo/
https://s0godoc0org.icopy.site/github.com/onsi/ginkgo
https://blog.csdn.net/goodboynihaohao/article/details/79392500
转载自原文链接, 如需删除请联系管理员。
原文链接:ginkgo:初学者指南,转载请注明来源!