0、需要的jar
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>2.0.2-beta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.7.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.7.4</version>
<scope>test</scope>
</dependency>
1、简单单元测试
package test;
public class ProcessDB {
public String getResultOfConnectDB() {
return "haha, Really went to the database";
}
}
package test;
public class BussinessService {
private ProcessDB processDB;
public BussinessService(ProcessDB db){
this.processDB = db;
}
public String testedMehtod(){
return processDB.getResultOfConnectDB();
}
}
测试类
package test
import org.junit.Assert;
import org.junit.Test;
public class BussinessTest {
@Test
public void testFuction(){
ProcessDB db = new ProcessDB();
BussinessService bs = new BussinessService(db);
System.out.println(bs.testedMehtod());
Assert.assertEquals("haha, Really went to the database", bs.testedMehtod());
}
}
正常的一个单元测试,测试一下正常流程,对结果进行断言。
2、mockito模拟对象
大多数时候,所测试对象依赖于一些例如数据库连接,中间件等其他远程服务,对测试造成了影响,下面用mockito模拟连接数据库的过程
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
public class BussinessTest {
private ProcessDB mockDB;
private BussinessService bs;
@Before
public void setUp() throws Exception {
mockDB = mock(ProcessDB.class);
String aa = "haha, everything is fake";
when(mockDB.getResultOfConnectDB()).thenReturn(aa);
bs = new BussinessService(mockDB);
}
@Test
public void mockConnectDB(){
assertEquals("haha, everything is fake", bs.testedMehtod());
}
}
这样就模拟了连接数据库的过程输出,本来真实连接数据库的时候输出“haha, Really went to the database“,但是真实连接数据库来测试比较麻烦,所以现在被模拟对象取代,输出你自己造的数据"haha, everything is fake"。
不过这一切都建立在你mock的对象是你被测试对象的属性变量,而不是在测试方法内部自己new出来的ProcessDB对象。下面看方法内部new对象,使用mockito模拟对象会怎样
3、mockito模拟方法内部new的对象
被测试类简单修改:
package test;
public class BussinessService {
public String testedMehtod(){
ProcessDB processDB = new ProcessDB();
return processDB.getResultOfConnectDB();
}
}
测试:
package test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
public class BussinessTest {
private ProcessDB mockDB;
private BussinessService bs;
@Before
public void setUp() throws Exception {
mockDB = mock(ProcessDB.class);
String aa = "haha, everything is fake";
when(mockDB.getResultOfConnectDB()).thenReturn(aa);
bs = new BussinessService();
}
@Test
public void mockConnectDB(){
System.out.println(bs.testedMehtod());
assertEquals("haha, everything is fake", bs.testedMehtod());
}
}
测试失败:
4、PowerMockito模拟方法内部创建出来的对象
package test;
public class ProcessDB {
public static ProcessDB getInstance(){
return new ProcessDB();
}
public String getResultOfConnectDB() {
return "haha, Really went to the database";
}
}
package test;
public class BussinessService {
public String testedMehtod(){
ProcessDB processDB = ProcessDB.getInstance();
return processDB.getResultOfConnectDB();
}
}
有时候被测试类存在这样的写法,上述mockito又不管用,使用PowerMockito来模拟
package test;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest(ProcessDB.class)
public class BussinessTest {
private ProcessDB mockDB;
private BussinessService bs;
@Test
public void mockConnectDB(){
PowerMockito.mockStatic(ProcessDB.class);
mockDB = PowerMockito.mock(ProcessDB.class);
bs = new BussinessService();
PowerMockito.when(ProcessDB.getInstance()).thenReturn(mockDB);
String aa = "haha, everything is fake";
PowerMockito.when(mockDB.getResultOfConnectDB()).thenReturn(aa);
System.out.println(bs.testedMehtod());
assertEquals("haha, everything is fake", bs.testedMehtod());
}
}
这样又可以测试通过,模拟了连接数据库的过程,特别注意头顶的两个注解,@RunWith(PowerMockRunner.class)
@PrepareForTest(ProcessDB.class) 。分两步:第一步模拟出假对象PowerMockito.when(ProcessDB.getInstance()).thenReturn(mockDB); ,第二步模拟假对象调用的方法PowerMockito.when(mockDB.getResultOfConnectDB()).thenReturn(aa);
5、仿照上面解决3中的问题,内部直接使用new对象而不是通过静态方法getInstance()
package test;
public class BussinessService {
public String testedMehtod(){
ProcessDB processDB = new ProcessDB();
return processDB.getResultOfConnectDB();
}
}
测试类:
package test;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest(ProcessDB.class)
public class BussinessTest {
private ProcessDB mockDB;
private BussinessService bs;
@Test
public void mockConnectDB(){
mockDB = PowerMockito.mock(ProcessDB.class);
bs = new BussinessService();
try {
PowerMockito.whenNew(ProcessDB.class).withNoArguments().thenReturn(mockDB);
} catch (Exception e) {
e.printStackTrace();
}
String aa = "haha, everything is fake";
PowerMockito.when(mockDB.getResultOfConnectDB()).thenReturn(aa);
System.out.println(bs.testedMehtod());
assertEquals("haha, everything is fake", bs.testedMehtod());
}
}
这样看似与4相似,但是结果测试又通不过,输出的又是"haha, Really went to the database",加上PowerMockito.mockStatic(ProcessDB.class)也不管用。
6、真正解决3中的new对象的问题
其实5已经基本上实现了,经过一段时间的排查,原来问题是出在头顶上的两个注解上
package test;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest({ProcessDB.class, BussinessService.class})
public class BussinessTest {
private ProcessDB mockDB;
private BussinessService bs;
@Test
public void mockConnectDB(){
mockDB = PowerMockito.mock(ProcessDB.class);
bs = new BussinessService();
try {
PowerMockito.whenNew(ProcessDB.class).withNoArguments().thenReturn(mockDB);
} catch (Exception e) {
e.printStackTrace();
}
String aa = "haha, everything is fake";
PowerMockito.when(mockDB.getResultOfConnectDB()).thenReturn(aa);
System.out.println(bs.testedMehtod());
assertEquals("haha, everything is fake", bs.testedMehtod());
}
}
测试通过,一切OK
这样应该可以实现模拟对象的各种情况了,即使代码不规范,喜欢到处new依赖对象而不是将依赖对象注入服务,也可以轻松模拟对象,模拟连接远程服务,构造自己想要的数据从而完成测试自己代码逻辑。
7、流程测试的最后一步是外部依赖时的验证(例如发送到kafka等)
package mockito;
import java.util.LinkedList;
public class Verify {
private LinkedList k ;
public Verify(LinkedList k){
this.k = k;
}
public void test(){
k.add("d");
k.add("b");
System.out.println(k.get(0));
}
}
package mockito;
import java.util.LinkedList;
import static org.mockito.Mockito.*;
import org.junit.Test;
public class VeriryTestTest {
@Test
public void testt(){
LinkedList mockedList = mock(LinkedList.class);
when(mockedList.get(anyInt())).thenReturn("first");
new Verify(mockedList).test();
verify(mockedList).get(anyInt());
}
}
此测试LinkedList相当于外部依赖,最后验证依赖的某个方法是否被调用过,如果有机会调用说明流程上基本上没什么问题,验证不严谨。