基於go的微服務搭建(四)- 用GoConvey做測試和mock

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

第四章:用GoConvey做測試和mock

我們應該怎樣做微服務的測試?這有什麼特別的挑戰麼.這節,我們將看下面幾點:

  • 單元測試
  • 用Goconey寫行為模式的單元測試
  • 介紹mocking技巧

因為這章不會改變核心服務代碼,所以沒有基測

微服務測試簡介

首先,你必須記住測試金字塔:

單元測試必須作為你整合,e2e的基礎,驗收測試更不容易開發和維護
微服務有一些不同的測試痛點,構建軟體架構用到的準則和做測試一樣.微服務的單元測試和傳統的不太一樣,我們會在這裡講一下.
總之,我強調幾點:

  • 做正常地單元測試-你的商業邏輯,驗證器等等不因為在微服務上運行而不同.
  • 整合的部分,例如和其他服務溝通,發送資訊,使用資料庫,這些必須用依賴注入的方法來設計,這樣才能用上mock
  • 許多微服務的特性:設定檔,其他服務的溝通,彈力測試等.花很多的時間才能做一點測試.這些測試可以做整合測試,你把docker容器整體的做測試.這樣性價比會比較高.

代碼

完整代碼

git checkout P4

介紹

go的單元測試又go的作者設計的遵從語言習慣的模式.測試檔案由命名來識別.如果我們想測試handler.go中的東西,我們建立檔案handlers_test.go,在同一個檔案夾下.
我們從悲觀測試開始,斷言404,當我們請求不存在的地址

package serviceimport (        . "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,這會運行這個檔案夾下的測試.

Mocking

上面的測試不需要mock,因為我們不會用到GetAccount裡面的DBClient.對於好的請求,我們需要返回結果,我們就需要mock用戶端來串連BoltDb.有許多mocking的方法.我最喜歡的是stretchr/testify/mock這個包
在/dbclient檔案夾下,建立mockclient.go來實現IBoltClient介面:

package dbclientimport (        "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/testifytype 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.

編寫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中

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.