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

简介: 《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的热门话题分析
Apsara Clouder大数据专项技能认证配套课程:基于MaxCompute的热门话题分析
相关文章
|
10月前
|
算法 IDE Java
Java 项目实战之实际代码实现与测试调试全过程详解
本文详细讲解了Java项目的实战开发流程,涵盖项目创建、代码实现(如计算器与汉诺塔问题)、单元测试(使用JUnit)及调试技巧(如断点调试与异常排查),帮助开发者掌握从编码到测试调试的完整技能,提升Java开发实战能力。
882 0
|
缓存 监控 负载均衡
如何提升 API 性能:来自 Java 和测试开发者的优化建议
本文探讨了如何优化API响应时间,提升用户体验。通过缓存(如Redis/Memcached)、减少数据负载(REST过滤字段或GraphQL精确请求)、负载均衡(Nginx/AWS等工具)、数据压缩(Gzip/Brotli)、限流节流、监控性能(Apipost/New Relic等工具)、升级基础设施、减少第三方依赖、优化数据库查询及采用异步处理等方式,可显著提高API速度。快速响应的API不仅让用户满意,还能增强应用整体性能。
|
12月前
|
缓存 NoSQL Java
Java Redis 面试题集锦 常见高频面试题目及解析
本文总结了Redis在Java中的核心面试题,包括数据类型操作、单线程高性能原理、键过期策略及分布式锁实现等关键内容。通过Jedis代码示例展示了String、List等数据类型的操作方法,讲解了惰性删除和定期删除相结合的过期策略,并提供了Spring Boot配置Redis过期时间的方案。文章还探讨了缓存穿透、雪崩等问题解决方案,以及基于Redis的分布式锁实现,帮助开发者全面掌握Redis在Java应用中的实践要点。
646 6
|
11月前
|
安全 Java 测试技术
Java 项目实战中现代技术栈下代码实现与测试调试的完整流程
本文介绍基于Java 17和Spring技术栈的现代化项目开发实践。项目采用Gradle构建工具,实现模块化DDD分层架构,结合Spring WebFlux开发响应式API,并应用Record、Sealed Class等新特性。测试策略涵盖JUnit单元测试和Testcontainers集成测试,通过JFR和OpenTelemetry实现性能监控。部署阶段采用Docker容器化和Kubernetes编排,同时展示异步处理和反应式编程的性能优化。整套方案体现了现代Java开发的最佳实践,包括代码实现、测试调试
362 0
|
11月前
|
人工智能 Java 测试技术
Java or Python?测试开发工程师如何选择合适的编程语言?
测试工程师如何选择编程语言?Java 还是 Python?多位资深专家分享建议:Python 入门简单、开发效率高,适合新手及自动化测试;Java 生态成熟,适合大型项目和平台开发。建议结合公司技术栈、个人基础及发展方向选择。长远来看,两者兼通更佳,同时关注 Go 等新兴语言。快速学习与实践才是关键。
|
Java 流计算
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
268 2
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
|
测试技术 开发者 UED
探索软件测试的深度:从单元测试到自动化测试
【10月更文挑战第30天】在软件开发的世界中,测试是确保产品质量和用户满意度的关键步骤。本文将深入探讨软件测试的不同层次,从基本的单元测试到复杂的自动化测试,揭示它们如何共同构建一个坚实的质量保证体系。我们将通过实际代码示例,展示如何在开发过程中实施有效的测试策略,以确保软件的稳定性和可靠性。无论你是新手还是经验丰富的开发者,这篇文章都将为你提供宝贵的见解和实用技巧。
|
Java 测试技术 Maven
Java一分钟之-PowerMock:静态方法与私有方法测试
通过本文的详细介绍,您可以使用PowerMock轻松地测试Java代码中的静态方法和私有方法。PowerMock通过扩展Mockito,提供了强大的功能,帮助开发者在复杂的测试场景中保持高效和准确的单元测试。希望本文对您的Java单元测试有所帮助。
2442 2
|
Java 程序员 测试技术
Java|让 JUnit4 测试类自动注入 logger 和被测 Service
本文介绍如何通过自定义 IDEA 的 JUnit4 Test Class 模板,实现生成测试类时自动注入 logger 和被测 Service。
338 5
|
存储 人工智能 Java
将 Spring AI 与 LLM 结合使用以生成 Java 测试
AIDocumentLibraryChat 项目通过 GitHub URL 为指定的 Java 类生成测试代码,支持 granite-code 和 deepseek-coder-v2 模型。项目包括控制器、服务和配置,能处理源代码解析、依赖加载及测试代码生成,旨在评估 LLM 对开发测试的支持能力。
681 1