《Java单元测试实战》——案例集锦:Java单元测试典型案例集锦(4) https://developer.aliyun.com/article/1232055?groupCode=java
五、 如何测试Lambda表达式
在有些单元测试中,Lambda表达式并不一定被执行,所以导致Lambda表达式没有被测试。
1. 案例代码
这里,以从ODPS中查询用户交易订单为例说明。
1) 被测代码
交易订单查询服务,其中有一段转化订单的Lambda表达式。
2) 依赖代码
封装了通用的ODPS查询方法。
/** * 交易ODPS服务类 */ @Slf4j @Service public class TradeOdpsService { /** 注入依赖对象 */ /** 交易ODPS */ @Resource(name = "tradeOdps") private Odps tradeOdps; /** * 执行查询 * * @param <T> 模板类型 * @param sql SQL语句 * @param dataParser 数据解析器 * @return 查询结果列表 */ public <T> List<T> executeQuery(String sql, Function<Record, T> dataParser) { try { // 打印提示信息 log.info("开始执行ODPS数据查询..."); // 执行ODPS查询 Instance instance = SQLTask.run(tradeOdps, sql); instance.waitForSuccess(); // 获取查询结果 List<Record> recordList = SQLTask.getResult(instance); if (CollectionUtils.isEmpty(recordList)) { log.info("完成执行ODPS数据查询: totalSize=0"); return Collections.emptyList(); } // 依次读取数据 List<T> dataList = new ArrayList<>(); for (Record record : recordList) { T data = dataParser.apply(record); if (Objects.nonNull(data)) { dataList.add(data); } } // 打印提示信息 log.info("完成执行ODPS数据查询: totalSize={}", dataList.size()); // 返回查询结果 return dataList; } catch (OdpsException e) { log.warn("执行ODPS数据查询异常: sql={}", sql, e); throw new BusinessException("执行ODPS数据查询异常", e); } } }
2. 方法1:直接测试法(不推荐)
按照通用的单元测试方法进行测试,发现Lambda表达式没有被测试到。
/** * 交易订单服务测试类 */ @RunWith(MockitoJUnitRunner.class) public class TradeOrderServiceTest { /** 定义静态常量 */ /** 资源路径 */ private static final String RESOURCE_PATH = "testTradeOrderService/"; /** 模拟依赖对象 */ /** 交易ODPS服务 */ @Mock private TradeOdpsService tradeOdpsService; /** 定义测试对象 */ /** 交易订单服务 */ @InjectMocks private TradeOrderService tradeOrderService; /** * 测试: 查询交易订单-正常 */ @Test public void testQueryTradeOrderWithNormal() { // 模拟依赖方法 // 模拟依赖方法: tradeOdpsService.executeQuery List<TradeOrderVO> tradeOrderList = CastUtils.cast(Mockito.mock(List.class)); Mockito.doReturn(tradeOrderList).when(tradeOdpsService).executeQuery(Mockito.anyString(), Mockito.any()); // 调用测试方法 Long userId = 12345L; Integer maxCount = 100; Assert.assertSame("交易订单列表不一致", tradeOrderList, tradeOrderService.queryTradeOrder(userId, maxCount)); // 验证依赖方法 // 验证依赖方法: tradeOdpsService.executeQuery String path = RESOURCE_PATH + "testQueryTradeOrderWithNormal/"; String text = ResourceHelper.getResourceAsString(getClass(), path + "queryTradeOrder.sql"); Mockito.verify(tradeOdpsService).executeQuery(Mockito.eq(text), Mockito.any()); } }
3. 方法2:联合测试法(不推荐)
有人建议,可以把TradeOrderService(交易订单服务)和TradeOdpsService(交易ODPS服务)联合测试,这样就可以保证Lambda表达式被测试到。
/** * 交易订单服务测试类 */ @RunWith(PowerMockRunner.class) @PrepareForTest({SQLTask.class}) public class TradeOrderServiceTest { /** 定义静态常量 */ /** 资源路径 */ private static final String RESOURCE_PATH = "testTradeOrderService/"; /** 模拟依赖对象 */ /** 交易ODPS */ @Mock(name = "tradeOdps") private Odps tradeOdps; /** 定义测试对象 */ /** 交易ODPS服务 */ @InjectMocks private TradeOdpsService tradeOdpsService = Mockito.spy(TradeOdpsService.class); /** 交易订单服务 */ @InjectMocks private TradeOrderService tradeOrderService; /** * 测试: 查询交易订单-正常 * * @throws OdpsException ODPS异常 */ @Test public void testQueryTradeOrderWithNormal() throws OdpsException { // 模拟依赖方法 PowerMockito.mockStatic(SQLTask.class); // 模拟依赖方法: SQLTask.run Instance instance = Mockito.mock(Instance.class); PowerMockito.when(SQLTask.run(Mockito.eq(tradeOdps), Mockito.anyString())).thenReturn(instance); // 模拟依赖方法: SQLTask.getResult Record record1 = PowerMockito.mock(Record.class); Record record2 = PowerMockito.mock(Record.class); List<Record> recordList = Arrays.asList(record1, record2); PowerMockito.when(SQLTask.getResult(instance)).thenReturn(recordList); // 模拟依赖方法: record.getString Mockito.doReturn(1L).when(record1).getBigint("id"); Mockito.doReturn(2L).when(record2).getBigint("id"); // 调用测试方法 Long userId = 12345L; Integer maxCount = 100; List<TradeOrderVO> tradeOrderList = tradeOrderService.queryTradeOrder(userId, maxCount); String path = RESOURCE_PATH + "testQueryTradeOrderWithNormal/"; String text = ResourceHelper.getResourceAsString(getClass(), path + "tradeOrderList.json"); Assert.assertEquals("交易订单列表不一致", text, JSON.toJSONString(tradeOrderList)); // 验证依赖方法 PowerMockito.verifyStatic(SQLTask.class); // 验证依赖方法: SQLTask.run text = ResourceHelper.getResourceAsString(getClass(), path + "queryTradeOrder.sql"); SQLTask.run(tradeOdps, text); // 验证依赖方法: SQLTask.getResult SQLTask.getResult(instance); // 验证依赖方法: instance.waitForSuccess Mockito.verify(instance).waitForSuccess(); // 验证依赖方法: record.getString Mockito.verify(record1).getBigint("id"); Mockito.verify(record2).getBigint("id"); } }
主要问题:需要了解TradeOdpsService.executeQuery(执行查询)方法的逻辑并构建单元测试用例,导致TradeOrderService.queryTradeOrder(查询交易订单)方法的单测测试用例非常复杂。
4. 方法3:重构测试法(推荐)
其实,只需要把这段Lambda表达式提取成一个convertTradeOrder(转化交易订单)方法,即可让代码变得清晰明了,又可以让代码更容易单元测试。
1) 重构代码
提取Lambda表达式为convertTradeOrder(转化交易订单)方法。
@Slf4j /** * 交易订单服务类 */ @Service public class TradeOrderService { /** 注入依赖对象 */ /** 交易ODPS服务 */ @Autowired private TradeOdpsService tradeOdpsService; /** * 查询交易订单 * * @param userId 用户标识 * @param maxCount 最大数量 * @return 交易订单列表 */ public List<TradeOrderVO> queryTradeOrder(Long userId, Integer maxCount) { String format = ResourceHelper.getResourceAsString(getClass(), "query_trade_order.sql"); String sql = String.format(format, userId, maxCount); return tradeOdpsService.executeQuery(sql, TradeOrderService2::convertTradeOrder); } /** * 转化交易订单 * * @param record ODPS记录 * @return 交易订单 */ private static TradeOrderVO convertTradeOrder(Record record) { TradeOrderVO tradeOrder = new TradeOrderVO(); tradeOrder.setId(record.getBigint("id")); // ... return tradeOrder; } }
2) 测试用例
针对queryTradeOrder(查询交易订单)方法和convertTradeOrder(转化交易订单)方法分别进行单元测试。
/** * 交易订单服务测试类 */ @RunWith(MockitoJUnitRunner.class) public class TradeOrderServiceTest { /** 定义静态常量 */ /** 资源路径 */ private static final String RESOURCE_PATH = "testTradeOrderService/"; /** 模拟依赖对象 */ /** 交易ODPS服务 */ @Mock private TradeOdpsService tradeOdpsService; /** 定义测试对象 */ /** 交易订单服务 */ @InjectMocks private TradeOrderService tradeOrderService; /** * 测试: 查询交易订单-正常 */ @Test public void testQueryTradeOrderWithNormal() { // 模拟依赖方法 // 模拟依赖方法: tradeOdpsService.executeQuery List<TradeOrderVO> tradeOrderList = CastUtils.cast(Mockito.mock(List.class)); Mockito.doReturn(tradeOrderList).when(tradeOdpsService).executeQuery(Mockito.anyString(), Mockito.any()); // 调用测试方法 Long userId = 12345L; Integer maxCount = 100; Assert.assertSame("交易订单列表不一致", tradeOrderList, tradeOrderService.queryTradeOrder(userId, maxCount)); // 验证依赖方法 // 验证依赖方法: tradeOdpsService.executeQuery String path = RESOURCE_PATH + "testQueryTradeOrderWithNormal/"; String text = ResourceHelper.getResourceAsString(getClass(), path + "queryTradeOrder.sql"); Mockito.verify(tradeOdpsService).executeQuery(Mockito.eq(text), Mockito.any()); } /** * 测试: 转化交易订单 * * @throws Exception 异常信息 */ @Test public void testConvertTradeOrder() throws Exception { // 模拟依赖方法 Long id = 12345L; Record record = Mockito.mock(Record.class); Mockito.doReturn(id).when(record).getBigint("id"); // 调用测试方法 TradeOrderVO tradeOrder = Whitebox.invokeMethod(TradeOrderService2.class, "convertTradeOrder", record); Assert.assertEquals("订单标识不一致", id, tradeOrder.getId()); // 验证依赖方法 Mockito.verify(record).getBigint("id"); } }
《Java单元测试实战》——案例集锦:Java单元测试典型案例集锦(6) https://developer.aliyun.com/article/1232053?groupCode=java