Mockito 写单元测试的意义是什么? Mock 和代码的强耦合怎么解决?

2019-02-11 18:47:14 +08:00
 lhx2008
学了两天 Mockito,初看完全解耦非常棒,但是写了几个测试发现是个大坑。

因为测试代码和实际代码强耦合了!比如你在 Service 里面调了 dao 类的 getA 方法,Mockito 就要求你 mock dao 类的 getA 方法。我如果到时候改成调 dao 类的 getB 方法,测试代码也必须改。无论是 service 还是 dao 类,只要任何一个的方法名改了或者逻辑改了,都必须改测试代码。

而我认为单元测试的意义还是在于,我不管你下面的实现是什么,只要这个方法完成了我的测试需求就行。但是 mockito 这种测试代码和实际代码强耦合的情况,我必须把被测代码的所有其他类调用都找出来并且 mock,你重构 service 或者换了数据库,测试代码也得重新找出来改!

另外关于测试的效果,我看还有点掩耳盗铃的感觉:写测试的时候,我假设我原来的代码没问题,才找调用出来 mock,而测出来肯定也没有问题呀。而事实上,问题很多时候就是出在别的类调用的实际返回值不符合预期。
3099 次点击
所在节点    问与答
20 条回复
pudgedoor
2019-02-11 18:56:27 +08:00
我之前也这么觉得,有一次写单测的时候,set 值弄错了,结果单测断言的时候失败了,然后找到了这个写错的地方。单测应该 mock 数据和 api,测的是业务逻辑吧。我也不太懂,坐等大神解释。
momocraft
2019-02-11 19:09:22 +08:00
可以看一下"依赖注入"
lhx2008
2019-02-11 19:23:48 +08:00
@momocraft 不是注入的问题,是 mock 的问题,dao 层不是固定的,dao 层改了,mock 的对象也得跟着改,这样测试代码和开发代码就完全联动了。
earendil1412
2019-02-11 19:28:00 +08:00
逻辑改了,改单元测试不是很正常吗?
CFO
2019-02-11 19:40:09 +08:00
和我的感觉差不多 我假装在测我假装写的没问题的代码
feiyuanqiu
2019-02-11 20:09:35 +08:00
「我如果到时候改成调 dao 类的 getB 方法,测试代码也必须改」

应该改。单元测试主要是验证 SUT ( system under test )本身的实现逻辑的正确性,你把实现逻辑都改了,单元测试当然也需要修改。比如你的例子,getA 改成 getB 方法,参数、返回有没有变更?有变更那 service 的代码就需要修改,service 修改了就需要单元测试确保修改的正确性;如果只是换个名字,现在 IDE 的重构功能可以把相关名称都替换了,也不需要手动修改。


「但是 mockito 这种测试代码和实际代码强耦合的情况,我必须把被测代码的所有其他类调用都找出来并且 mock 」

mock 是个测试替身,你不告诉它在被调用时应该怎么返回,它也不清楚你究竟想要什么。如果你不需要 mockito 的灵活性,完全可以为这个类写一个替身实现,然后在测试中使用这个替身类,就不需要在每个依赖这个类的地方都写一遍 mockito 代码了。


「写测试的时候,我假设我原来的代码没问题,才找调用出来 mock,而测出来肯定也没有问题呀」

mockito 主要还是在 TDD 中用得多些,你在写代码的时候,依赖类还没实现,这时候就需要用测试替身来完成单元测试。依靠 mockito 的灵活性,方便调整依赖类的入参返回,等上层实现稳定后,也就相当于依赖类的实现契约也建立好了,就可以根据这个契约再去实现依赖类...以此类推。如果是先写代码,再写测试,确实会有你这种感觉。
feiyuanqiu
2019-02-11 20:16:55 +08:00
再补充一些,《 Practical Unit Testing with JUnit and Mockito 》这本书讲得比较细,可以看一看。
lhx2008
2019-02-11 20:28:43 +08:00
@feiyuanqiu 谢谢,很客观,用 TDD 的思路来说是讲得通的,但是真的完全使用这一套方法似乎工作量会陡增。如果不是 TDD,我认为还是只有在数据源确实还没有时(如别人写的接口)用 mock 的比较合适,全部 mock (包括自己写的 dao 等) 似乎不太现实。当然完全 TDD 也是有很多内容,我也不是很了解。
kaneg
2019-02-11 20:43:56 +08:00
mock 将本来不可测的代码变成可测的,降低了系统的不确定性,这本来就已经很了不起了,还要啥自行车。
ipwx
2019-02-11 21:08:35 +08:00
你的问题用任何测试工具本身都是解决不了的。

你得换编程模型。

比如 Actor Model (代表类库 Scala Akka ),就基本没有 Mock 的问题。

缺点是学习曲线比较抖。
DeweyReed
2019-02-11 21:14:44 +08:00
图个安心..(狗头
richard1122
2019-02-12 00:06:52 +08:00
"写测试的时候,我假设我原来的代码没问题,才找调用出来 mock,而测出来肯定也没有问题呀。而事实上,问题很多时候就是出在别的类调用的实际返回值不符合预期。"

如果有些非常简单的业务逻辑可能不适合这类单元测试的,也有可能是业务逻辑没有抽象好,分散在各处。 如果能比较好的把业务逻辑“内聚”了,方便测某一部分
richard1122
2019-02-12 00:08:33 +08:00
从另一个角度来说楼主大概追求的是集成测试?比如测某个接口前直接把 DB 数据准备好,然后传一些参数,里面真实跑,然后直接看返回值或者结束后 DB 状态。

我觉得也不用过度追求单元测试,某些情况下单元测试好用,某些情况下也可以直接上集成测试
hlwjia
2019-02-12 01:46:03 +08:00
推荐阅读的 Write tests. Not too many. Mostly integration.
aijam
2019-02-12 04:53:41 +08:00
TDD 和 Restful 什么的都一样,都是鸡汤玄学。
lzdhlsc
2019-02-12 05:42:23 +08:00
接口和模块化的意义不就在于此吗?当你写一个单元测试的时候,你不关心 dependency 的逻辑实现,你只关心接口。至于 dependency 如何测试,在你的测试中不应该关心。
lhx2008
2019-02-12 08:29:15 +08:00
@lzdhlsc
是这样子,我的重点是我还要用 mock 模拟并跟踪 dependency 的行为,是巨大的负担。

一些 dependency 由于被封装不彻底,他的所有行为你甚至无法掌握,比如 redis 客户端,http 接口调用等。报错处理,或者执行反馈你都必须要 mock 到。工作量巨大,而且没 mock 到还很麻烦。

一些 dependency 没有设计好,后面改了行为,但是这个时候如果 mock 没有跟着改,又会出现类似的问题。

如果不用 mock 直接测的话,我则只需要被测对象关心本身的行为,我不需要关心 dependency,换了 dependency 也和这个测试没关系。
q4336431
2019-02-12 09:47:10 +08:00
我用 Mockito 就是为了省去从数据库或者缓存里面拿数据源的操作步骤
lzdhlsc
2019-02-12 11:46:51 +08:00
@lhx2008 有道理。有时候确实直接用 dependency 比较好, 我能想到的无法替代 mock 的场景就是 error handling 了,毕竟模拟某些 java driver throw exception 有时候很难。另外有时候有些 test driver 很好用 比如 mock mongo 的 Fongo。
loveCoding
2019-02-12 11:52:17 +08:00
需求变动,改单测是免不了的,楼主可以试试 tdd
先写单测,等到场景覆盖完了单测也就写完了,业务实现空方法,剩下的就是实现逻辑而已.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/534006

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX