在junit4中即使一个assert失败也要继续执行测试

60

我已经使用Jfunc构建了现有的框架,它提供了即使在测试用例中的某个assert失败时也可以继续执行的功能。Jfunc使用junit 3.x框架。但是现在我们正在迁移到junit4,所以我不能再使用Jfunc了,已经用junit 4.10 jar替换了它。
现在的问题是,由于我们在框架中广泛使用了jfunc,并且使用junit 4时希望使我们的代码即使一个assert在测试用例中失败也能够继续执行。
是否有人对此有任何建议/想法?我知道在junit中,测试需要更原子化,即每个测试用例只有一个assert,但由于某些原因我们无法在我们的框架中这样做。

7个回答

77
您可以使用ErrorCollector规则来实现此操作。
要使用它,首先在测试类中将该规则添加为字段:
public class MyTest {
    @Rule
    public ErrorCollector collector = new ErrorCollector();

    //...tests...
}

然后将您的断言替换为对 collector.checkThat(...) 的调用。

例如:

@Test
public void myTest() {
    collector.checkThat("a", equalTo("b"));
    collector.checkThat(1, equalTo(2));
}

5
谢谢你的回复,Chris。 它在第一次失败之后仍然起作用(即collector.checkThat(10, CoreMatchers.equalTo(11)))。 但问题是,即使其中一个检查失败,我也希望将其标记为测试失败。 当前,在执行上述测试之后,测试被标记为已通过。我希望它失败并显示故障行的堆栈跟踪(类似于断言失败时的情况)。 - user85
1
很奇怪。我刚刚尝试了我在答案中写的代码,它按预期工作。测试失败,并打印出每个失败断言的堆栈跟踪。这是完整的测试代码和我从IntelliJ运行测试得到的输出:https://gist.github.com/2419626 - Chris B
哦...我的错,我刚忘记添加规则了,这导致了所有的混乱.....对于造成的困惑非常抱歉,非常感谢您的帖子。 - user85
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Nier
你好,它是否也将所有失败的步骤标记为“失败”? - Sakshi Singla

24

我也使用ErrorCollector,但同时使用assertThat并将它们放入try-catch块中。

import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;

@Rule
public ErrorCollector collector = new ErrorCollector();

@Test
public void calculatedValueShouldEqualExpected() {
    try {
        assertThat(calculatedValue(), is(expected));
    } catch (Throwable t) {
        collector.addError(t);
        // do something
    }
}

7

你也可以使用assertj - 软断言

@Test
public void testCollectErrors(){
   SoftAssertions softly = new SoftAssertions();
   softly.assertThat(true).isFalse();
   softly.assertThat(false).isTrue();
   // Don't forget to call SoftAssertions global verification !
   softly.assertAll();
}

还有其他的方法可以使用它而不必手动调用softly.assertAll();

  1. 使用规则
  2. 使用自动关闭
  3. 使用静态assertSoftly方法

由于此代码没有使用 try...catch 语句并且符合 lib-authors 的意图,我认为这是最标准的解决方案。 - Nils-o-mat

3

使用try/finally块。在我的情况下这很有效:

...
try {
    assert(...)
} finally {
    // code to be executed after assert
}
...

1
你意识到这个问题已经发布了2年,已经有一个被接受的答案了吗? - Mingle Li
12
这个网站不仅仅是为了接受答案,而是为了帮助搜索答案的用户找到解决问题的方法。有时候有多种方法可以解决一个问题,而且对于一个人有效的方法可能对另一个人来说效果不太好。@TheJuniorProgrammer,这只是一般说明。 - nomistic
@nomistic 好的,足够正确。 - Mingle Li
1
我发现这个答案只在有一个断言语句的情况下有效。多个断言在错误被抛出后不会执行。 - Lydia Ralph
@LydiaRalph:是的,每个assert都需要自己的try/finally块。这在答案中没有解释。 - sleske
问题是关于单元测试断言,而不是Java语言断言。 - Charlie

