软件测试是软件质量保证的关健环节,代表了需求、设计和编码的最终检查。
如上图所示,测试金字塔将测试分为三类
测试金字塔也向我们揭示了一个实时:单元测试是整个测试环节的根基,如果单元测试做不好,上层的集成测试、功能测试都会无从谈起。
遗憾的是,多数开发者都不具备编写单元测试的良好习惯,甚至缺乏编写单元的动力。
除了缺乏软件质量保障的意识外,"嫌麻烦"也是这类开发者面对单元测试的口头禅。
本节将介绍Mockito,这是一个单元测试的利器。Mockito的出现,让我们可以更加轻松地编写单元测试。
在介绍Mockito之前,先来解释下,我们为什么不推荐使用Spring Boot启动单元测试框架。
实际上,Spring Boot本身是提供了单元测试框架的,可以在JUnit中通过注解的配置,启动一个Spring上下文环境,并支持自动注入等功能,如果你感兴趣,可以参考这篇文档。
在实际工作中,我也尝试过上述方法,但效果却并不太好,主要原因是:
基于上述原因,我强烈不推荐在单元测试中启动Spring Boot环境。
对于服务之间存在依赖关系的场景,建议直接使用Mockito的打桩(Mock)进行。
希望在仔细的阅读本节后,你也会爱上单元测试:-)
在软件测试中,Mock指的是效仿、模仿。Mockito就是为了解决测试中的Mock问题而诞生的,它可以很好的解决单元测试中,由于不同类耦合而带来的难以测试的问题。
还是以上面Spring Boot环境为例子。假设我们要测试A类,而类A又调用了B类和C类。此时可能有两种选择:
现在有了Mockito后,我们有了另外的思路:无需构造B和C,而是通过Mockito,"Mock"出B和C(构造符合接口但没有实现的类),由于我们要测试的是A类中的逻辑,只要检查A调用B和C的时机、次数、参数是否正确,就可以了。
我们通过一个例子,来说明mockito的用法。
首先是ServiceA和它的实现:
package com.coder4.lmsia.abc.server.service.intf;
/**
* @author coder4
*/
public interface ServiceA {
int methodA(int a, int b);
}
package com.coder4.lmsia.abc.server.service.impl;
import com.coder4.lmsia.abc.server.service.intf.ServiceA;
import com.coder4.lmsia.abc.server.service.intf.ServiceB;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author coder4
*/
@Service
public class ServiceAImpl implements ServiceA {
@Autowired
private ServiceB serviceB;
@Override
public int methodA(int a, int b) {
if (a <= 10 && b <= 10) {
return a + b;
} else {
return serviceB.methodB(a, b);
}
}
}
然后是服务B和它的实现:
package com.coder4.lmsia.abc.server.service.intf;
/**
* @author coder4
*/
public interface ServiceB {
int methodB(int a, int b);
}
package com.coder4.lmsia.abc.server.service.impl;
import com.coder4.lmsia.abc.server.service.intf.ServiceB;
import org.springframework.stereotype.Service;
/**
* @author coder4
*/
@Service
public class ServiceBImpl implements ServiceB {
@Override
public int methodB(int a, int b) {
return a * b;
}
}
我们总结一下功能:
在编写单元测试前,先要引用对应的包,lmabc-server/build.gradle:
dependencies {
compile project(':lmsia-abc-common')
...
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-all:1.9.5'
}
这里要指出的是,mockito本身还是需要单元测试框架才能运行的,我们这里用的是最常见的JUnit。
然后看一下单元测试
package com.coder4.lmsia.abc.server;
import com.coder4.lmsia.abc.server.service.impl.ServiceAImpl;
import com.coder4.lmsia.abc.server.service.intf.ServiceA;
import com.coder4.lmsia.abc.server.service.intf.ServiceB;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.internal.util.reflection.Whitebox;
import static org.hamcrest.CoreMatchers.is;
/**
* @author coder4
*/
public class ServiceATest {
private ServiceA serviceA;
private ServiceB serviceB;
@Before
public void before() {
serviceA = new ServiceAImpl();
serviceB = Mockito.mock(ServiceB.class);
Whitebox.setInternalState(serviceA, "serviceB", serviceB);
}
@Test
public void testBelow10() {
Assert.assertThat(serviceA.methodA(1, 1), is(2));
Mockito.verifyZeroInteractions(serviceB);
}
@Test
public void testAbove10() {
serviceA.methodA(100, 1);
Mockito.verify(serviceB).methodB(100, 1);
}
}
我们分步解释一下:
怎么样,有了Mockito后,测试是不是变得有趣起来了:-)
官方文档中提供了更多有趣的例子,等待你的发掘。