《Java单元测试实战》——案例集锦:Java单元测试典型案例集锦(9) https://developer.aliyun.com/article/1232050?groupCode=java
十、 如何测试多线程并发编程
Java多线程并发编程,就是通过多个线程同时执行多个任务来缩短执行时间、提高执行效率的方法。在JDK1.8中,新增了CompletableFuture类,实现了对任务编排的能力——可以轻松地组织不同任务的运行顺序、规则及方式。
1. 案例代码
这里,以并行获取批量交易订单为例说明。
/** * 交易订单服务类 */ @Slf4j @Service public class TradeOrderService { /** 定义静态常量 */ /** 等待时间(毫秒) */ private static final long WAIT_TIME = 1000L; /** 注入依赖对象 */ /** 交易订单DAO */ @Autowired private TradeOrderDAO tradeOrderDAO; /** 执行器服务 */ @Autowired private ExecutorService executorService; /** * 获取交易订单列表 * * @param orderIdList 订单标识列表 * @return 交易订单列表 */ public List<TradeOrderVO> getTradeOrders(List<Long> orderIdList) { // 检查订单标识列表 if (CollectionUtils.isEmpty(orderIdList)) { return Collections.emptyList(); } // 获取交易订单期望 List<CompletableFuture<TradeOrderVO>> futureList = orderIdList.stream() .map(this::getTradeOrder).collect(Collectors.toList()); // 聚合交易订单期望 CompletableFuture<List<TradeOrderVO>> joinFuture = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])) .thenApply(v -> futureList.stream().map(CompletableFuture::join).collect(Collectors.toList())); // 返回交易订单列表 try { return joinFuture.get(WAIT_TIME, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); log.warn("获取订单中断异常", e); throw new BusinessException("获取订单中断异常", e); } catch (ExecutionException | TimeoutException | RuntimeException e) { log.warn("获取订单其它异常", e); throw new BusinessException("获取订单其它异常", e); } } /** * 获取交易订单 * * @param orderId 订单标识 * @return 交易订单期望 */ private CompletableFuture<TradeOrderVO> getTradeOrder(Long orderId) { return CompletableFuture.supplyAsync(() -> tradeOrderDAO.get(orderId), executorService) .thenApply(TradeOrderService::convertTradeOrder); } /** * 转化交易订单 * * @param tradeOrder 交易订单DO * @return 交易订单VO */ private static TradeOrderVO convertTradeOrder(TradeOrderDO tradeOrder) { TradeOrderVO tradeOrderVO = new TradeOrderVO(); tradeOrderVO.setId(tradeOrder.getId()); // ... return tradeOrderVO; } }
2. 测试用例
对于多线程并发编程,如果采集mock静态方法的方式进行单元测试,将会使单元测试用例变得非常复杂。通过实践总结,采用注入线程池的方式,将会使单元测试用例变得非常简单。
/** * 交易订单服务测试类 */ @RunWith(MockitoJUnitRunner.class) public class TradeOrderServiceTest { /** 定义静态常量 */ /** 资源路径 */ private static final String RESOURCE_PATH = "testTradeOrderService/"; /** 模拟依赖对象 */ /** 交易订单DAO */ @Mock private TradeOrderDAO tradeOrderDAO; /** 执行器服务 */ @Spy private ExecutorService executorService = Executors.newFixedThreadPool(10); /** 定义测试对象 */ /** 交易订单服务 */ @InjectMocks private TradeOrderService tradeOrderService; /** * 测试: 获取交易订单列表-正常 */ @Test public void testGetTradeOrdersWithNormal() { // 模拟依赖方法 // 模拟依赖方法: tradeOrderDAO.get String path = RESOURCE_PATH + "testGetTradeOrdersWithNormal/"; String text = ResourceHelper.getResourceAsString(getClass(), path + "tradeOrderMap.json"); Map<Long, TradeOrderDO> tradeOrderMap = JSON.parseObject(text, new TypeReference<Map<Long, TradeOrderDO>>() {}); Mockito.doAnswer(invocation -> tradeOrderMap.get(invocation.getArgument(0))) .when(tradeOrderDAO).get(Mockito.anyLong()); // 调用测试方法 text = ResourceHelper.getResourceAsString(getClass(), path + "orderIdList.json"); List<Long> orderIdList = JSON.parseArray(text, Long.class); List<TradeOrderVO> tradeOrderList = tradeOrderService.getTradeOrders(orderIdList); text = ResourceHelper.getResourceAsString(getClass(), path + "tradeOrderList.json"); Assert.assertEquals("交易订单列表不一致", text, JSON.toJSONString(tradeOrderList)); // 验证依赖方法 // 验证依赖方法: tradeOrderDAO.get ArgumentCaptor<Long> orderIdCaptor = ArgumentCaptor.forClass(Long.class); Mockito.verify(tradeOrderDAO, Mockito.atLeastOnce()).get(orderIdCaptor.capture()); Assert.assertEquals("订单标识列表不一致", orderIdList, orderIdCaptor.getAllValues()); } }
《Java单元测试实战》——案例集锦:Java单元测试典型案例集锦(11) https://developer.aliyun.com/article/1232048?groupCode=java