如何进行Golang的单元测试--过程与总结

最近一周写一个比较复杂的业务模块,越写到后面真心越心虚。操作越来越复杂了,代码也逐渐凌乱了起来。比如一个接口,传入的是一个比较复杂的大json,我需要解析这个大json,然后根据json中字段进行增删改查,调用第三方服务等操作。告诉前端接口已经完成的时候,总是没有什么底气。说实话,在写PHP的时候,我确实很少写单元测试,大都是对着页面进行一波一波的调试,现在想想,一个是懒,还有一个是确实PHP是不需要编译的语言,没有编译时间,测试-修正,整个流程非常短。但是这次是一个比较大的GoLang项目,如果还是按照“编译-起服务-调用-调整代码-编译-起服务-调用-...” 这种循环来做调试,真是会疯了的。所以我能静下心好好研究研究如何写Golang的单元测试了。

如何进行Golang的单元测试--过程与总结


数据库怎么办?

这个是第一个需要思考的问题。这个问题和语言无关。一旦有数据库操作,就需要考虑如何在测试用例中如何处理数据库操作。我想了想,无外乎两种做法,一种是直接mock数据库的返回对象。另外一种,是搭建一个测试DB,然后灌入假数据,进行测试。

这两种方式我选择了后一种。有两个理由:

1、mock数据库返回数据是一个比灌入DB数据更为复杂的逻辑,数据库返回的数据根据sql各种各样,要想在每个环节都写好数据库操作返回,倒不如我直接伪造一些数据来的方便。
2、mock数据库返回会丢失model层的测试逻辑,当然如果你是轻model层,整个model就只有一个orm,这个可能就不是理由了。
所以,我操作的第一步,从线上把数据库表结构copy一份到我本地vagrant的mysql中。
必须要注意:你的测试数据库和测试代码最好是同一个机器上,否则每跑一个测试用例,消耗的时间非常大,你的测试体验也不会太好。

第三方请求怎么办?

我的代码逻辑中也有一些第三方调用,调用其他服务。当然这里也有同样的两种办法,一种是直接在本地测试环境搭建第三方服务,另外一种是mock第三方服务的返回数据。这里我选择了mock数据的方式。基本想法是因为我这个测试毕竟不是一种全链路测试,测试的主体还是我的服务,我的服务基本上只包含服务+DB,如果要搭建第三方服务,这就有点舍本逐末的感觉了。
好了,如何mock第三方服务呢?
查了下golang中mock的包有两个比较出名,一个是golang官网出品的golang/mock,另外一个是monkey(https://github.com/bouk/monkey)。两个相比之下,我感觉golang/mock是师出有名,但是不如monkey好用,monkey属于黑科技,使用修改函数指针的方式进行mock函数。我想了想,实用第一位,投入了monkey的怀抱。
基本使用代码如下:

// mock路网接口
guard := monkey.Patch(lib.Curl, func(trace *lib.TraceContext, CurlType, urlString string, data url.Values, addToken bool) ([]byte, error) {
return []byte("{["10010":"后厂村路"}"]), nil
})
defer guard.Unpatch()

将lib.Curl整个函数给mock了,并且在函数结束后修改mock的函数,保证不影响其他测试用例。

配置文件怎么办?

web服务一般都会有读取配置的代码,我的服务是读取一个参数config=base.json来进行配置的读取的。go test中是没有办法给test的代码传递参数的,(我看网上的一些文章说有个-args的参数,但是我在go1.11版本中确实没有看到这个参数)。于是我只能选择使用环境变量的方式。在运行go test的时候,在最开头的部分设置下当前这个go test的环境变量CONFIG_PATH,然后修改下我的初始化配置文件的代码,允许传入参数进行配置文件的读取。
大概代码如下:
在运行go test的时候设置环境变量:

CONFIG_PATH=/home/vagrant/foo/conf/yejianfeng/base.json go test foo/signaledit/... -v -test.run TestGetGroups

测试环境的初始化配置文件逻辑:

package test
import (
..
)
var HasSetup = false

// signalEdit初始化,只调用一次
func SetUpSignalEdit() {
if HasSetup == false {
gin.SetMode(gin.TestMode)
confPath := os.Getenv("CONFIG_PATH")
// 获取环境变量
commonlib.Init(confPath, "")    // 初始化配置文件
conf.ParseLocalConfig()
db.InitDB()
HasSetup = true
}
DestroyTestData(db.EditDB)
CreateTestData(db.EditDB)
}

web怎么进行单元测试?

关于这个,httptest这个包提供给我们想要的逻辑了,网上的文章也一大堆了。使用起来也是很方便,就没有什么好说的了。

router := gin.New()
jc := Controller{}

// 灯组模型表获取信息
router.GET("/group/all", jc.GroupAll)
...

//构建返回值
w := httptest.NewRecorder()
//构建请求
r, _ := http.NewRequest("GET", "/group/all?logic_junction_id=test_junction", nil)

//调用请求接口
router.ServeHTTP(w, r)
resp := w.Result()
body, _ := ioutil.ReadAll(resp.Body)

上一页12下一页


留言