ginkgo:初學者指南

什麼是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
}
  1. 如果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=
->
  1. 創建測試關聯文件
    進入項目當前路徑下執行 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")
}
  1. 創建測試模版文件 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章