将单元测试标记为JUnit中的预期故障

时间:2010-10-29 19:02:22

标签: java unit-testing junit

如何将测试标记为JUnit 4中的预期故障?

在这种情况下,我想继续运行此测试,直到上游修补了某些内容。忽略测试有点太过分了,因为那时我可能会忘记它。我可以添加@expected注释并捕获assertThat抛出的异常,但这似乎也与预期的行为有关。

以下是我目前的测试结果:

@Test
public void unmarshalledDocumentHasExpectedValue() 
{
    doc = unmarshaller.unmarshal(getResourceAsStream("mydoc.xml"));
    final ST title = doc.getTitle();
    assertThat(doc.getTitle().toStringContent(), equalTo("Expected"));
}

该断言应该成功,但由于上游错误,它不会。然而,那个测试是正确的;它应该成功。事实上,我发现的所有替代方案都具有误导性。现在我认为@Ignore("This test should pass once fixed upstream")是我最好的选择,但我仍然要记得回到它。我更喜欢试运行。

在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);

在上述两种情况下,单元测试运行,这是我希望发生的。我在JUnit中遗漏了什么吗?

6 个答案:

答案 0 :(得分:24)

我不太了解您的方案的细节,但这是我通常如何测试预期的失败:

光滑的新方式:

@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
    }
}

如果您想要确保抛出异常,那么您可能还希望在循环中使用第二种技术,而不是为每种情况创建单独的测试方法。如果您只是使用expected在单个方法中循环遍历大量案例,则第一个抛出异常将导致测试结束,并且后续案例将不会被检查。

答案 1 :(得分:17)

我假设您希望测试在断言失败时通过,但如果断言成功,那么测试也应该通过。

最简单的方法是使用TestRule。 TestRule提供了在运行测试方法之前和之后执行代码的机会。这是一个例子:

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标记,我会忽略错误。您可以根据版本号,某些文本等执行您喜欢的任何逻辑来决定是否应该忽略错误。您还可以将动态确定的标志传递给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"));
}

有关更多示例,请参阅ExternalResourceExpectedException

忽略预期的故障测试而不是传递它

如果你想把测试标记为忽略而不是成功,它会变得有点复杂,因为测试在执行之前会被忽略,所以你必须回顾性地将测试标记为忽略,这将涉及构建你自己的Runner 。为了开始,请参阅我对How to define JUnit method rule in a suite?的回答。或者问另一个问题。

答案 2 :(得分:15)

明确期望AssertionError怎么样?

@Test(expected = AssertionError.class)
public void unmarshalledDocumentHasExpectedValue() {
    // ...
}

如果您有理由相信测试中只有JUnit机器会引发AssertionError,那么这似乎就像任何事情一样自我记录。

你仍然冒着忘记这种测试的风险。如果有的话,我不会长时间让这些测试进入版本控制。

答案 3 :(得分:7)

一个选项是将测试标记为@Ignore并将文本放在那里,这可能是一个错误并等待修复。这样它就不会运行了。然后它会被跳过。您也可以使用extensions以可能不同的方式满足您的需求。

答案 4 :(得分:4)

我更进一步采用了Matthew的答案,实际上实现了一个@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注释您的测试方法,即使引发了给定类型的异常,它也不会失败(提供您希望测试方法通过的一种或多种类型):

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.Assert重写您的测试。

来自Assume的JavaDoc:

  

一组方法,用于说明测试有意义的条件的假设。失败的假设并不意味着代码被破坏,而是测试没有提供有用的信息。默认的JUnit运行器将失败假设的测试视为忽略。

自JUnit 4.4

以来,

Assume可用

答案 5 :(得分:1)

如果可能,请使用模拟的上游类。用正确的结果存根。 (可选)在修复bug后,将mock替换为真实对象。