page contents

Java教程——Java单元测试典型案例集锦

本文讲述了Java教程——Java单元测试典型案例集锦!具有很好的参考价值,希望对大家有所帮助。一起跟随六星小编过来看看吧,具体如下:

attachments-2023-09-2aK73wrb650e3ef96f01b.jpg

本文讲述了Java教程——Java单元测试典型案例集锦!具有很好的参考价值,希望对大家有所帮助。一起跟随六星小编过来看看吧,具体如下:

阿里巴巴CTO线卓越工程小组举办了阿里巴巴第一届单元测试比赛《这!就是单测》并取得了圆满成功。本人有幸作为评委,在仔细地阅读了各个小组的单元测试用例后,发现了两大单元测试问题:

1、无效验证问题:


不进行有效地验证数据对象、抛出异常和调用方法。


2、测试方法问题:


不知道如何测试某些典型案例,要么错误地测试、要么不进行测试、要么利用集成测试来保证覆盖率。比如:


错误地测试:利用测试返回节点占比来测试随机负载均衡策略;


不进行测试:没有人针对虚基类进行单独地测试;


利用集成测试:很多案例中,直接注入真实依赖对象,然后一起进行集成测试。

针对无效验证问题,在我的文章《那些年,我们写过的无效单元测试》中,介绍了如何识别和解决单元测试无效验证问题,这里就不再累述了。在本文中,作者收集了一些的Java单元测试典型案例,主要是为了解决这个测试方法问题。

一、如何测试不可达代码


在程序代码中,由于无法满足进入条件,永远都不会执行到的代码,我们称之为"不可达代码"。不可达代码的危害主要有:复杂了代码逻辑,增加了代码运行和维护成本。不可达代码是可以由单元测试检测出来的——不管如何构造单元测试用例,都无法覆盖到不可达代码。

1.1. 案例代码


在下面的案例代码中,就存在一段不可达代码。

/**

 * 交易订单服务类

 */

@Service

public class TradeOrderService {

    /** 注入依赖对象 */

    /** 交易订单DAO */

    @Autowired

    private TradeOrderDAO tradeOrderDAO;


    /**

     * 查询交易订单

     * 

     * @param orderQuery 订单查询

     * @return 交易订单分页

     */

    public PageDataVO<TradeOrderVO> queryTradeOrder(TradeOrderQueryVO orderQuery) {

        // 查询交易订单

        // 查询交易订单: 总共数量

        Long totalSize = tradeOrderDAO.countByCondition(orderQuery);

        // 查询交易订单: 数据列表

        List<TradeOrderVO> dataList = null;

        if (NumberHelper.isPositive(totalSize)) {

            List<TradeOrderDO> tradeOrderList = tradeOrderDAO.queryByCondition(orderQuery);

            if (CollectionUtils.isNotEmpty(tradeOrderList)) {

                dataList = convertTradeOrders(tradeOrderList);

            }

        }


        // 返回分页数据

        return new PageDataVO<>(totalSize, dataList);

    }


    /**

     * 转化交易订单列表

     * 

     * @param tradeOrderList 交易订单DO列表

     * @return 交易订单VO列表

     */

    private static List<TradeOrderVO> convertTradeOrders(List<TradeOrderDO> tradeOrderList) {

        // 检查订单列表

        if (CollectionUtils.isEmpty(tradeOrderList)) {

            return Collections.emptyList();

        }


        // 转化订单列表

        return tradeOrderList.stream().map(TradeOrderService::convertTradeOrder)

            .collect(Collectors.toList());

    }


    /**

     * 转化交易订单

     * 

     * @param tradeOrder 交易订单DO

     * @return 交易订单VO

     */

    private static TradeOrderVO convertTradeOrder(TradeOrderDO tradeOrder) {

        TradeOrderVO tradeOrderVO = new TradeOrderVO();

        tradeOrderVO.setId(tradeOrder.getId());

        // ...

        return tradeOrderVO;

    }

}


由于方法convertTradeOrders(转化交易订单列表)传入的参数tradeOrderList(交易订单列表)不可能为空,所以“检查订单列表”这段代码是不可达代码。

