Go项目实战–数据Dao层代码的单元测试实战
Dao的单元测试
讲到数据库的单元测试,一般有那么几个流派
专门准备一个独立的数据库,单元测试时让所有测试用例读写这个独立的数据库,它的优点是单测真的去读写数据库啦,缺点嘛也显而易见,一个项目的数据库不是光有表就行,还得准备测试数据,这个搞起来就有点麻烦,尤其是关联性强的数据,造起来更麻烦。让项目在单元测试时访问内存数据库,它的优缺点其实跟上个差不多。采用sqlmock类的工具,对Dao要执行的SQL作出预期匹配,同时Mock SQL查询要返回的数据,保证Dao方法内部的逻辑正常执行。我们这里采用的是第三个流派,用 sqlmock 方式来做数据库Dao的单元测试,本节的内容大纲主要如下:
图片
这里我们会用到DataDog家开发的go-sqlmock这个工具,先来安装一下它:
安装过程如下:
图片
单元测试入口TestMain的设置
我们计划在 UserDao 和 OrderDao 中找几个典型的方法来做单元测试的实战,这里我们先在新建test/dao/user_test.go,创建完之后还不能马上开始写测试用例,我们再来做一下dao层单元测试的基础工作。
在TestMain方法中初始化go-sqlmock ,这样整个dao下的测试用例就都能使用它了,TestMain是在当前package下最先运行的一个函数,无论你运行哪个测试用例TestMain都会先被Go调用,所以它常用于测试基础组件的初始化。
我们的TestMain的代码如下:
这里我们创建一个 go-sqlmock 的数据库连接 和 mock对象,mock对象管理 db 预期要执行的SQL,具体初始化中各个参数的作用,直接看我上面代码里的注视吧。
因我我们项目里Dao使用的数据库连接在包外不可访问,所以我在这里给项目dao层里加了 SetDBMasterConn,SetDBSlaveConn两个方法把我们原本的数据库连接替换成了sqlmock的数据库连接。
基础设置完成后,接下来我们分别找Dao的Insert、Update、Select操作来展示怎么给他们做单元测试。
Insert 操作的单元测试
首先给UserDao的CreateUser方法做单元测试,它是用户注册接口的逻辑中会用到的Dao方法,其定义如下:
这里就不再对CreateUser这个方法里都是什么做展开了,大家直接看项目代码吧,它的单元测试如下:
这里我们首先自己初始化了一个CreateUser会用到的数据userInfo和passwordHash,然后使用 ExpectExec 指定预期要执行的SQL以及预期返回的结果。
这里我来说明一下sqlmock 默认使用 sqlmock.QueryMatcherRegex 作为默认的SQL匹配器,该匹配器使用mock.ExpectQuery 和 mock.ExpectExec 的参数作为正则表达式与真正执行的SQL语句进行匹配,如果使用QueryMatcherEqual 作为匹配器的话,那么我们写预期SQL时就要写完整的SQL了。
我推荐用默认的匹配器就行,因为接下来的WithArgs中我们还要给SQL的 ? 占位符提供参数值,这个参数值如果数量或者类型匹配不上的话,单测依然是无法通过的。
WillReturnResult(sqlmock.NewResult(1, 1)) 这行的意思是SQL执行后返回的 lastInsertId 是 1, 受影响行数也是 1。
拿到结果之后我们再做assert断言,判断结果是否符合预期。符合预期则通过,不符合的话测试用例会失败。大家可以自己尝试修改一下这个用例看它执行失败的效果。
Select 查询的单元测试
关于SQL查询的单元测试,和上面的区别是我们会Mock返回的结果集,这里我们拿的是OrderDao的GetUserOrders做的单元测试,代码如下。
这里我用 ExpectQuery 指定了两个预期要执行的SQL是为什么呢?因为GetUserOrders方法即返回了用户订单列表还返回了数据分页用的totalRaws变量,大家可以试试把它删掉看看这个单元测试能不能执行成功,这里我可以告诉你结果会成功但又没完全成功,会有一条Warning警告,报告出有一个执行的SQL没有做预期匹配。
执行单元测试时可以用上面我教的命令,也可以用IDE自带的测试按钮跑来跑这个测试用例。
Update操作的单元测试
Update操作的单元测试于Insert操作的类似,我们选用OrderDao的UpdateOrderStatus 方法来做单元测试。
这里的AnyTime是咱们自定义的一个类型
其实在使用SQL完全匹配模式时才必须用它,因为参数提供的Time.Now()做为UpdatedAt的时间,这与SQL执行时真正的UpdateAt时间是有很小的差异的,这个时候我们可以提供AnyTime做为更新时间,这样sqlmock在做预期SQL和实际SQL的匹配时,遇到了AnyTime类型的预期值,就会按照这里指定的规则,判断字段值只要是time.Time 类型就能验证通过。
总结
本节代码版本为c19.1
访问 https://github.com/go-study-lab/go-mall/compare/c18...c19.1 可在线查看详细的代码更新。