1
在“try”中使用断言,在“catch”中将可能出现的错误添加到集合中。 然后在测试结束时,在tearDown()中抛出异常。 因此,如果断言失败/错误,则会被捕获并继续测试。 (示例中的集合是静态的,您也可以在setUp()中为每个@Test创建新实例)
    public static List<String> errors = new ArrayList<>();


    try {
        //some assert...
    }
    catch (AssertionError error) {
        errors.add(error.toString());
    }



    @After
    public void tearDown() {

        try {
            if (!errors.isEmpty()) {
                throw new AssertionError(errors);
            }
        }
        finally {
            //empty list because it's static, alternatively make instance for each test in setUp()
            errors.clear();
        }
    }

0

我创建了自己的简单断言类。可以轻松扩展以适应您的用例:

public class MyEquals {

public static void checkTestSummary(MyTestSummary myTestSummary) {
    final List<MyTestResult> conditions = myTestSummary.getTestResults();
    final int total = conditions.size();

    final boolean isSuccessful = myTestSummary.isSuccessful();
    if (isSuccessful) {
        System.out.println(format("All [%s] conditions are successful!", total));
    } else {
        final List<MyTestResult> failedConditions = conditions.stream().filter(MyTestResult::isTestResult).collect(Collectors.toList());
        System.out.println(format("\nNot yet.. [%s out of %s] conditions are failed", failedConditions.size(), total));
    }

    if (!isSuccessful) {
        for (int i = 0; i < total; i++) {
            final MyTestResult myTestResult = conditions.get(i);
            if (myTestResult.isTestResult()) {
                System.out.println(format("  Success [%s of %s] => Expected %s Actual %s Good!", i + 1, total, myTestResult.getExpected(), myTestResult.getActual()));
            } else {
                System.out.println(format("!! Failed [%s of %s] => Expected %s Actual %s", i + 1, total, myTestResult.getExpected(), myTestResult.getActual()));
            }
        }
    }

    assertTrue(isSuccessful);
}

public static void myAssertEquals(MyTestSummary myTestSummary, Object expected, Object actual) {
    if (checkEquals(expected, actual)) {
        assertEquals(expected, actual);
        myTestSummary.addSuccessfulResult(expected, actual);
    } else {
        myTestSummary.addFailedResult(expected, actual);
        myTestSummary.setSuccessful(false);
    }
}

public static boolean checkEquals(Object value1, Object value2) {
    if (value1 == null && value2 == null) {
        return true;
    } else if (value1 != null && value2 == null) {
        return false;
    } else if (value1 == null && value2 != null) {
        return false;
    } else if (value1 != null && value2 != null) {
        return value1.equals(value2);
    }
    return false;
}

}

@Builder
@Value
public class MyTestResult {

    String expected;

    String actual;

    boolean testResult;

 }



@Data
public class MyTestSummary {

private boolean successful = true;

private List<MyTestResult> testResults = new ArrayList<>();

public MyTestSummary() {
}

public void addSuccessfulResult(Object expected, Object actual) {
    getTestResults().add(MyTestResult.builder()
        .expected(String.valueOf(expected))
        .actual(String.valueOf(actual))
        .testResult(true)
        .build()
    );
}

public void addFailedResult(Object expected, Object actual) {
    getTestResults().add(MyTestResult.builder()
        .expected(String.valueOf(expected))
        .actual(String.valueOf(actual))
        .testResult(false)
        .build()
    );
}
}

在 JUnit 测试中的使用

@Test
public void testThat() {
    MyTestSummary myTestSummary = new MyTestSummary();
    myAssertEquals(myTestSummary, 10, 5 + 5);
    myAssertEquals(myTestSummary, "xxx", "x" + "x");
    checkTestSummary(myTestSummary);
}

输出:

Not yet.. [1 out of 2] conditions are failed
Success [1 of 2] => Expected 10 Actual 10 Good!
!! Failed [2 of 2] => Expected xxx Actual xx

 org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
 Expected :true
 Actual   :false

-1
另一个选项是与lambda表达式一起使用的可观察模式。您可以使用类似上面的东西。
public class MyTestClass {

    private final List<Consumer<MyTestClass>> AFTER_EVENT = new ArrayList<>();

    @After
    public void tearDown() {
        AFTER_EVENT.stream().forEach(c -> c.accept(this));
    }

    @Test
    public void testCase() {
        //=> Arrange
        AFTER_EVENT.add((o) -> {
            // do something after an assertion fail.
        }));

        //=> Act

        //=> Assert
        Assert.assertTrue(false);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接