文章目录
Unit test 示例
单元测试选用的测试框架junit
,如果依赖Spring
框架需要Mock 数据,选用mockito
和powermock
。
-
mockito
的工作原理是通过创建依赖对象的proxy
,所有的调用先经过proxy
对象,proxy
对象拦截了所有的请求再根据预设的返回值进行处理。mockito
可以mockSpring
依赖的bean。适用需要创建实例的对象。 -
powerMock
则在mockito
原有的基础上做了扩展,通过修改类字节码并使用自定义ClassLoader
加载运行的方式来实现mock静态方法、final方法、private方法、系统类的功能。 -
依赖的
pom
可以参考:<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>1.10.19</version> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-core</artifactId> <version>1.6.2</version> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>1.6.2</version> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito</artifactId> <version>1.6.2</version> </dependency>
1. 工具类测试用例
形如工具类,所有的方法不依赖其他的bean参与,仅仅是逻辑处理,不需要mock任何数据,仅Junit
足矣处理。
@Test
public void putItemsInListBoxTest() {
val rtn = WorkflowHelper.getProcedure(TaskSetWorkflow.class);
assertFalse(rtn.getNodes().isEmpty());
}
2. 依赖spring
容器注入的bean
mockito
可以mock
依赖其他bean所处理的数据。
@InjectMocks
创建的Mock对象BagTaskIndexService
可以直接调用真实的代码。@Mock
创建的对象将被注入到BagTaskIndexService
中所有的bean。正因为这个特性。@InjectMock
修饰被测试类,@Mock
修饰其依赖。@Spy
修饰的成员变量调用时,默认是进行真实调用,但也可以Mock
调用。- 执行单元测试逻辑,需要预先
mock
对应的数据,简称插桩,格式形如when(@Mock bean call method).then(Mock data)
。
可以参考以下示例:
// MockitoJUnitRunner 框架
@RunWith(MockitoJUnitRunner.class)
// 忽略一些依赖初始化的东西
@PowerMockIgnore("javax.management.*")
public class BagTaskIndexServiceTest {
//InjectMocks 代表测试需要执行的bean
@InjectMocks
private BagTaskIndexService bagTaskIndexService;
//Mock 待测试执行bean 所依赖的其他bean
@Mock
private BagTaskInfoDao bagTaskInfoDao;
@Mock
private BagTaskDao bagTaskDao;
@Mock
private BagTaskContentsDao contentsDao;
@Mock
private ContentSegmentationDao segmentationDao;
@Mock
private EsQconfigListener listener;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test()
public void pushFullEntityToQmq() throws Exception {
when(bagTaskInfoDao.queryAll(0L, 1000)).thenReturn(mockGetTaskInfos());
when(bagTaskDao.queryByPk(1L)).thenReturn(mockGetTask(1L));
when(contentsDao.queryByInfoIdIn(Arrays.asList(1L))).thenReturn(mockGetTaskContents());
when(segmentationDao.findByBizIdAndBizTable(1L, 1)).thenReturn(mockGetSegments());
when(listener.getIdcList(bagTaskIndexService.getClusterName().name())).thenReturn(Lists.newArrayList());
when(bagTaskIndexService.sendMessage(Mockito.any(), bagTaskIndexService.getFullTopic())).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
return null;
}
});
try {
bagTaskIndexService.pushFullEntityToQmq("tuma_bag_task2020-04-26");
} catch (IllegalArgumentException e) {
Assert.assertTrue(e instanceof IllegalArgumentException);
}
}
private Optional<ContentSegmentation> mockGetSegments() {
return Optional.empty();
}
private List<BagTaskContents> mockGetTaskContents() {
BagTaskContents content = new BagTaskContents();
content.setId(1L);
content.setBagTaskInfoId(1L);
content.setValue("Text");
content.setLocale("zh-CN");
content.setEditor(-1);
return Arrays.asList(content);
}
private List<BagTaskInfo> mockGetTaskInfos() {
List<BagTaskInfo> bagTaskInfos = Lists.newArrayList();
BagTaskInfo info = new BagTaskInfo();
info.setId(1L);
info.setBagTaskId(1L);
info.setResourceType("test");
info.setResourceKey("1234");
bagTaskInfos.add(info);
return bagTaskInfos;
}
private BagTask mockGetTask(Long taskId) {
BagTask bagTask = new BagTask();
bagTask.setId(taskId);
bagTask.setName("test");
return bagTask;
}
}
3. 依赖Spring容器bean同时依赖静态类
mockito
可以mock
依赖其他bean所处理的数据。powermock
可以mock静态方法、final方法、private方法、系统类的功能。
mockito
参考 2. 依赖spring
容器注入的bean。
powermock
静态方法,参考如下步骤:
1. 指定所需静态类 `@PrepareForTest(BagTaskHelper.class)`。
2. 初始化 测试类型所需的静态类 `PowerMockito.mockStatic(BagTaskHelper.class)`。
3. `mock`对应的数据,`PowerMockito.when(BagTaskHelper.getTask(26L)).thenReturn(task);`.
// MockitoJUnitRunner 框架
@RunWith(PowerMockRunner.class)
// 忽略一些依赖初始化的东西
@PowerMockIgnore("javax.management.*")
public class BagTranslationLogServiceTest {
// 需要注入的bean
@InjectMocks
private BagTranslationLogService bagTranslationLogService;
// 需要注入的bean而依赖的bean 即为InjectMocks 所有依赖的bean
@Mock
private BagTaskTranslationHiveDao hiveDao;
// 静态类型所需要的类型
@PrepareForTest(BagTaskHelper.class)
@Test
public void query() throws IOException, InvocationTargetException, IllegalAccessException {
List<BagTaskTranslationHiveEntity> logEntities = mockHiveEntities();
// mock bean method 返回的数据
when(hiveDao.scanLogEntities(26L, "1235", "zh-CN", null, null, 1000)).thenReturn(logEntities);
BagTask task = new BagTask();
task.setName("test");
// Power mock static 方法数据
PowerMockito.mockStatic(BagTaskHelper.class);
PowerMockito.when(BagTaskHelper.getTask(26L)).thenReturn(task);
List<Map<String, Object>> objects = bagTranslationLogService.query(26L, "1235", "zh-CN", null, null);
Assert.assertTrue(!objects.isEmpty() && objects.get(0).size() == 4);
}
private List<BagTaskTranslationHiveEntity> mockHiveEntities() {
List<BagTaskTranslationHiveEntity> logEntities = Lists.newArrayList();
BagTaskTranslationHiveEntity entity = new BagTaskTranslationHiveEntity();
entity.setBagTaskId(26L);
entity.setLocale("zh-CN");
entity.setOperator("test");
entity.setReferenceLocale("en-Us");
entity.setReferenceText("test");
entity.setResourceKey("1235");
entity.setResourceType("test");
entity.setStatus("Add");
entity.setTranslation("test");
entity.setTimeStamp(new Timestamp(System.currentTimeMillis()));
logEntities.add(entity);
return logEntities;
}
}
4. Mock各种实际情况
1)忽略静态变量初始化
@SuppressStaticInitializationFor({"com.ctrip.ibu.flit.boot.utils.XX"})
指定当前类所有的静态变量和静态块都不会初始化。
2)初始化静态变量
Whitebox.setInternalState(XX.class, "FIELD_NAME", VALUE);
针对忽略静态块和变量初始化,但是部分变量需要初始化。
3)依赖的bean
为list
或者map
a) bean
为 list
@Spy
List<ISentenceTrans> flitLegacyTransList = Lists.newArrayList();
@Mock
private EchoTrans echoTrans;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
flitLegacyTransList.add(echoTrans);
}
b)bean
为map
@Mock
private EnRoomNameProcessor processor;
@Spy
private Map<String, RoomTypeProcessorChain> roomTypeProcessorChain = Maps.newHashMap();
@Before
public void init() {
MockitoAnnotations.initMocks(this);
roomTypeProcessorChain.put("OVERSEA", new RoomTypeProcessorChain(Arrays.asList(processor)));
roomTypeProcessorChain.put("DOMESTIC", new RoomTypeProcessorChain(Arrays.asList(processor)));
roomTypeProcessorChain.put("CN_TC_TW", new RoomTypeProcessorChain(Arrays.asList(processor)));
roomTypeProcessorChain.put("NORMAL", new RoomTypeProcessorChain(Arrays.asList(processor)));
}
4) 链式调用mock
深度插桩
需要注意的是,只有Mockito
才支持深度插桩,PowerMock
是不支持的,指定mock
参数RETURNS_DEEP_STUBS
。
@RunWith(MockitoJUnitRunner.class)
public class RefundGoTranslatorTest {
@Test
public void getTranslatorName() {
RefundGoTranslator changeBackTranslator = Mockito.mock(RefundGoTranslator.class, RETURNS_DEEP_STUBS);
Assert.assertEquals(changeBackTranslator.translate(any()).getProgress(), Progress.COMPLETED);
}
}
建议
编写所有的method,尽量都有返回值,因为如果过没有返回值,就没有办法mock数据。比如说你想跳过流程中某个函数,虽然你可以doNothing().when(T).dothing()
,第一次起作用,但是第二次运行会有问题。