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

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

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



九、 如何测试相同返回值的代码分支

 

在业务代码中,经常会出现不同的代码分支返回相同值的情况。这个时候,仅通过验证返回值是没法判断是否命中了对应的代码分支的。那么,这种情况如何进行单元测试呢?

 

1. 案例代码

 

这里,以灰度发布服务判定方法为例说明。


/**
 * 灰度发布服务类
 */
@Slf4j
@Service
public class GrayReleaseService {
    /** 定义静态常量 */
    /** 灰度发布分子 */
    private static final long GRAY_NUMERATOR = 0L;
    /** 灰度发布分母 */
    private static final long GRAY_DENOMINATOR = 10000L;
    /** 注入依赖对象 */
    /** 灰度发布配置 */
    @Autowired
    private GrayReleaseConfig grayReleaseConfig;
    /**
     * 是否灰度发布
     * 
     * @param key 主键
     * @param channel 渠道
     * @param userId 用户标识
     * @param value 取值
     * @return 判断结果
     */
    public boolean isGrayRelease(String key, String channel, String userId, Object value) {
        // 判断灰度发布取值
        if (Objects.isNull(value)) {
            log.info("命中灰度取值为空");
            return false;
        }
        // 获取灰度发布映射
        Map<String, GrayReleaseItem> grayReleaseMap = grayReleaseConfig.getGrayReleaseMap();
        if (MapUtils.isEmpty(grayReleaseMap)) {
            log.info("命中灰度发布映射为空");
            return false;
        }
        // 获取灰度发布项
        GrayReleaseItem grayReleaseItem = grayReleaseMap.get(key);
        if (Objects.isNull(grayReleaseItem)) {
            log.info("命中灰度发布映项为空: key={}", key);
            return false;
        }
        // 判断渠道白名单
        Set<String> channelWhiteSet = grayReleaseItem.getChannelWhiteSet();
        if (CollectionUtils.isNotEmpty(channelWhiteSet) && channelWhiteSet.contains(channel)) {
            log.info("命中渠道白名单灰度: key={}, channel={}", key, channel);
            return true;
        }
        // 判断用户白名单
        Set<String> userIdWhiteSet = grayReleaseItem.getUserIdWhiteSet();
        if (CollectionUtils.isNotEmpty(userIdWhiteSet) && userIdWhiteSet.contains(userId)) {
            log.info("命中用户白名单灰度: key={}, userId={}", key, userId);
            return true;
        }
        // 判断灰度发布比例
        long grayNumerator = Optional.ofNullable(grayReleaseItem.getGrayNumerator()).orElse(GRAY_NUMERATOR);
        long grayDenominator = Optional.ofNullable(grayReleaseItem.getGrayDenominator()).orElse(GRAY_DENOMINATOR);
        boolean isGray = Math.abs(Objects.hashCode(value)) % grayDenominator <= grayNumerator;
        log.info("命中灰度发布比例: key={}, value={}, isGray={}", key, value, isGray);
        return isGray;
    }
}

2. 普通测试法(不推荐)

 

这里,只测试了命中渠道白名单的情况。


/**
 * 灰度发布服务测试类
 */
@RunWith(MockitoJUnitRunner.class)
public class GrayReleaseServiceTest {
    /** 模拟依赖方法 */
    /** 灰度发布配置 */
    @Mock
    private GrayReleaseConfig grayReleaseConfig;
    /** 定义测试对象 */
    /** 灰度发布服务 */
    @InjectMocks
    private GrayReleaseService grayReleaseService;
    /**
     * 测试: 是否灰度发布-命中渠道白名单
     */
    @Test
    public void testIsGrayReleaseWithChannelWhiteSet() {
        // 模拟依赖方法
        GrayReleaseItem grayReleaseItem = new GrayReleaseItem();
        grayReleaseItem.setChannelWhiteSet(Sets.newHashSet("alipay"));
        grayReleaseItem.setUserIdWhiteSet(Sets.newHashSet("123456"));
        Map<String, GrayReleaseItem> grayReleaseMap = Maps.newHashMap();
        grayReleaseMap.put("test", grayReleaseItem);
        Mockito.doReturn(grayReleaseMap).when(grayReleaseConfig).getGrayReleaseMap();
        // 调用测试方法
        String key = "test";
        String channel = "alipay";
        String userId = "123456";
        Object value = 1234567890L;
        Assert.assertTrue("判断结果不为真", grayReleaseService.isGrayRelease(key, channel, userId, value));
        // 验证依赖方法
        Mockito.verify(grayReleaseConfig).getGrayReleaseMap();
    }
}

在一次代码重构中,把“判断用户白名单”放在“判断渠道白名单”之前,这个单元测试是无法检测出来的。

 

3. 验证测试法(推荐)

 

通过对mock方法的验证,可以相对准确地确定命中的代码分支。


/**
 * 灰度发布服务测试类
 */
