在JUnit中将unit testing标记为预期的失败
我如何在JUnit 4中将testing标记为预期的失败?
在这种情况下,我想继续运行这个testing,直到上游有补丁。 忽略这个testing有点过分,因为我可能会忘记它。 我可以添加一个@expected
注解,并捕获由assertThat
抛出的exception,但这似乎也是在于预期的行为。
以下是我目前的testing:
@Test public void unmarshalledDocumentHasExpectedValue() { doc = unmarshaller.unmarshal(getResourceAsStream("mydoc.xml")); final ST title = doc.getTitle(); assertThat(doc.getTitle().toStringContent(), equalTo("Expected")); }
这个断言应该成功,但是由于上游的错误,它不会。 然而,那个testing是正确的; 它应该成功。 几乎所有我find的替代品都是误导性的。 现在我认为@Ignore("This test should pass once fixed upstream")
是我最好的select,但我仍然要记得回到它。 我更喜欢testing运行。
在Python中,我可以使用expectedFailure装饰器:
class ExpectedFailureTestCase(unittest.TestCase): @unittest.expectedFailure def test_fail(self): self.assertEqual(1, 0, "broken")
在C ++中使用Qt的QTestLib ,你可以使用QEXPECT_FAIL :
QEXPECT_FAIL("", "Will be fixed next version", Continue); QCOMPARE(i, 42);
在上面的两种情况下,unit testing运行,这是我希望发生的事情。 我在JUnit中错过了什么吗?
我假设你希望testing通过,如果你的断言失败,但如果断言成功,那么testing也应该通过。
最简单的方法是使用TestRule 。 TestRule提供了在运行testing方法之前和之后执行代码的机会。 这里是一个例子:
public class ExpectedFailureTest { public class ExpectedFailure implements TestRule { public Statement apply(Statement base, Description description) { return statement(base, description); } private Statement statement(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { try { base.evaluate(); } catch (Throwable e) { if (description.getAnnotation(Deprecated.class) != null) { // you can do whatever you like here. System.err.println("test failed, but that's ok:"); } else { throw e; } } } }; } } @Rule public ExpectedFailure expectedFailure = new ExpectedFailure(); // actually fails, but we catch the exception and make the test pass. @Deprecated @Test public void testExpectedFailure() { Object o = null; o.equals("foo"); } // fails @Test public void testExpectedFailure2() { Object o = null; o.equals("foo"); } }
首先请注意,第一个方法被标记为@Deprecated
。 我使用这个作为我想忽略任何断言失败的方法的标记。 你可以做任何你喜欢的方法,这只是一个例子。
接下来,在ExpectedFailure#apply()
,当我执行base.evaluate()时,我捕获任何Throwable(其中包括AssertionError),并且如果该方法标注了@Deprecated注释,我将忽略该错误。 您可以根据版本号,某些文本等,执行任何您喜欢的逻辑来决定是否应该忽略错误。您还可以将一个dynamic确定的标志传递给ExpectedFailure,以允许某些版本号失败:
public void unmarshalledDocumentHasExpectedValue() { doc = unmarshaller.unmarshal(getResourceAsStream("mydoc.xml")); expectedFailure.setExpectedFailure(doc.getVersionNumber() < 3000); final ST title = doc.getTitle(); assertThat(doc.getTitle().toStringContent(), equalTo("Expected")); }
有关更多示例,请参阅ExternalResource和ExpectedException
忽略预期的失败testing,而不是通过它
如果你想把你的testing标记为Ignored而不是Success,它会变得更加复杂一点,因为testing在被执行之前被忽略了,所以你必须回溯地标记一个被忽略的testing,这将涉及构build你自己的Runner。 为了给你一个开始,请参阅我的答案如何在套件中定义JUnit方法规则? 。 或者问另一个问题。
我不太清楚你的情况的细节,但这里是我通常testing预期的失败:
光滑的新方法:
@Test(expected=NullPointerException.class) public void expectedFailure() { Object o = null; o.toString(); }
对于旧版本的JUnit:
public void testExpectedFailure() { try { Object o = null; o.toString(); fail("shouldn't get here"); } catch (NullPointerException e) { // expected } }
如果你有一堆你想要确保抛出exception的东西,你可能还想在循环中使用第二种技术,而不是为每种情况创build一个单独的testing方法。 如果你只是用一个单一的方法循环使用expected
方法,那么第一个抛出exception就会结束testing,而后面的情况不会被检查。
那么明确期待一个AssertionError呢?
@Test(expected = AssertionError.class) public void unmarshalledDocumentHasExpectedValue() { // ... }
如果你确信只有testing中的JUnit机制会引发AssertionError,那么这看起来就像自我logging一样。
你仍然冒着忘记这种testing的风险。 如果有的话,我不会让这样的testing进入版本控制。
一种select是将testing标记为@Ignore ,并将文本放在那里,这可能是一个错误,正在等待修复。 这样它就不会运行。 它会被跳过。 您还可以使用扩展来以一种潜在的不同方式来满足您的需求。
我已经把马修的回答进一步说了@Optional
,实际上实现了@Optional
注释,而不是你在他的回答中提到的@Deprecated
标记注释。 虽然很简单,但我会与您分享代码,也许对某人有所帮助:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Optional { /** * Specify a Throwable, to cause a test method to succeed even if an exception * of the specified class is thrown by the method. */ Class<? extends Throwable>[] exception(); }
通过马特的ExpectedFailure
类的简单修改:
public class ExpectedFailure implements TestRule { @Override public Statement apply(final Statement base, final Description description) { return statement(base, description); } private Statement statement(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { try { base.evaluate(); } catch (Throwable e) { // check for certain exception types Optional annon = description.getAnnotation(Optional.class); if (annon != null && ArrayUtils.contains(annon.exception(), e.getClass())) { // ok } else { throw e; } } } }; } }
你现在可以使用@Optional
来注释你的testing方法,即使引发了给定types的exception,也不会失败(提供一种或多种你希望testing方法通过的方法):
public class ExpectedFailureTest { @Rule public ExpectedFailure expectedFailure = new ExpectedFailure(); // actually fails, but we catch the exception and make the test pass. @Optional(exception = NullPointerException.class) @Test public void testExpectedFailure() { Object o = null; o.equals("foo"); } }
[UPDATE]
你也可以用JUnit的org.junit.Assume
而不是传统的org.junit.Assume
来重写你的testing,如果你想让你的testing通过,即使假设不成立。
从Assume
的JavaDoc:
一套有用的方法来陈述关于testing有意义的条件的假设。一个失败的假设并不意味着代码被破坏,而是testing没有提供有用的信息。 默认的JUnit运行者将忽略的失败假设对待testing。
Assume
从JUnit 4.4开始可用
如果可能的话,使用嘲弄的上游类。 将其与正确的结果进行比较。 修正错误后,可以用真实的对象代替模拟。