《Java单元测试实战》——案例集锦:Java单元测试典型案例集锦(5)

本文涉及的产品
云原生大数据计算服务 MaxCompute,5000CU*H 100GB 3个月
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
简介: 《Java单元测试实战》——案例集锦:Java单元测试典型案例集锦(5)

《Java单元测试实战》——案例集锦:Java单元测试典型案例集锦(4) https://developer.aliyun.com/article/1232055?groupCode=java



五、 如何测试Lambda表达式


在有些单元测试中,Lambda表达式并不一定被执行,所以导致Lambda表达式没有被测试。

 

1. 案例代码

 

这里,以从ODPS中查询用户交易订单为例说明。

 

1) 被测代码

 

交易订单查询服务,其中有一段转化订单的Lambda表达式。

 

image.png


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

相关实践学习
基于MaxCompute的热门话题分析
本实验围绕社交用户发布的文章做了详尽的分析,通过分析能得到用户群体年龄分布,性别分布,地理位置分布,以及热门话题的热度。
SaaS 模式云数据仓库必修课
本课程由阿里云开发者社区和阿里云大数据团队共同出品,是SaaS模式云原生数据仓库领导者MaxCompute核心课程。本课程由阿里云资深产品和技术专家们从概念到方法,从场景到实践,体系化的将阿里巴巴飞天大数据平台10多年的经过验证的方法与实践深入浅出的讲给开发者们。帮助大数据开发者快速了解并掌握SaaS模式的云原生的数据仓库,助力开发者学习了解先进的技术栈,并能在实际业务中敏捷的进行大数据分析,赋能企业业务。 通过本课程可以了解SaaS模式云原生数据仓库领导者MaxCompute核心功能及典型适用场景,可应用MaxCompute实现数仓搭建,快速进行大数据分析。适合大数据工程师、大数据分析师 大量数据需要处理、存储和管理,需要搭建数据仓库?学它! 没有足够人员和经验来运维大数据平台,不想自建IDC买机器,需要免运维的大数据平台?会SQL就等于会大数据?学它! 想知道大数据用得对不对,想用更少的钱得到持续演进的数仓能力?获得极致弹性的计算资源和更好的性能,以及持续保护数据安全的生产环境?学它! 想要获得灵活的分析能力,快速洞察数据规律特征?想要兼得数据湖的灵活性与数据仓库的成长性?学它! 出品人:阿里云大数据产品及研发团队专家 产品 MaxCompute 官网 https://www.aliyun.com/product/odps&nbsp;
相关文章
|
11天前
|
测试技术 开发者 UED
探索软件测试的深度:从单元测试到自动化测试
【10月更文挑战第30天】在软件开发的世界中,测试是确保产品质量和用户满意度的关键步骤。本文将深入探讨软件测试的不同层次,从基本的单元测试到复杂的自动化测试,揭示它们如何共同构建一个坚实的质量保证体系。我们将通过实际代码示例,展示如何在开发过程中实施有效的测试策略,以确保软件的稳定性和可靠性。无论你是新手还是经验丰富的开发者,这篇文章都将为你提供宝贵的见解和实用技巧。
|
1月前
|
分布式计算 大数据 Spark
大数据-95 Spark 集群 SparkSQL Action与Transformation操作 详细解释与测试案例(二)
大数据-95 Spark 集群 SparkSQL Action与Transformation操作 详细解释与测试案例(二)
39 1
|
2月前
|
前端开发 机器人 测试技术
【RF案例】Web自动化测试弹窗处理
在进行Web自动化测试时,常会遇到不同类型的弹窗,如ajax、iframe、新窗口及alert/Confirm等。这些弹窗可通过Selenium进行定位与处理。其中,ajax弹窗直接定位处理;iframe需先选中再操作;新窗口类似iframe处理;而alert/Confirm则需特殊方法应对。在Robot Framework中,需先定义并获取窗口后使用特定关键字处理。此外,还有部分div弹窗需在消失前快速定位。希望本文能帮助大家更好地处理各类弹窗。
41 6
【RF案例】Web自动化测试弹窗处理
|
1月前
|
存储 SQL 分布式计算
大数据-95 Spark 集群 SparkSQL Action与Transformation操作 详细解释与测试案例(一)
大数据-95 Spark 集群 SparkSQL Action与Transformation操作 详细解释与测试案例(一)
37 0
|
2月前
|
IDE 测试技术 持续交付
Python自动化测试与单元测试框架:提升代码质量与效率
【9月更文挑战第3天】随着软件行业的迅速发展,代码质量和开发效率变得至关重要。本文探讨了Python在自动化及单元测试中的应用,介绍了Selenium、Appium、pytest等自动化测试框架,以及Python标准库中的unittest单元测试框架。通过详细阐述各框架的特点与使用方法,本文旨在帮助开发者掌握编写高效测试用例的技巧,提升代码质量与开发效率。同时,文章还提出了制定测试计划、持续集成与测试等实践建议,助力项目成功。
84 5
|
3月前
|
测试技术 C# 开发者
“代码守护者:详解WPF开发中的单元测试策略与实践——从选择测试框架到编写模拟对象,全方位保障你的应用程序质量”
【8月更文挑战第31天】单元测试是确保软件质量的关键实践,尤其在复杂的WPF应用中更为重要。通过为每个小模块编写独立测试用例,可以验证代码的功能正确性并在早期发现错误。本文将介绍如何在WPF项目中引入单元测试,并通过具体示例演示其实施过程。首先选择合适的测试框架如NUnit或xUnit.net,并利用Moq模拟框架隔离外部依赖。接着,通过一个简单的WPF应用程序示例,展示如何模拟`IUserRepository`接口并验证`MainViewModel`加载用户数据的正确性。这有助于确保代码质量和未来的重构与扩展。
75 0
|
3月前
|
测试技术 Java Spring
Spring 框架中的测试之道:揭秘单元测试与集成测试的双重保障,你的应用真的安全了吗?
【8月更文挑战第31天】本文以问答形式深入探讨了Spring框架中的测试策略,包括单元测试与集成测试的有效编写方法,及其对提升代码质量和可靠性的重要性。通过具体示例,展示了如何使用`@MockBean`、`@SpringBootTest`等注解来进行服务和控制器的测试,同时介绍了Spring Boot提供的测试工具,如`@DataJpaTest`,以简化数据库测试流程。合理运用这些测试策略和工具,将助力开发者构建更为稳健的软件系统。
54 0
|
3月前
|
测试技术 Java
全面保障Struts 2应用质量:掌握单元测试与集成测试的关键策略
【8月更文挑战第31天】Struts 2 的测试策略结合了单元测试与集成测试。单元测试聚焦于单个组件(如 Action 类)的功能验证,常用 Mockito 模拟依赖项;集成测试则关注组件间的交互,利用 Cactus 等框架确保框架拦截器和 Action 映射等按预期工作。通过确保高测试覆盖率并定期更新测试用例,可以提升应用的整体稳定性和质量。
73 0
|
3月前
|
测试技术 数据库
探索JSF单元测试秘籍!如何让您的应用更稳固、更高效?揭秘成功背后的测试之道!
【8月更文挑战第31天】在 JavaServer Faces(JSF)应用开发中,确保代码质量和可维护性至关重要。本文详细介绍了如何通过单元测试实现这一目标。首先,阐述了单元测试的重要性及其对应用稳定性的影响;其次,提出了提高 JSF 应用可测试性的设计建议,如避免直接访问外部资源和使用依赖注入;最后,通过一个具体的 `UserBean` 示例,展示了如何利用 JUnit 和 Mockito 框架编写有效的单元测试。通过这些方法,不仅能够确保代码质量,还能提高开发效率和降低维护成本。
50 0
|
3月前
|
Java 测试技术 API
SpringBoot单元测试快速写法问题之复杂的业务逻辑设计有效的单元测试如何解决
SpringBoot单元测试快速写法问题之复杂的业务逻辑设计有效的单元测试如何解决