@RunWith(MockitoJUnitRunner.class)
public class GrayReleaseServiceTest {
    /** 模拟依赖方法 */
    /** 灰度发布配置 */
    @Mock
    private GrayReleaseConfig grayReleaseConfig;
    /** 定义测试对象 */
    /** 灰度发布服务 */
    @InjectMocks
    private GrayReleaseService grayReleaseService;
    /**
     * 测试: 是否灰度发布-命中渠道白名单
     */
    @Test
    public void testIsGrayReleaseWithChannelWhiteSet() {
        // 模拟依赖方法
        // 模拟依赖方法: grayReleaseItem.getChannelWhiteSet
        GrayReleaseItem grayReleaseItem = Mockito.mock(GrayReleaseItem.class);
        Mockito.doReturn(Sets.newHashSet("alipay")).when(grayReleaseItem).getChannelWhiteSet();
        // 模拟依赖方法: grayReleaseItem.getUserIdWhiteSet
        Mockito.doReturn(Sets.newHashSet("123456")).when(grayReleaseItem).getUserIdWhiteSet();
        // 模拟依赖方法: grayReleaseConfig.getGrayReleaseMap
        Map<String, GrayReleaseItem> grayReleaseMap = Maps.newHashMap();
        grayReleaseMap.put("test", grayReleaseItem);
        Mockito.doReturn(grayReleaseMap).when(grayReleaseConfig).getGrayReleaseMap();
        // 调用测试方法
        String key = "test";
        String channel = "alipay";
        String userId = "123456";
        Object value = 1234567890L;
        Assert.assertTrue("判断结果不为真", grayReleaseService.isGrayRelease(key, channel, userId, value));
        // 验证依赖方法
        // 验证依赖方法: grayReleaseConfig.getGrayReleaseMap
        Mockito.verify(grayReleaseConfig).getGrayReleaseMap();
        // 验证依赖方法: grayReleaseItem.getChannelWhiteSet
        Mockito.verify(grayReleaseItem).getChannelWhiteSet();
        // 验证依赖对象
        Mockito.verifyNoMoreInteractions(grayReleaseConfig, grayReleaseItem);
    }
}

如果把“判断用户白名单”放在“判断渠道白名单”之前,这个单元测试会报出以下错误日志:

image.png

错误日志告诉我们,grayReleaseItem.getChannelWhiteSet方法并没有被调用,所以不可能命中渠道白名单代码分支。

 

4. 日志测试法(推荐)

 

对于有日志打印的代码,可以通过验证日志方法来确定命中的代码分支,而且这种验证方法是非常简单直白的。如果没有日志打印,我们也可以添加日志打印(可能会涉及日志存储成本的增加)。

 

/**
 * 灰度发布服务测试类
 */
@RunWith(MockitoJUnitRunner.class)
public class GrayReleaseServiceTest {
    /** 模拟依赖方法 */
    /** 日志器 */
    @Mock
    private Logger log;
    /** 灰度发布配置 */
    @Mock
    private GrayReleaseConfig grayReleaseConfig;
    /** 定义测试对象 */
    /** 灰度发布服务 */
    @InjectMocks
    private GrayReleaseService grayReleaseService;
    /**
     * 在测试前
     */
    @Before
    public void beforeTest() {
        FieldHelper.writeStaticFinalField(GrayReleaseService.class, "log", log);
    }
    /**
     * 测试: 是否灰度发布-命中渠道白名单
     */
    @Test
    public void testIsGrayReleaseWithChannelWhiteSet() {
        // 模拟依赖方法
        GrayReleaseItem grayReleaseItem = new GrayReleaseItem();
        grayReleaseItem.setChannelWhiteSet(Sets.newHashSet("alipay"));
        grayReleaseItem.setUserIdWhiteSet(Sets.newHashSet("123456"));
        Map<String, GrayReleaseItem> grayReleaseMap = Maps.newHashMap();
        grayReleaseMap.put("test", grayReleaseItem);
        Mockito.doReturn(grayReleaseMap).when(grayReleaseConfig).getGrayReleaseMap();
        // 调用测试方法
        String key = "test";
        String channel = "alipay";
        String userId = "123456";
        Object value = 1234567890L;
        Assert.assertTrue("判断结果不为真", grayReleaseService.isGrayRelease(key, channel, userId, value));
        // 验证依赖方法
        // 验证依赖方法: grayReleaseConfig.getGrayReleaseMap
        Mockito.verify(grayReleaseConfig).getGrayReleaseMap();
        // 验证依赖方法: log.info
        Mockito.verify(log).info("命中渠道白名单灰度: key={}, channel={}", key, channel);
        // 验证依赖对象
        Mockito.verifyNoInteractions(log, grayReleaseConfig);
    }
}

如果把“判断用户白名单”放在“判断渠道白名单”之前,这个单元测试会报出以下错误日志:

image.png