// 检查订单列表

        if (CollectionUtils.isEmpty(tradeOrderList)) {

            return Collections.emptyList();

        }



1.2. 方案1:删除不可达代码(推荐)

最简单的方法,就是删除方法convertTradeOrders(转化交易订单列表)中的不可达代码。

/**

 * 转化交易订单列表

 * 

 * @param tradeOrderList 交易订单DO列表

 * @return 交易订单VO列表

 */

private static List<TradeOrderVO> convertTradeOrders(List<TradeOrderDO> tradeOrderList) {

    return tradeOrderList.stream().map(TradeOrderService2::convertTradeOrder)

        .collect(Collectors.toList());

}

1.3. 方案2:利用不可达代码(推荐)


还有一种方法,把不可达代码利用起来,可以降低方法queryTradeOrder(查询交易订单)的代码复杂度。

/**

 * 查询交易订单

 * 

 * @param orderQuery 订单查询

 * @return 交易订单分页

 */

public PageDataVO<TradeOrderVO> queryTradeOrder(TradeOrderQueryVO orderQuery) {

    // 查询交易订单

    // 查询交易订单: 总共数量

    Long totalSize = tradeOrderDAO.countByCondition(orderQuery);

    // 查询交易订单: 数据列表

    List<TradeOrderVO> dataList = null;

    if (NumberHelper.isPositive(totalSize)) {

        List<TradeOrderDO> tradeOrderList = tradeOrderDAO.queryByCondition(orderQuery);

        dataList = convertTradeOrders(tradeOrderList);

    }


    // 返回分页数据

    return new PageDataVO<>(totalSize, dataList);

}


1.4. 方案3:测试不可达代码(不推荐)


对于一些祖传代码,有些小伙伴不敢删除代码。在某些情况下,可以针对不可达代码进行单独测试。

/**

 * 测试: 转化交易订单列表-交易订单列表为空

 * 

 * @throws Exception 异常信息

 */

@Test

public void testConvertTradeOrdersWithTradeOrderListEmpty() throws Exception {

    List<TradeOrderDO> tradeOrderList = null;

    Assert.assertSame("交易订单列表不为空", Collections.emptyList(),

        Whitebox.invokeMethod(TradeOrderService1.class, "convertTradeOrders", tradeOrderList));

}


二、如何测试内部的构造方法


在这次单元测试总决赛中,有一个随机负载均衡策略,需要针对Random(随机数)进行单元测试。

2.1. 代码案例


按照题目要求,编写了一个简单的随机负载均衡策略。

/**

 * 随机负载均衡策略类

 */

public class RandomLoadBalanceStrategy implements LoadBalanceStrategy {

    /**

     * 选择服务节点

     * 

     * @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 = new Random().nextInt(totalWeight);


        // 查找对应节点

        for (ServerNode serverNode : serverNodeList) {

            int currentWeight = serverNode.getWeight();

            if (currentWeight > randomIndex) {

                return serverNode;

            }

            randomIndex -= currentWeight;

        }

        return null;

    }

}


2.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方法。

2.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无法统计单元测试的覆盖率。


2.4. 方法3:工具方法法(推荐)


其实,随机数生成,还有很多工具方法,我们可以利用工具方法RandomUtils.nextInt代替构造方法。

2.4.1. 重构代码

/**

 * 随机负载均衡策略类

 */

public class RandomLoadBalanceStrategy implements LoadBalanceStrategy {

    /**

     * 选择服务节点

     * 

     * @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.4.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);

    }

}


2.5. 方法4:注入对象法(推荐)


如果不愿意使用工具方法,也可以注入依赖对象,我们可以利用RandomProvider(随机数提供者)来代替构造方法。

2.5.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.5.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圈子。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Java入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2023-03-2AoKIjPQ64014b4ad30a3.jpg

  • 发表于 2023-09-23 09:27
  • 阅读 ( 227 )
  • 分类:Java开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
王昭君
王昭君

209 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1316 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章