Golang項目的測試實踐
最近有一個項目,鏈路涉及了4個服務。最核心的是一個配時服務。要如何對這個項目進行測試,保證輸出質量,是最近思考和實踐的重點。這篇就說下最近這個實踐的過程總結。html
測試金字塔
按照Mike Cohn提出的「測試金字塔」概念,測試分爲4個層次mysql
最下面是單元測試,單元測試對代碼進行測試。再而上是集成測試,它對一個服務的接口進行測試。繼而是端到端的測試,我也稱他爲鏈路測試,它負責從一個鏈路的入口輸入測試用例,驗證輸出的系統的結果。再上一層是咱們最經常使用的UI測試,就是測試人員在UI界面上根據功能進行點擊測試。git
單元測試
對於一個Golang寫的服務,單元測試已是很方便了。咱們在寫一個文件,函數的時候,能夠直接在須要單元測試的文件旁邊增長一個_test.go的文件。然後直接使用 go test
直接跑測試用例就能夠了。github
通常單元測試,最直接的衡量標準就是代碼覆蓋率。web
單元測試通常測試的對象是一個函數,一個類。sql
這個部分已經有不少實踐例子了,就沒什麼好聊的。shell
集成測試
思考和需求
對於一個服務,會提供多個接口,那麼,測試這些接口的表現就是集成測試最重要的目標了。只有經過了集成測試,咱們的這個服務纔算是有保障。數據庫
手頭這個配時項目,對外提供的是一系列HTTP服務,基本上代碼是以MVC的形式架構的。在思考對它的集成測試過程當中,我但願最終能作到下面幾點:json
首先,我但願我手上這個配時服務的集成測試是自動化的。最理想的狀況下,我能調用一個命令,直接將全部case都跑一遍。瀏覽器
其次,衡量集成測試的達標指標。這個糾結過一段時間,是否須要有衡量指標呢?仍是直接全部case經過就行?咱們的服務,輸入比較複雜,並非簡單的1-2個參數,是一個比較複雜的json。那麼這個json的構造有各類各樣的。須要實現寫一些case,可是怎麼保證個人這些case是否是有漏的呢?這裏仍是須要有個衡量指標的,最終我仍是選擇用代碼覆蓋率來衡量個人測試達標狀況,可是這個代碼覆蓋率在MVC中,我並不強制要求全部層的全部代碼都要覆蓋住,主要是針對Controller層的代碼。controller層主要是負責流程控制的,須要保證全部流程分支都能走到。
而後,我但願集成測試中有完善的測試概念,主要是TestCase, TestSuite,這裏參考了JUnit的一些概念。TestCase是一個測試用例,它提供測試用例啓動和關閉時候的注入函數,TestSuite是一個測試套件,表明的是一系列相似的測試用例集合,它也帶測試套件啓動和關閉時候的注入函數。
最後,可視化需求。我但願這個測試結果很友好,能有一個可視化的測試界面,我能很方便知道哪一個測試套件,哪一個測試用例中的哪一個斷言失敗了。
集成測試實踐
Golang 只有_test.go的測試,其中的每一個Test_XXX至關因而TestCase的概念,也沒有提供測試case啓動,關閉執行的注入函數,也沒有TestSuite的概念。首先我須要使用 Golang 的test搭建一個測試架子。
集成測試和單元測試不同,它不屬於某個文件,集成測試可能涉及到多個文件中多個接口的測試,因此它須要有一個單獨的文件夾。它的目錄結構我是這麼設計的:
suites
存放測試套件
suites/xxx
這裏存放測試套件,測試套件文件夾須要包含下列文件:
before.go存放有
- SetUp() 函數,這個函數在Suite運行以前會運行
- Before() 函數,這個函數在全部Case運行以前運行
after.go存放有
- TearDown() 函數,這個函數在Suite運行以後會運行
- After() 函數,這個函數在Suite運行以後運行
run_test.go文件
這個文件是testsuite的入口,代碼以下:
package adapt import "testing" import . "github.com/smartystreets/goconvey/convey" func TestRunSuite(t *testing.T) { SetUp() defer TearDown() Convey("初始化", t, nil) runCase(t, NormalCasePEE001) runCase(t, PENormalCase01) runCase(t, PENormalCase04) runCase(t, PENormalCase11) runCase(t, PENormalCase13) runCase(t, PENormalCase14) runCase(t, NormalCasePIE001) runCase(t, NormalCasePIE002) runCase(t, NormalCase01) runCase(t, NormalCase02) runCase(t, NormalCase07) runCase(t, NormalCase08) runCase(t, NormalCasePIN003) runCase(t, NormalCasePIN005) runCase(t, NormalCasePIN006) runCase(t, NormalCasePIN015) } func runCase(t *testing.T, testCase func(*testing.T)) { Before() defer After() testCase(t) }
envionment
初始化測試環境的工具
當前我這裏面存放了初始化環境的配置文件和db的建表文件。
report
存放報告的地址
代碼覆蓋率須要額外跑腳本
在tester目錄下運行:
sh coverage.sh
會在report下生成coverage.out和coverage.html,並自動打開瀏覽器
引入Convey
關於可視化的需求。
我引入了Convey這個項目,http://goconvey.co/ 。第一次看到這個項目,以爲這個項目的腦洞真大。
下面可了勁的誇一誇這個項目的優勢:
斷言
首先它提供了基於原裝go test的斷言框架;提供了Convey和So兩個重要的關鍵字,還提供了 Shouldxxx等一系列很好用的方法。它的測試用例寫下來像是這個樣子:
package package_name import ( "testing" . "github.com/smartystreets/goconvey/convey" ) func TestIntegerStuff(t *testing.T) { Convey("Given some integer with a starting value", t, func() { x := 1 Convey("When the integer is incremented", func() { x++ Convey("The value should be greater by one", func() { So(x, ShouldEqual, 2) }) }) }) }
很清晰明瞭,而且超讚的是不少參數都使用函數封裝起來了,go中的 := 和 = 的問題能很好避免了。而且不要再絞盡腦汁思考tmp1,tmp2這種參數命名了。(由於都已經分散到Convey語句的func中了)
Web界面
其次,它提供了一個很讚的Web平臺,這個web平臺有幾個點我很是喜歡。首先它有一個case編輯器
什麼叫好的測試用例實踐? 我認爲這個編輯器徹底體現出來了。寫一個完整的case先考慮流程和斷言,生成代碼框架,而後咱們再去代碼框架中填寫具體的邏輯。這種實踐步驟很好解決了以前寫測試用例思想偷懶的問題,特別是斷言,基本不會因爲偷懶而少寫。
其次它提供很讚的測試用例結果顯示頁面: 很贊吧,哪一個case錯誤,哪一個斷言問題,都很清楚顯示出來。
還有,goconvey能監控你運行測試用例的目錄,當目錄中有任何文件改動的時候,都會從新跑測試用例,而且提供提醒
這個真是太方便了,能夠在每次保存的時候,都知道當前寫的case是否有問題,能直接提升測試用例編寫的效率。
TestSuite初始化
Web服務測試的環境是個很大問題。特別是DB依賴,這裏不一樣的人有不一樣的作法。有使用model mock的,有使用db的。這裏個人經驗是:集成測試儘可能使用真是DB,可是這個DB應該是私有的,不該該是多我的共用一個DB。
因此個人作法,把須要初始化的DB結構使用sql文件導出,放在目錄中。這樣,每一個人想要跑這一套測試用例,只須要搭建一個mysql數據庫,倒入sql文件,就能夠搭建好數據庫環境了。其餘的初始化數據等都在TestSuite初始化的SetUp函數中調用。
關於保存測試數據環境,我這裏有個小貼士,在SetUp函數中實現 清空數據庫+初始化數據庫 ,在TearDown函數中不作任何事情。這樣若是你要單獨運行某個TestSuite,能保持最後的測試數據環境,有助於咱們進行測試數據環境測試。
TestCase編寫
在集成測試環境中,TestCase編寫調用HTTP請求就是使用正常的 httptest包,其使用方式沒有什麼特別的。
代碼覆蓋率
goconvey有個小問題,測試覆蓋率是根據運行goconvey的目錄計算的,不能額外設置,可是go test是提供的。因此代碼覆蓋率我還額外寫了一個shell腳本
#!/bin/bash go test -coverpkg xxx/controllers/... -coverprofile=report/coverage.out ./... go tool cover -html=report/coverage.out -o report/coverage.html open report/coverage.html
主要就是使用converpkg參數,把代碼覆蓋率限制在controller層。
集成測試總結
這套搭建實踐下來,對接口的代碼測試有底不少了,也測試出很多controller層面的bug。
端到端測試
這個是測試金字塔的第二層了。
關於端到端的測試,個人理解就是全鏈路測試。從整個項目角度來看,它屬於一個架構的層次了,須要對每一個服務有必定的改造和設計。這個測試須要保證的是整個鏈路流轉是按照預期的。
好比個人項目的鏈路經過了4個服務,一個請求可能在多個服務之間進行鏈路調用。可是這個項目特別的是,這些服務並不都是一個語言的。如何進行測試呢?
理想的端到端測試個人設想是這樣的,測試人員經過postman調用最上游的服務,構造不一樣的請求參數和case,有的case其實可能沒法通到最下游,那麼就須要有一個全鏈路日誌監控系統,在這個系統能夠看到這個請求在各個服務中的流轉狀況。全鏈路日誌監控系統定義了一套tag和一個traceid,要求全部服務在打日誌的時候帶上這個traceid,和當前步驟的tag,日誌監控系統根據這些日誌,在頁面上能很好反饋出這個鏈路。
而後測試人員每一個case,就根據返回的traceid,去日誌中查找,而且確認鏈路中的tag是否都全齊。
關於如何在各個服務中傳遞traceid,這個不少微服務監控的項目中都已經說過了,我也是同樣的作法,在http的header頭中增長這個traceId。
關於打日誌的地方,其實有不少地方均可以打日誌,可是我只建議在失敗的地方+請求的地方打上tag日誌,而且是由調用方進行tag日誌記錄,這樣主要是能把請求和返回都記錄,方便調試,查錯等問題。
UI測試
這個目前仍是讓測試人員手動進行點擊。這種方式看起來確實比較low,可是貌似也是目前大部分互聯網公司的測試方法了。
總結
這幾周主要是在集成測試方面作了一些實踐,有一些想法和思路,因此拿出來進行了分享,確定還有不少不成熟的地方沒有考慮到,歡迎評論留言討論。
測試是一個費時費力的工做,大多數狀況下,業務的迭代速度估計都不容許作很詳細的測試。可是對於複雜,重要的業務,強烈建議這四層的測試都能作到,這樣代碼上線纔能有所底氣。