Java 在JUnit中将单元测试标记为预期失败

Java 在JUnit中将单元测试标记为预期失败,java,unit-testing,junit,Java,Unit Testing,Junit,如何在JUnit4中将测试标记为预期失败 在这种情况下,我想继续运行这个测试,直到上游的东西被修补。忽略测试有点过分,因为那样我可能会忘记它。我可能能够添加一个@expected注释,并捕获assertThat引发的异常,但这似乎也与预期行为有关 以下是我当前的测试结果: @Test public void unmarshalledDocumentHasExpectedValue() { doc = unmarshaller.unmarshal(getResourceAsStream(

如何在JUnit4中将测试标记为预期失败

在这种情况下,我想继续运行这个测试,直到上游的东西被修补。忽略测试有点过分,因为那样我可能会忘记它。我可能能够添加一个
@expected
注释,并捕获
assertThat
引发的异常,但这似乎也与预期行为有关

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

@Test
public void unmarshalledDocumentHasExpectedValue() 
{
    doc = unmarshaller.unmarshal(getResourceAsStream("mydoc.xml"));
    final ST title = doc.getTitle();
    assertThat(doc.getTitle().toStringContent(), equalTo("Expected"));
}
断言应该成功,但由于上游错误,它没有成功。然而,这个测试是正确的;它应该成功。事实上,我发现的所有替代方案都是误导性的。现在我认为,
@Ignore(“这个测试应该通过一次上游修复”)
是我最好的选择,但我还是要记得回到它。我更喜欢试运行

在Python中,我可以使用decorator:

class ExpectedFailureTestCase(unittest.TestCase):
    @unittest.expectedFailure
    def test_fail(self):
        self.assertEqual(1, 0, "broken")

C++中使用Qt,可以使用:


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

我不太了解您场景的细节,但我通常是这样测试预期失败的:

巧妙的新方式:

@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
在单个方法中循环处理一组案例,则第一个抛出异常的案例将结束测试,后续案例将不会被检查。

一个选项是将测试标记为,并在其中放入可能存在错误并等待修复的文本。这样它就不会跑了。然后它将被跳过。您还可以使用,以一种可能不同的方式满足您的需要。

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

那么显式地期望断言错误呢

@Test(expected = AssertionError.class)
public void unmarshalledDocumentHasExpectedValue() {
    // ...
}
如果您有理由相信,只有测试中的JUnit机器才会引发AssertionError,那么这似乎是一种自我记录


你还是有可能忘记这样的测试。我不会让这样的测试进入版本控制太久,如果有的话。

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

做到这一点最简单的方法是使用。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()时,我将捕获任何可丢弃的(包括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"));
}
public void unmarshalledDocumentHasExpectedValue(){
doc=unmarshaller.unmarshal(getResourceAsStream(“mydoc.xml”);
expectedFailure.setExpectedFailure(doc.getVersionNumber()<3000);
final ST title=doc.getTitle();
资产(doc.getTitle().toStringContent(),equalTo(“预期”);
}
有关更多示例,请参见和

忽略预期的失败测试,而不是通过它
如果您想将测试标记为忽略而不是成功,那么它会变得更加复杂,因为测试在执行之前会被忽略,因此您必须回顾性地将测试标记为忽略,这将涉及构建您自己的运行程序。给你一个开始,看看我的答案。或者问另一个问题。

我将Matthew的回答进一步,实际实现了一个
@可选的
注释,您可以使用它来代替他在回答中提到的
@不推荐的
标记注释。虽然很简单,但我将与您共享代码,也许这对某些人有帮助:

@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();
}
现在,您可以使用
@Optional
注释您的测试方法,即使引发了给定类型的异常,它也不会失败(请提供一个或多个您希望测试方法通过的类型):

[更新]

如果您希望测试通过,即使假设不成立,也可以使用JUnit的
org.JUnit.aspect
而不是传统的
org.JUnit.Assert
,重写测试

假设
的JavaDoc:

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


假设从JUnit 4.4开始提供了
,这可以通过以下方式更好地处理:
@Test(expected=NullPointerException.class)
。然后,您可以删除try/catch块和fail语句,因为JUnit会告诉您,它预期会发生异常,如果没有抛出异常,则不会收到异常。我忘了他们还加了那个。在你们这样做之后,你们可能再也不会回到真实的物体上来了:-)好主意!如果没有更好的答案,我几天后就接受。(如果我没有接受答案,因为我忘了打电话给我)。那可能是最好的选择。。。但是,当断言成功时,测试将“失败”,这让人感觉不好。@SamuelEdwinWard同意这让人感觉不好……然而,我只会将其用于短期失败。如果它们持续的时间超过几个小时
@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();
}
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;
          }
        }
      }
    };
  }
}
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");
  }

}