《Java单元测试实战》——案例集锦:Java单元测试典型案例集锦(3) https://developer.aliyun.com/article/1232056?groupCode=java
四、 如何测试策略模式的策略服务
1. 案例代码
在这次单元测试比赛中,很多选手都编写了策略服务类,但是没有看到任何一个选手针对策略服务类进行了单独的测试。这里,还是以负载均衡的策略服务为例说明。
1) 策略接口
首先,定义一个负载均衡策略接口。
2) 策略服务
其次,实现一个负载均衡策略服务,根据负载均衡策略类型选择对应的负载均衡策略来执行。
/** * 负载均衡服务类 */ public class LoadBalanceService { /** 负载均衡策略映射 */ private final Map<LoadBalanceStrategyType, LoadBalanceStrategy> strategyMap; /** * 构造方法 * * @param strategyList 负载均衡策略列表 */ public LoadBalanceService(List<LoadBalanceStrategy> strategyList) { strategyMap = new EnumMap<>(LoadBalanceStrategyType.class); for (LoadBalanceStrategy strategy : strategyList) { strategyMap.put(strategy.supportType(), strategy); } } /** * 选择服务节点 * * @param strategyType 策略类型 * @param serverNodeList 服务节点列表 * @param clientRequest 客户请求 * @return 服务节点 */ public ServerNode selectNode(LoadBalanceStrategyType strategyType, List<ServerNode> serverNodeList, ClientRequest clientRequest) { // 获取负载均衡策略 LoadBalanceStrategy strategy = strategyMap.get(strategyType); if (Objects.isNull(strategy)) { throw new BusinessException("负载均衡策略不存在"); } // 执行负载均衡策略 return strategy.selectNode(serverNodeList, clientRequest); } }
3) 策略实现
最后,实现一个随机负载均衡策略实现类。
/** * 随机负载均衡策略类 */ public class RandomLoadBalanceStrategy implements LoadBalanceStrategy { /** * 支持策略类型 * * @return 策略类型 */ @Override public LoadBalanceStrategyType supportType() { return LoadBalanceStrategyType.RANDOM; } /** * 选择服务节点 * * @param serverNodeList 服务节点列表 * @param clientRequest 客户请求 * @return 服务节点 */ @Override public ServerNode selectNode(List<ServerNode> serverNodeList, ClientRequest clientRequest) { // 检查节点列表 if (CollectionUtils.isEmpty(serverNodeList)) { return null; } // 计算随机序号 int totalWeight = serverNodeList.stream().mapToInt(ServerNode::getWeight).sum(); int randomIndex = RandomUtils.nextInt(0, totalWeight); // 查找对应节点 for (ServerNode serverNode : serverNodeList) { int currentWeight = serverNode.getWeight(); if (currentWeight > randomIndex) { return serverNode; } randomIndex -= currentWeight; } return null; } }
2. 方法1:联合测试法(不推荐)
很多时候,策略模式是用来优化if-else代码的。所以,采用联合测试法(策略服务和策略实现同时测试),能够最大限度地利用原有的单元测试代码。
/** * 负载均衡服务测试类 */ @RunWith(PowerMockRunner.class) @PrepareForTest(RandomUtils.class) public class LoadBalanceServiceTest { /** * 测试: 选择服务节点-正常 */ @Test public void testSelectNodeWithNormal() { // 模拟依赖方法 PowerMockito.mockStatic(RandomUtils.class); PowerMockito.when(RandomUtils.nextInt(Mockito.eq(0), Mockito.anyInt())).thenReturn(9); // 调用测试方法 ServerNode serverNode1 = new ServerNode(1L, 10); ServerNode serverNode2 = new ServerNode(2L, 20); ServerNode serverNode3 = new ServerNode(3L, 30); List<ServerNode> serverNodeList = Arrays.asList(serverNode1, serverNode2, serverNode3); ClientRequest clientRequest = new ClientRequest(); RandomLoadBalanceStrategy randomLoadBalanceStrategy = new RandomLoadBalanceStrategy(); LoadBalanceService loadBalanceService = new LoadBalanceService(Arrays.asList(randomLoadBalanceStrategy)); ServerNode serviceNode = loadBalanceService.selectNode(LoadBalanceStrategyType.RANDOM, serverNodeList, clientRequest); Assert.assertEquals("服务节点不一致", serverNode1, serviceNode); // 验证依赖方法 PowerMockito.verifyStatic(RandomUtils.class); int totalWeight = serverNodeList.stream().mapToInt(ServerNode::getWeight).sum(); RandomUtils.nextInt(0, totalWeight); } }
策略模式的联合测试法主要有以下问题:
• 策略服务依赖于策略实现,需要了解策略实现的具体逻辑,才能写出策略服务的单元测试;
• 对于策略服务来说,该单元测试并不关心策略服务的实现,这是黑盒测试而不是白盒测试。
如果我们对策略服务进行以下破坏,该单元测试并不能发现问题:
• strategyMap没有根据strategyList生成;
• strategyMap.get(strategyType)为空时,初始化一个RandomLoadBalanceStrategy。
/** * 负载均衡服务类 */ public class LoadBalanceService { /** 负载均衡策略映射 */ private final Map<LoadBalanceStrategyType, LoadBalanceStrategy> strategyMap; /** * 构造方法 * * @param strategyList 负载均衡策略列表 */ public LoadBalanceService(List<LoadBalanceStrategy> strategyList) { strategyMap = new EnumMap<>(LoadBalanceStrategyType.class); } /** * 选择服务节点 * * @param strategyType 策略类型 * @param serverNodeList 服务节点列表 * @param clientRequest 客户请求 * @return 服务节点 */ public ServerNode selectNode(LoadBalanceStrategyType strategyType, List<ServerNode> serverNodeList, ClientRequest clientRequest) { // 获取负载均衡策略 LoadBalanceStrategy strategy = strategyMap.get(strategyType); if (Objects.isNull(strategy)) { strategy = new RandomLoadBalanceStrategy(); } // 执行负载均衡策略 return strategy.selectNode(serverNodeList, clientRequest); } }
3. 方法2:独立测试法(推荐)
现在,先假设策略实现RandomLoadBalanceStrategy(随机负载均衡策略)不存在,直接对策略服务LoadBalanceService(负载均衡服务)独立测试,而且是分别对构造方法和selectNode(选择服务节点)方法进行独立测试。其中,测试构造方法是为了保证strategyMap构造逻辑没有问题,测试selectNode(选择服务节点)方法是为了保证选择策略逻辑没有问题。
/** * 负载均衡服务测试类 */ public class LoadBalanceServiceTest { /** * 测试: 构造方法 */ @Test public void testConstructor() { // 模拟依赖方法 LoadBalanceStrategy loadBalanceStrategy = Mockito.mock(LoadBalanceStrategy.class); Mockito.doReturn(LoadBalanceStrategyType.RANDOM).when(loadBalanceStrategy).supportType(); // 调用测试方法 LoadBalanceService loadBalanceService = new LoadBalanceService(Arrays.asList(loadBalanceStrategy)); Map<LoadBalanceStrategyType, LoadBalanceStrategy> strategyMap = Whitebox.getInternalState(loadBalanceService, "strategyMap"); Assert.assertEquals("策略映射大小不一致", 1, strategyMap.size()); Assert.assertEquals("策略映射对象不一致", loadBalanceStrategy, strategyMap.get(LoadBalanceStrategyType.RANDOM)); // 验证依赖方法 Mockito.verify(loadBalanceStrategy).supportType(); } /** * 测试: 选择服务节点-正常 */ @Test public void testSelectNodeWithNormal() { // 模拟依赖方法 LoadBalanceStrategy loadBalanceStrategy = Mockito.mock(LoadBalanceStrategy.class); // 模拟依赖方法: loadBalanceStrategy.supportType Mockito.doReturn(LoadBalanceStrategyType.RANDOM).when(loadBalanceStrategy).supportType(); // 模拟依赖方法: loadBalanceStrategy.selectNode ServerNode serverNode = Mockito.mock(ServerNode.class); Mockito.doReturn(serverNode).when(loadBalanceStrategy) .selectNode(Mockito.anyList(), Mockito.any(ClientRequest.class)); // 调用测试方法 List<ServerNode> serverNodeList = CastUtils.cast(Mockito.mock(List.class)); ClientRequest clientRequest = Mockito.mock(ClientRequest.class); LoadBalanceService loadBalanceService = new LoadBalanceService(Arrays.asList(loadBalanceStrategy)); Assert.assertEquals("服务节点不一致", serverNode, loadBalanceService.selectNode(LoadBalanceStrategyType.RANDOM, serverNodeList, clientRequest)); // 验证依赖方法 // 验证依赖方法: loadBalanceStrategy.supportType Mockito.verify(loadBalanceStrategy).supportType(); // 验证依赖方法: loadBalanceStrategy.selectNode Mockito.verify(loadBalanceStrategy).selectNode(serverNodeList, clientRequest); } }
其实,不只是策略模式,很多模式下都不建议联合测试,而是推荐采用独立的单元测试。因为单元测试是白盒测试——一种专注于自身代码逻辑的测试。
《Java单元测试实战》——案例集锦:Java单元测试典型案例集锦(5) https://developer.aliyun.com/article/1232054?groupCode=java