错误日志告诉我们,我们期望命中渠道白名单灰度代码分支,实际却命中的是用户白名单灰度代码分支。




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

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
缓存 NoSQL Java
Java Redis 面试题集锦 常见高频面试题目及解析
本文总结了Redis在Java中的核心面试题,包括数据类型操作、单线程高性能原理、键过期策略及分布式锁实现等关键内容。通过Jedis代码示例展示了String、List等数据类型的操作方法,讲解了惰性删除和定期删除相结合的过期策略,并提供了Spring Boot配置Redis过期时间的方案。文章还探讨了缓存穿透、雪崩等问题解决方案,以及基于Redis的分布式锁实现,帮助开发者全面掌握Redis在Java应用中的实践要点。
818 6
|
前端开发 机器人 测试技术
【RF案例】Web自动化测试弹窗处理
在进行Web自动化测试时,常会遇到不同类型的弹窗,如ajax、iframe、新窗口及alert/Confirm等。这些弹窗可通过Selenium进行定位与处理。其中,ajax弹窗直接定位处理;iframe需先选中再操作;新窗口类似iframe处理;而alert/Confirm则需特殊方法应对。在Robot Framework中,需先定义并获取窗口后使用特定关键字处理。此外,还有部分div弹窗需在消失前快速定位。希望本文能帮助大家更好地处理各类弹窗。
632 6
【RF案例】Web自动化测试弹窗处理
|
人工智能 供应链 安全
AI辅助安全测试案例某电商-供应链平台平台安全漏洞
【11月更文挑战第13天】该案例介绍了一家电商供应链平台如何利用AI技术进行全面的安全测试,包括网络、应用和数据安全层面,发现了多个潜在漏洞,并采取了有效的修复措施,提升了平台的整体安全性。
934 4
|
测试技术 开发者 UED
探索软件测试的深度:从单元测试到自动化测试
【10月更文挑战第30天】在软件开发的世界中,测试是确保产品质量和用户满意度的关键步骤。本文将深入探讨软件测试的不同层次,从基本的单元测试到复杂的自动化测试,揭示它们如何共同构建一个坚实的质量保证体系。我们将通过实际代码示例,展示如何在开发过程中实施有效的测试策略,以确保软件的稳定性和可靠性。无论你是新手还是经验丰富的开发者,这篇文章都将为你提供宝贵的见解和实用技巧。
|
JSON Dubbo 测试技术
单元测试问题之增加JCode5插件生成的测试代码的可信度如何解决
单元测试问题之增加JCode5插件生成的测试代码的可信度如何解决
388 2
单元测试问题之增加JCode5插件生成的测试代码的可信度如何解决
|
分布式计算 大数据 Spark
大数据-95 Spark 集群 SparkSQL Action与Transformation操作 详细解释与测试案例(二)
大数据-95 Spark 集群 SparkSQL Action与Transformation操作 详细解释与测试案例(二)
287 1
|
测试技术 API 开发者
.NET单元测试框架大比拼:MSTest、xUnit与NUnit的实战较量与选择指南
【8月更文挑战第28天】单元测试是软件开发中不可或缺的一环,它能够确保代码的质量和稳定性。在.NET生态系统中,MSTest、xUnit和NUnit是最为流行的单元测试框架。本文将对这三种测试框架进行全面解析,并通过示例代码展示它们的基本用法和特点。
2383 8
|
IDE 测试技术 持续交付
Python自动化测试与单元测试框架:提升代码质量与效率
【9月更文挑战第3天】随着软件行业的迅速发展,代码质量和开发效率变得至关重要。本文探讨了Python在自动化及单元测试中的应用,介绍了Selenium、Appium、pytest等自动化测试框架,以及Python标准库中的unittest单元测试框架。通过详细阐述各框架的特点与使用方法,本文旨在帮助开发者掌握编写高效测试用例的技巧,提升代码质量与开发效率。同时,文章还提出了制定测试计划、持续集成与测试等实践建议,助力项目成功。
376 5
|
存储 SQL 分布式计算
大数据-95 Spark 集群 SparkSQL Action与Transformation操作 详细解释与测试案例(一)
大数据-95 Spark 集群 SparkSQL Action与Transformation操作 详细解释与测试案例(一)
364 0
|
测试技术 C# 开发者
“代码守护者:详解WPF开发中的单元测试策略与实践——从选择测试框架到编写模拟对象,全方位保障你的应用程序质量”
【8月更文挑战第31天】单元测试是确保软件质量的关键实践,尤其在复杂的WPF应用中更为重要。通过为每个小模块编写独立测试用例,可以验证代码的功能正确性并在早期发现错误。本文将介绍如何在WPF项目中引入单元测试,并通过具体示例演示其实施过程。首先选择合适的测试框架如NUnit或xUnit.net,并利用Moq模拟框架隔离外部依赖。接着,通过一个简单的WPF应用程序示例,展示如何模拟`IUserRepository`接口并验证`MainViewModel`加载用户数据的正确性。这有助于确保代码质量和未来的重构与扩展。
1060 0