基于go的微服务搭建(四)- 用GoConvey做测试和mock
第四章:用GoConvey做测试和mock 我们应该怎样做微服务的测试?这有什么特别的挑战么.这节,我们将看下面几点:
因为这章不会改变核心服务代码,所以没有基测 微服务测试简介首先,你必须记住测试金字塔:
单元测试必须作为你集成,e2e的基础,验收测试更不容易开发和维护
代码完整代码 git checkout P4 介绍go的单元测试又go的作者设计的遵从语言习惯的模式.测试文件由命名来识别.如果我们想测试handler.go中的东西,我们创建文件handlers_test.go,在同一个文件夹下. 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用来作为断言的条件. > 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这个包 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我们创建下一个测试函数在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",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,200) account := model.Account{} json.Unmarshal(resp.Body.Bytes(),&account) So(account.Id,"123") So(account.Name,"Person_123") }) }) }) } 这段测试请求path/accounts/123,我们的mock实现了这个.在when中,我们断言http状态,反序列化Account结构,同时段验结果和我们的mock的结果相同. Convey("Given a HTTP request for /accounts/456","/accounts/456",req) Convey("Then the response should be a 404",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写第一个单元测试.同事用mock包帮我们模拟.下一节,我们会启动docker swarm并且部署我们的服务进swarm中 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |