这里我们想要测试 Service 的正确性,但是又不想要真的持久化 DAO,所以,这个时候我们会自己创建一个 Stub,然后提供给 Service,同时,我们还能操作 DAO 的行为,达到运行得效果,例如:
这样我们就能测试到登录成功的代码了,登录成功的测试了,我们还希望测试一下登录失败的,那么也很简单,修改一下就可以了呗:
这里对测试代码稍微改了一下,可以发现,我们可以通过修改一个变量来控制 Stub 的输出,从而达到测试不同功能的效果,这就解决了组件依赖的问题。
函数依赖函数依赖相比于组件依赖会更麻烦一点,因为我们在前面可以看到,组件依赖的话我们可以传递 Stub 进行,这样我们可以随意得控制 Stub 的行为,但是函数不行呀,这里我们又不能传函数进去,因为函数是被 import 进去的啊。问题就在这了,因为函数是被 import 进去的,所以可以理解为函数是全局的了,既然这样,那么我们为什么不修改一下函数呢?什么意思?我们先来看着正常的业务例子:
这里我是想表达的一个意思就是需要先登录,然后登录完之后我们才能回复消息,这里我们的登录逻辑是简单的,但是,在实际业务中可能这里的登录逻辑就设计到 DB 访问等等,我们希望不走真实的逻辑,而是自己来控制 Login 的行为。
首先,先分析一下我们的 UT 目的,我们的目的是测试 Reply 函数,我们期望是 Login 成功,那么 Reply 也应该是成功的;如果 Login 失败,那么 Reply 也应该是失败的。这个测试结论不应该被 Login 所影响,及时以后 Login 逻辑修改了,我们也应该是这个逻辑,不会受到影响,那么我们可以这么编写 UT:
这里可以发现,我们是修改了 Login 这个函数的代码,从而控制 Login 函数的返回值,这样我们就可以测试我们写的代码的逻辑是否正确了。
可能有心细的同学发现,第二个函数依赖里面的代码有个特别的地方,在于函数 Login 是个变量,为什么要用函数变量呢,不能直接使用函数吗?这里确实是一个麻烦得地方,因为如果使用直接函数的话,我们没得赋值,那么也就无法修改它了。如果代码不是我们自己写的,而是使用的其他同事的代码,那么问题就大了。这种情况下,我们可以怎么处理呢?
在 Reference 的中有一篇文章:Mocking functions in Go 介绍了一种方法,那就是将我们引用的函数赋值为函数变量再使用,从而达到同样的效果。
第三方库在前面的介绍中,我们都是自己重新写了一个 Stub 类,但是同时问题暴露了,写起来比较复杂和繁琐,所以就有了一些第三方库可以方便我们编写这些东西,Gomock 就是一个,这里就简单演示一下 GoMock 的一些功能。
gomock 有两个组件,分别是 gomock 和 mockgen。mockgen 可以根据我们的 interface 生成对应的 Stub 对象,例如我们前面提到的 DAO,因为我们 DAO 的 Interface 已经写好了,所以我们可以很方便得生成 StubDao,只需要使用命令:
$ mockgen -source=main.go > mock_dao.go然后我们查看一下 mock_dao.go 的内容:
可以发现两个函数都被实现了 ReadAll 和 SaveData,但是,同时我们也发现,生成的 DAO 比我们预期的要复杂得多,至于为什么药这么复杂,看看接下来的使用就明白了。现在我们已经有了 DAO,那么下一步就是控制 DAO 的输出了。
这里可以看到,使用 gomock 可以让我们的控制输出更加简便,之前我们需要通过控制变量的方式来达到控制输出的目的,但是这里可以很方便得使用:
d.EXPECT().FuncXXX.Return(xxxx)来指定函数应该返回什么结果,确实方便了,同时,还省去了我们自己编写 MockClass 的时间,这个还是值得一试的。
总结OK,本文关于 Go 的 UT 就差不多这么多了,这里介绍了 Go 单元测试的编写套路,以及介绍了我们在项目中常见的一些场景的处理,最后再介绍了一款第三库用于加快 UT 的编写。在我另一篇文章:从开源项目看 Python 单元测试 中我曾经提到,UT 是我们都希望别人写,但是自己又不想写的东西,希望,Go 语言相对简单的编写套路和模式能够让大家对 UT 有所重视,并且愿意写起来。lol。