第四章:用GoConvey作測試和mockgit
咱們應該怎樣作微服務的測試?這有什麼特別的挑戰麼.這節,咱們將看下面幾點:github
由於這章不會改變核心服務代碼,因此沒有基測docker
首先,你必須記住測試金字塔:數據庫
單元測試必須做爲你集成,e2e的基礎,驗收測試更不容易開發和維護
微服務有一些不一樣的測試難點,構建軟件架構用到的準則和作測試同樣.微服務的單元測試和傳統的不太同樣,咱們會在這裏講一下.
總之,我強調幾點:json
完整代碼api
git checkout P4
go的單元測試又go的做者設計的聽從語言習慣的模式.測試文件由命名來識別.若是咱們想測試handler.go中的東西,咱們建立文件handlers_test.go,在同一個文件夾下.
咱們從悲觀測試開始,斷言404,當咱們請求不存在的地址架構
package service import ( . "github.com/smartystreets/goconvey/convey" "testing" "net/http/httptest" ) func TestGetAccountWrongPath(t *testing.T) { Convey("Given a HTTP request for /invalid/123", t, func() { req := httptest.NewRequest("GET", "/invalid/123", nil) resp := httptest.NewRecorder() Convey("When the request is handled by the Router", func() { NewRouter().ServeHTTP(resp, req) Convey("Then the response should be a 404", func() { So(resp.Code, ShouldEqual, 404) }) }) }) }
這個測試顯示"Given-when-then"(若是-當-推斷)的模式.咱們也用httptest包,咱們用它來聲明請求的object也用作回覆的object用來做爲斷言的條件.
去accountservice下運行他:框架
> go test ./... ? github.com/callistaenterprise/goblog/accountservice [no test files] ? github.com/callistaenterprise/goblog/accountservice/dbclient [no test files] ? github.com/callistaenterprise/goblog/accountservice/model [no test files] ok github.com/callistaenterprise/goblog/accountservice/service 0.012s
./...會運行當前文件夾和全部子文件夾下的測試文件.咱們也能夠進入service文件夾下go test,這會運行這個文件夾下的測試.函數
上面的測試不須要mock,由於咱們不會用到GetAccount裏面的DBClient.對於好的請求,咱們須要返回結果,咱們就須要mock客戶端來鏈接BoltDb.有許多mocking的方法.我最喜歡的是stretchr/testify/mock這個包
在/dbclient文件夾下,建立mockclient.go來實現IBoltClient接口:微服務
package dbclient import ( "github.com/stretchr/testify/mock" "github.com/callistaenterprise/goblog/accountservice/model" ) // MockBoltClient is a mock implementation of a datastore client for testing purposes. // Instead of the bolt.DB pointer, we're just putting a generic mock object from // strechr/testify type MockBoltClient struct { mock.Mock } // From here, we'll declare three functions that makes our MockBoltClient fulfill the interface IBoltClient that we declared in part 3. func (m *MockBoltClient) QueryAccount(accountId string) (model.Account, error) { args := m.Mock.Called(accountId) return args.Get(0).(model.Account), args.Error(1) } func (m *MockBoltClient) OpenBoltDb() { // Does nothing } func (m *MockBoltClient) Seed() { // Does nothing }
MockBoltClient如今能夠做爲咱們能夠編寫的mock.向上邊那樣,咱們隱式的定義了全部的函數,實現IBoltClient接口.
若是你不喜歡這樣的mock方法,能夠看一下mockery,他能夠產生任何go接口的mock
QueryAccount函數裏有點奇怪.但這就是testify的作法,這樣能讓咱們有一個全面的內部控制的mock.
咱們建立下一個測試函數在handlers_test.go中:
func TestGetAccount(t *testing.T) { // Create a mock instance that implements the IBoltClient interface mockRepo := &dbclient.MockBoltClient{} // Declare two mock behaviours. For "123" as input, return a proper Account struct and nil as error. // For "456" as input, return an empty Account object and a real error. mockRepo.On("QueryAccount", "123").Return(model.Account{Id:"123", Name:"Person_123"}, nil) mockRepo.On("QueryAccount", "456").Return(model.Account{}, fmt.Errorf("Some error")) // Finally, assign mockRepo to the DBClient field (it's in _handlers.go_, e.g. in the same package) DBClient = mockRepo Convey("Given a HTTP request for /accounts/123", t, func() { req := httptest.NewRequest("GET", "/accounts/123", nil) resp := httptest.NewRecorder() Convey("When the request is handled by the Router", func() { NewRouter().ServeHTTP(resp, req) Convey("Then the response should be a 200", func() { So(resp.Code, ShouldEqual, 200) account := model.Account{} json.Unmarshal(resp.Body.Bytes(), &account) So(account.Id, ShouldEqual, "123") So(account.Name, ShouldEqual, "Person_123") }) }) }) }
這段測試請求path/accounts/123,咱們的mock實現了這個.在when中,咱們斷言http狀態,反序列化Account結構,同時段驗結果和咱們的mock的結果相同.
我喜歡Goconvey由於這種"Given-when-then"的方式很容易讀
咱們也請求一個悲觀地址/accounts/456,斷言會獲得http404:
Convey("Given a HTTP request for /accounts/456", t, func() { req := httptest.NewRequest("GET", "/accounts/456", nil) resp := httptest.NewRecorder() Convey("When the request is handled by the Router", func() { NewRouter().ServeHTTP(resp, req) Convey("Then the response should be a 404", func() { So(resp.Code, ShouldEqual, 404) }) }) })
跑一下.
> go test ./... ? github.com/callistaenterprise/goblog/accountservice [no test files] ? github.com/callistaenterprise/goblog/accountservice/dbclient [no test files] ? github.com/callistaenterprise/goblog/accountservice/model [no test files] ok github.com/callistaenterprise/goblog/accountservice/service 0.026s
全綠!goconvey有一個GUI能在咱們每次保存文件時自動執行全部測試.我不細講了,這裏看一下代碼覆蓋度報告:
goconvey這種用行爲測試方式寫的單元測試並非每一個人都喜歡.有許多其餘的測試框架.你能搜索到不少.
若是咱們看測試金字塔上面,咱們會想寫集成測試,或者最後的驗收測試.咱們以後會啓動真正的boltDb,以後來說一講集成測試.也許會用go docker的遠程api和寫好的boltdb鏡像
這部分咱們用goconvey寫第一個單元測試.同事用mock包幫咱們模擬.下一節,咱們會啓動docker swarm而且部署咱們的服務進swarm中