记得刚开始学 Java 那会儿看过一个讲解 Mockito 的视频,当时不理解,感觉这东西不过如此,平时不太会需要用到,就把它忘到九霄云外了。
两年过去了,翻过来看到了同一个视频,这才意识到这东西的重要性!简直就是开发效率提升神器好不好!
两年间,是我的编程习惯发生了天翻地覆的变化。之前的我喜欢学新技术,喜欢把功能快速实现(当然,也是产品的压力在那里),不太在意代码的可维护性,单元测试更是没写过;现在的我则明白了软件开发行业一个很重要的道理:
要想走得快,先得走得稳。
The only way to go fast, is to go well.
(这话是 Robert C. Martin 在《架构整洁之道》里说的。)
简单粗暴地把功能实现,表面上看“走得快”,但其实随着项目变大,要付出更多的维护成本,反而走得慢;如果多花一些时间设计架构和编写测试,把软件拆分成更小、更灵活、更易测试的组件,先保证“走得稳”,那么长期来看反而会走得快。
所以,单元测试是一件常常被忽视,但其实十分关键的事情。
众所周知,Java 已经有了 JUnit 这个很好用的单元测试工具。那么 Mockito 替我们解决了什么问题呢?
正如其名字暗示的一样,Mockito 主要解决了 mock 的问题。
简单来说,假设你要测试一个组件 A,而这个组件调用了另一个组件 B 的某方法 someMethod()
。这就意味着,你在执行组件 A 的时候,组件 B 的 someMethod()
也会被执行。
在开发中,这种情况当然是无可避免的;但对测试来说,这可不是个好消息!试想如下几种情况:
someMethod()
涉及网络请求。一方面网络请求不稳定,另一方面该请求需要 token,不可能在每个需要执行单元测试的环境都配置这样的 token;someMethod()
涉及用户输入。不可能让用户在单元测试的时候配合输入吧;someMethod()
涉及大量计算,占用大量资源。如果这样执行单元测试,显然成本过高;在这些情况下,如果想单独测试组件 A,更好的做法就是把组件 B “mock” 掉,即用一个假的组件 B 提供假的 someMethod()
方法。这就像是想要测试汽车的安全性,没必要在碰撞实验中用真人吧!用假人就行了。
下面通过几个具体的例子,来看看 Mockito 的优点:
比如我想测试组件 A 的某个方法 someMethod1
,但该方法需要调用组件 B 的 someMethod
,如何把它 mock 掉呢?
非常简单:
ComponentA componentA = new ComponentA();
ComponentB componentB = mock(ComponentB.class); // 创建一个 mock 的 componentB
when(componentB.someMethod()).thenReturn("some value");
比如,我还是想测试组件 A 的某个方法 someMethod1
,但该方法需要调用组件 A 自己的 someMethod2
,而我想把 someMethod2
mock 掉,可以吗?
可以!借助 Mockito 的 spy
方法就行了:
ComponentA componentA = new ComponentA();
ComponentA spyComponentA = spy(componentA); // 创建一个 spy 对象
doReturn("some value").when(spyComponentA).someMethod2(); // 这样一来,spyComponentA 在调用 componentA 时就会返回 "some value"
assert spyComponentA.someMethod1() == "some other value"; // 测试 someMethod1
是不是非常强大!
有没有发现,上面用到的 when
、thenReturn
、spy
、doReturn
都非常简洁?简直以冗长著称的 Java
代码里写出了脚本语言的味道!
它们都是 Mockito 提供的 static import
:
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.thenReturn;
import static org.mockito.Mockito.when;
这周刚在项目里把 Mockito 用起来了,mock 了一个 API 调用,效果非常好。以后一定会成为一件经常使用的工具。