《Java单元测试实战》——案例集锦:Java单元测试典型案例集锦(1) https://developer.aliyun.com/article/1232058?groupCode=java
二、 如何测试内部的构造方法
在这次单元测试总决赛中,有一个随机负载均衡策略,需要针对Random(随机数)进行单元测试。
1. 代码案例
按照题目要求,编写了一个简单的随机负载均衡策略。
2. 方法1:直接测试法(不推荐)
有些参赛选手,不知道如何测试随机数(主要原因是因为不知道如何Mock构造方法),所以直接利用测试返回节点占比来测试随机负载均衡策略。
/** * 随机负载均衡策略测试类 */ @RunWith(MockitoJUnitRunner.class) public class RandomLoadBalanceStrategyTest { /** 定义测试对象 */ /** 随机负载均衡策略 */ @InjectMocks private RandomLoadBalanceStrategy randomLoadBalanceStrategy; /** * 测试: 选择服务节点-随机 * * @throws Exception 异常信息 */ @Test public void testSelectNodeWithRandom() throws Exception { int nodeCount1 = 0; int nodeCount2 = 0; int nodeCount3 = 0; 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(); for (int i = 0; i < 1000; i++) { ServerNode serviceNode = randomLoadBalanceStrategy.selectNode(serverNodeList, clientRequest); if (serviceNode == serverNode1) { nodeCount1++; } else if (serviceNode == serverNode2) { nodeCount2++; } else if (serviceNode == serverNode3) { nodeCount3++; } } Assert.assertEquals("节点1占比不一致", serverNode1.getWeight() / 60.0D, nodeCount1 / 1000.0D, 1E-3D); Assert.assertEquals("节点2占比不一致", serverNode2.getWeight() / 60.0D, nodeCount2 / 1000.0D, 1E-3D); Assert.assertEquals("节点3占比不一致", serverNode3.getWeight() / 60.0D, nodeCount3 / 1000.0D, 1E-3D); } }
这个测试用例主要存在3个问题:
• 执行时间长:被测方法需要被执行1000遍;
• 不一定通过:由于随机数是随机,并不一定保证比例,所以导致测试用例并不一定通过;
• 测试目标变更:单测测试的测试目标应该是负载均衡逻辑,现在感觉测试目标变成了Random方法。
3. 方法2:直接mock法(不推荐)
用过PowerMockito高级功能的,知道如何去Mock构造方法。
/** * 随机负载均衡策略测试类 */ @RunWith(PowerMockRunner.class) @PrepareForTest(RandomLoadBalanceStrategy.class) public class RandomLoadBalanceStrategyTest { /** 定义测试对象 */ /** 随机负载均衡策略 */ @InjectMocks private RandomLoadBalanceStrategy randomLoadBalanceStrategy; /** * 测试: 选择服务节点-第一个节点 * * @throws Exception 异常信息 */ @Test public void testSelectNodeWithFirstNode() throws Exception { // 模拟依赖方法 Random random = Mockito.mock(Random.class); Mockito.doReturn(9).when(random).nextInt(Mockito.anyInt()); PowerMockito.whenNew(Random.class).withNoArguments().thenReturn(random); // 调用测试方法 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(); ServerNode serviceNode = randomLoadBalanceStrategy.selectNode(serverNodeList, clientRequest); Assert.assertEquals("服务节点不一致", serverNode1, serviceNode); // 验证依赖方法 int totalWeight = serverNodeList.stream().mapToInt(ServerNode::getWeight).sum(); Mockito.verify(random).nextInt(totalWeight); } }
但是,这个测试用例也存在问题:需要把RandomLoadBalanceStrategy加到@PrepareForTest注解中,导致Jacoco无法统计单元测试的覆盖率。
4. 方法3:工具方法法(推荐)
其实,随机数生成,还有很多工具方法,我们可以利用工具方法RandomUtils.nextInt代替构造方法。
1) 重构代码
1) 测试用例
/** * 随机负载均衡策略测试类 */ @RunWith(PowerMockRunner.class) @PrepareForTest(RandomUtils.class) public class RandomLoadBalanceStrategyTest { /** 定义测试对象 */ /** 随机负载均衡策略 */ @InjectMocks private RandomLoadBalanceStrategy randomLoadBalanceStrategy; /** * 测试: 选择服务节点-第一个节点 */ @Test public void testSelectNodeWithFirstNode() { // 模拟依赖方法 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(); ServerNode serviceNode = randomLoadBalanceStrategy.selectNode(serverNodeList, clientRequest); Assert.assertEquals("服务节点不一致", serverNode1, serviceNode); // 验证依赖方法 PowerMockito.verifyStatic(RandomUtils.class); int totalWeight = serverNodeList.stream().mapToInt(ServerNode::getWeight).sum(); RandomUtils.nextInt(0, totalWeight); } }
5. 方法4:注入对象法(推荐)
如果不愿意使用工具方法,也可以注入依赖对象,我们可以利用RandomProvider(随机数提供者)来代替构造方法。
1) 重构代码
2) 测试用例
/** * 随机负载均衡策略测试类 */ @RunWith(PowerMockRunner.class) @PrepareForTest(RandomUtils.class) public class RandomLoadBalanceStrategyTest { /** 定义测试对象 */ /** 随机负载均衡策略 */ @InjectMocks private RandomLoadBalanceStrategy randomLoadBalanceStrategy; /** * 测试: 选择服务节点-第一个节点 */ @Test public void testSelectNodeWithFirstNode() { // 模拟依赖方法 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(); ServerNode serviceNode = randomLoadBalanceStrategy.selectNode(serverNodeList, clientRequest); Assert.assertEquals("服务节点不一致", serverNode1, serviceNode); // 验证依赖方法 PowerMockito.verifyStatic(RandomUtils.class); int totalWeight = serverNodeList.stream().mapToInt(ServerNode::getWeight).sum(); RandomUtils.nextInt(0, totalWeight); } }
5. 方法4:注入对象法(推荐)
如果不愿意使用工具方法,也可以注入依赖对象,我们可以利用RandomProvider(随机数提供者)来代替构造方法。
1) 重构代码
/** * 随机负载均衡策略类 */ public class RandomLoadBalanceStrategy implements LoadBalanceStrategy { /** 注入依赖对象 */ /** 随机数提供者 */ @Autowired private RandomProvider randomProvider; /** * 选择服务节点 * * @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 = randomProvider.nextInt(totalWeight); // 查找对应节点 for (ServerNode serverNode : serverNodeList) { int currentWeight = serverNode.getWeight(); if (currentWeight > randomIndex) { return serverNode; } randomIndex -= currentWeight; } return null; } }
2) 测试用例
/** * 随机负载均衡策略测试类 */ @RunWith(MockitoJUnitRunner.class) public class RandomLoadBalanceStrategyTest { /** 模拟依赖方法 */ /** 随机数提供者 */ @Mock private RandomProvider randomProvider; /** 定义测试对象 */ /** 随机负载均衡策略 */ @InjectMocks private RandomLoadBalanceStrategy randomLoadBalanceStrategy; /** * 测试: 选择服务节点-第一个节点 */ @Test public void testSelectNodeWithFirstNode() { // 模拟依赖方法 Mockito.doReturn(9).when(randomProvider).nextInt(Mockito.anyInt()); // 调用测试方法 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(); ServerNode serviceNode = randomLoadBalanceStrategy.selectNode(serverNodeList, clientRequest); Assert.assertEquals("服务节点不一致", serverNode1, serviceNode); // 验证依赖方法 int totalWeight = serverNodeList.stream().mapToInt(ServerNode::getWeight).sum(); Mockito.verify(randomProvider).nextInt(totalWeight); } }
《Java单元测试实战》——案例集锦:Java单元测试典型案例集锦(3) https://developer.aliyun.com/article/1232056?groupCode=java