自2015年開始,七牛工效團隊一直使用Go語言+Ginkgo的組合來編寫自動化測試用例,積累了大約5000+的數量。在使用和維護過程中,我們覺得Ginkgo的很多設計理念和功能非常贊,因此特分享給大家。
本篇不是該框架的入門指導。如果您也編寫或維護過大量自動化測試用例,希望能獲得一些共鳴.
BDD(Behavior Driven Development)
要說Ginkgo最大的特點,筆者認為,那就是對BDD風格的支持。比如:
Describe("delete app api", func() {
It("should delete app permanently", func() {...})
It("should delete app failed if services existed", func() {...})
It's about expressiveness。Ginkgo定義的DSL語法(Describe/Context/It)可以非常方便的幫助大家組織和編排測試用例。在BDD模式中,測試用例的標題書寫,要非常注意表達,要能清晰的指明用例測試的業務場景。只有這樣才能極大的增強用例的可讀性,降低使用和維護的心智負擔。
可讀性這一點,在自動化測試用例設計原則上,非常重要。因為測試用例不同於一般意義上的程序,它在絕大部分場景下,看起來都像是一段段獨立的方法,每個方法背后隱藏的業務邏輯也是細小的,不具通識性。這個問題在用例量少的情況下,還不明顯。但當用例數量上到一定量級,你會發現,如國能快速理解用例到底是能做什么的,真的非常重要。而這正是BDD能補足的地方。
不過還是要強調,Ginkgo只是提供對BDD模式的支持,你的用例最終呈現的效果,還是依賴你自己的書寫。
進程級並行,穩定高效
相應的我們知道,BDD框架,因為其DSL的深度嵌套支持,會存在一些共享上下文的資源,如此的話想做線程級的並發會比較困難。而Ginkgo巧妙的避開了這個問題,它通過在運行時,運行多個被測服務的進程,來達到真正的並行,穩定性大大提高。其使用姿勢也非常簡單,ginkgo -p
命令就可以。在實踐中,我們通常使用32核以上的服務器來跑集測,執行效率非常高。
這里有個細節,Ginkgo雖然並行執行測試用例,但其輸出的日志和測試報告格式,仍然是整齊不錯亂的,這是如何做到的呢?原來,通過源碼會發現,ginkgo CLI工具在並行跑用例時,其內部會起一個監聽隨機端口的本地服務器,來做不同進程之間的消息同步,以及日志和報告的聚合工作,是不是很巧妙?
其他的一些Tips
Ginkgo框架的功能非常強大,對常見測試場景的都有比較好的支持,即使是一些略顯復雜的場景,比如:
-
在平時的代碼中,我們經常會看到需要做異步處理的測試用例。但是這塊的邏輯如果處理不好,用例可能會因為死鎖或者未設置超時時間而異常卡住,非常的惱人。好在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)
-
針對分布式系統,我們在驗收一些場景時,可能需要等待一段時間,目標結果才生效。而這個時間會因為不同集群負載而有所不同。所以簡單的硬編碼來sleep一個固定時間,很明顯不合適。這種場景下若是使用Ginkgo對應的matcher庫Gomega的Eventually功能就非常的貼切,在大大提升用例穩定性的同時,最大可能的減少無用的等待時間。
-
筆者一直認為,自動化測試用例不應該僅僅是QA手中的工具,而應該盡可能多的作為業務驗收服務,輸出到CICD,灰度驗證,線上驗收等盡可能多的場景,以服務於整個業務線。同樣利用Ginkgo我們可以很容易做到這一點:
- CICD: 在定義suite時,使用
RunSpecWithDefaultReporters
方法,可以讓測試結果既輸出到stdout,還可以輸出一份Junit格式的報告。這樣就可以通過類似Jenkins的工具方便的呈現測試結果,而不用任何其他的額外操作。 - TaaS(Test as a Service): 通過
ginkgo build
或者原生的go test -c
命令,可以方便的將測試用例,編譯成package.test的二進制文件。如此的話,我們就可以方便的進行測試服務分發。典型的,如交付給SRE同學,輔助其應對線上灰度場景下的測試驗收。所以在測試用例的組織上,這里有個小建議,過往我會看到有同學會習慣一個目錄就定義一個suite文件,這樣編譯出的二進制文件就非常多,不利於分發。所以建議不要定義太多的suite,可以一條產品就一個suite入口,其他的用例包通過_
導入進來。比如:
- CICD: 在定義suite時,使用
另外,值得說道的是,Ginkgo框架在提供強大功能和靈活性的同時,有些地方也需要使用者特別留心:
DescribeTable
功能是對TableDriven模式的友好支持,但它的原理是通過Entry
在用例執行之前,通過反射機制來自動生成It
方法,所以如果期望類似BeforeEach+It
的原生組合來使用BeforeEach+Entry
的話,可能在值類型的變量傳遞上,會不符合預期。其實,相較於DescribeTable+Entry
的模式,我個人更傾向於通過方法+多個It
的原生組合來寫用例,雖然代碼量顯得有點多,但是用例表達的邏輯主題會更清晰,可讀性較高。類似如下:
- Ginkgo CLI的focus和skip命令非常好用,能夠靈活的指定想執行或者排除的測試用例。不過要注意的是,focus和skip傳入的是正則表達式,而適配這個正則的,是組成用例的所有的Container標題的組合(Suite+Describe+Context+It), 這些標題從外到里拼接成的完整字符串,所以使用時當注意。
都有誰在用Ginkgo?
Ginkgo的官方文檔非常詳細,非常利於使用。另外,我們看到著名的容器雲項目Kubernetes也是使用Ginkgo框架來編寫其e2e測試用例。
最后,如果您也使用Go語言來編寫測試用例,不妨嘗試下Ginkgo。
Contact me ?
Email: jinsdu@outlook.com
Blog: http://www.cnblogs.com/jinsdu/
Github: https://github.com/CarlJi