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

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 《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

相关实践学习
日志服务之数据清洗与入湖
本教程介绍如何使用日志服务接入NGINX模拟数据,通过数据加工对数据进行清洗并归档至OSS中进行存储。
相关文章
|
4天前
|
负载均衡 Java 测试技术
性能测试与负载均衡:保证Java应用的稳定性
性能测试与负载均衡:保证Java应用的稳定性
|
4天前
|
监控 Java 测试技术
Java性能测试与调优工具使用指南
Java性能测试与调优工具使用指南
|
4天前
|
Java 测试技术 数据库
Java单元测试与集成测试的最佳实践
Java单元测试与集成测试的最佳实践
|
6天前
|
Java 测试技术 数据库连接
解密Java事务传播行为与隔离级别:案例详解与解决方案
解密Java事务传播行为与隔离级别:案例详解与解决方案
6 1
|
1天前
|
XML 测试技术 数据格式
《手把手教你》系列基础篇(八十五)-java+ selenium自动化测试-框架设计基础-TestNG自定义日志-下篇(详解教程)
【7月更文挑战第3天】TestNG教程展示了如何自定义日志记录。首先创建一个名为`TestLog`的测试类,包含3个测试方法,其中一个故意失败以展示日志。使用`Assert.assertTrue`和`Reporter.log`来记录信息。接着创建`CustomReporter`类,继承`TestListenerAdapter`,覆盖`onTestFailure`, `onTestSkipped`, 和 `onTestSuccess`,在这些方法中自定义日志输出。
17 6
|
1天前
|
机器学习/深度学习 自然语言处理 Java
Java中的自然语言处理应用案例分析
Java中的自然语言处理应用案例分析
|
1天前
|
负载均衡 Java 微服务
Java中的可扩展微服务架构设计案例解析
Java中的可扩展微服务架构设计案例解析
|
2天前
|
机器学习/深度学习 人工智能 自然语言处理
Java中的自然语言处理应用案例分析
Java中的自然语言处理应用案例分析
|
2天前
|
负载均衡 Java 微服务
Java中的可扩展微服务架构设计案例解析
Java中的可扩展微服务架构设计案例解析
|
2天前
|
Java 测试技术 Android开发
《手把手教你》系列基础篇(八十四)-java+ selenium自动化测试-框架设计基础-TestNG日志-上篇(详解教程
【7月更文挑战第2天】TestNG是一个用于自动化测试的Java框架,提供日志记录功能。日志有两种模式:底层级详细记录每个步骤,高层级仅记录关键事件。示例代码展示了如何在测试方法中使用`Reporter.log()`记录信息,这些信息会显示在TestNG HTML报告中。文章还提及了日志显示时可能出现的编码问题及解决办法。