JaCoCo指出在两段式正则表达式的匹配/查找中存在未覆盖的分支,但这一点已被多次单元测试证明是被覆盖的

编程语言 2026-07-09

我有以下电子邮件验证方法,它基于两个条件来检查电子邮件地址:(1)主体格式必须有效,(2)UUID不能出现在字符串中的任何位置

有两个正则表达式,它们将结果加入错误列表的整体条件是:

if (!matcher.matches() || uuidMatcher.find())

public void validate(String email, String emailName, List<String> errorMessages) {
    if (!StringUtils.isBlank(email)) {
        // 1. Main Email Format
        String regex = "^[A-Za-z0-9._+-]+@[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*\\.[A-Za-z]{2,}$";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(email);

        // 2. UUID cannot appear anywhere
        String uuidRegex = "\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12}";
        Pattern uuidPattern = Pattern.compile(uuidRegex);
        Matcher uuidMatcher = uuidPattern.matcher(email);

        // This is for testing/debugging only: Print each scenario
        if (matcher.matches() && uuidMatcher.find())
            System.out.println("Satisfied (1): matcher.matches & uuidMatcher.find");
        else if (!matcher.matches() && uuidMatcher.find())
            System.out.println("Satisfied (2): !matcher.matches & uuidMatcher.find");
        else if (matcher.matches() && !uuidMatcher.find())
            System.out.println("Satisfied (3): matcher.matches & !uuidMatcher.find");
        else
            System.out.println("Satisfied (4): !matcher.matches & !uuidMatcher.find");

        // Add to errorMessage depending on: !matcher.matches() || uuidMatcher.find()
        if (!matcher.matches() || uuidMatcher.find()) {
            errorMessages.add(emailName + " is invalid");
        }
    }
}

以下这三个测试方法在它们之间覆盖所有这些场景。测试方法#1适用于全部有效,#2适用于全部无效,#3测试顶部的必填项(目前我们不需要担心)。

@Test
public void testValidateEmailTrue() throws Exception {
    List<String> emails = new ArrayList<>();
    emails.add("[email protected]");
    emails.add("[email protected]");
    emails.add("[email protected]");
    emails.add("[email protected]");
    emails.add("[email protected]");
    emails.add("[email protected]");
    emails.add("[email protected]");
    emails.add("[email protected]");
    emails.add("[email protected]");
    emails.add("[email protected]");

    for (String email : emails)mail {
        service.validate(email, "email", errorMessages);
    }

    assertTrue(errorMessages.isEmpty());
}

@Test
public void testValidateEmailFalse() throws Exception {
    List<String> emails = new ArrayList<>();
    emails.add("@test.com");
    emails.add("test&test.com");
    emails.add("test#@test.me.org");
    emails.add("[email protected]");
    emails.add("test%[email protected]");
    emails.add("[email protected]");
    emails.add("%[email protected]");
    emails.add("[email protected]");


    for (String email : emails) {
        service.validate(email, "email", errorMessages);
    }

    assertFalse(errorMessages.isEmpty());
}

@Test
public void testValidateEmailNullOrEmpty() throws Exception {
    service.validate(null, "email", errorMessages);
    service.validate("", "email", errorMessages);
    assertTrue(errorMessages.isEmpty());
}

当我执行所有测试方法时,我发现四种场景中的每一个都被jUnit #1和 #2覆盖。来自测试方法#1/#2的总输出共有18条:来自#1的 10条(它们都覆盖场景3),接着来自#2的 8条(它们覆盖场景4、1和 2):

Satisfied (3): matcher.matches & !uuidMatcher.find
Satisfied (3): matcher.matches & !uuidMatcher.find
Satisfied (3): matcher.matches & !uuidMatcher.find
Satisfied (3): matcher.matches & !uuidMatcher.find
Satisfied (3): matcher.matches & !uuidMatcher.find
Satisfied (3): matcher.matches & !uuidMatcher.find
Satisfied (3): matcher.matches & !uuidMatcher.find
Satisfied (3): matcher.matches & !uuidMatcher.find
Satisfied (3): matcher.matches & !uuidMatcher.find
Satisfied (3): matcher.matches & !uuidMatcher.find
Satisfied (4): !matcher.matches & !uuidMatcher.find
Satisfied (4): !matcher.matches & !uuidMatcher.find
Satisfied (4): !matcher.matches & !uuidMatcher.find
Satisfied (4): !matcher.matches & !uuidMatcher.find
Satisfied (4): !matcher.matches & !uuidMatcher.find
Satisfied (1): matcher.matches & uuidMatcher.find
Satisfied (2): !matcher.matches & uuidMatcher.find
Satisfied (1): matcher.matches & uuidMatcher.find

但无论我怎么做,JaCoCo在最终报告中都告诉我场景(3)是一个未覆盖的分支:

在此输入图片描述

如果我只有测试方法#1,并把#2注释掉,那么它显示场景(3)已覆盖,但其他场景都没有覆盖(正好相反):

仅使用测试方法#1(移除#2): 在此输入图片描述

解决方案

问题在于当第一个if语句为真时,它覆盖了第三个if语句的第二个假分支,这意味着测试永远无法覆盖所有执行分支。因为每个if都包含两个操作数,每个if都有两种让它为假的情况。Jacoco期望你逐一测试它们。Jacoco不仅仅考虑整个if语句为真还是为假,它还要考虑if语句可能为真或为假的所有情况。

        if (matcher.matches() && uuidMatcher.find())
            System.out.println("Satisfied (1): matcher.matches & uuidMatcher.find");
        else if (!matcher.matches() && uuidMatcher.find())
            System.out.println("Satisfied (2): !matcher.matches & uuidMatcher.find");
        else if (matcher.matches() && !uuidMatcher.find())
            // If matcher.maches() == true and uuidMatcher.find() == true,
            // execution took the first if and never came here. Jacoco
            // complains because you never test the second false-branch
            // of this if-statement. You only test the case where
            // matcher.matches() == false && uuidMatcher.find() == false.
            // Solution is to remove uuidMatcher from this if because it
            // is dead code.
            System.out.println("Satisfied (3): matcher.matches & !uuidMatcher.find");
        else
            System.out.println("Satisfied (4): !matcher.matches & !uuidMatcher.find");

但随后最好重新格式化代码,使if分支更易于理解。你可能不喜欢嵌套的if语句的写法,但这会让你避免这个问题。

        if (matcher.matches()) {
            if (uuidMatcher.find()) {
                System.out.println("Satisfied (1): matcher.matches & uuidMatcher.find");
            } else {
                System.out.println("Satisfied (3): matcher.matches & !uuidMatcher.find");
            }
        } else {
            if (uuidMatcher.find()) {
                System.out.println("Satisfied (2): !matcher.matches & uuidMatcher.find");
            } else {
                System.out.println("Satisfied (4): !matcher.matches & !uuidMatcher.find");
            }
        }

当你把if语句写成这样时,有助于直观地看到代码分支的复杂性,并可能更早发现需要重构的方法。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章