第四章:用GoConvey做测试和mock
我们应该怎样做微服务的测试?这有什么特别的挑战么.这节,我们将看下面几点:
- 单元测试
- 用Goconey写行为模式的单元测试
- 介绍mocking技巧
因为这章不会改变核心服务代码,所以没有基测
微服务测试简介
首先,你必须记住测试金字塔:
单元测试必须作为你集成,e2e的基础,验收测试更不容易开发和维护
微服务有一些不同的测试难点,构建软件架构用到的准则和做测试一样.微服务的单元测试和传统的不太一样,我们会在这里讲一下.
总之,我强调几点:
- 做正常地单元测试-你的商业逻辑,验证器等等不因为在微服务上运行而不同.
- 集成的部分,例如和其他服务沟通,发送信息,使用数据库,这些必须用依赖注入的方法来设计,这样才能用上mock
- 许多微服务的特性:配置文件,其他服务的沟通,弹力测试等.花很多的时间才能做一点测试.这些测试可以做集成测试,你把docker容器整体的做测试.这样性价比会比较高.
代码
完整代码
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,这会运行这个文件夹下的测试.
Mocking
上面的测试不需要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.
编写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的结果相同.
我喜欢Goconvey因为这种"Given-when-then"的方式很容易读
我们也请求一个悲观地址/accounts/456,断言会得到http404:
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这种用行为测试方式写的单元测试并不是每个人都喜欢.有许多其他的测试框架.你能搜索到很多.
如果我们看测试金字塔上面,我们会想写集成测试,或者最后的验收测试.我们之后会启动真正的boltDb,之后来讲一讲集成测试.也许会用go docker的远程api和写好的boltdb镜像
总结
这部分我们用goconvey写第一个单元测试.同事用mock包帮我们模拟.下一节,我们会启动docker swarm并且部署我们的服务进swarm中
原文链接:https://www.f2er.com/go/187163.html