如何在 JUnit 4.12 中组合 @Rule 和 @ClassRule

How to combine @Rule and @ClassRule in JUnit 4.12

根据the 4.12 release notes,可以使用@Rule 和@ClassRule 注释测试class 的静态成员:

a static member annotated with both @Rule and @ClassRule is now considered valid. This means a single rule may be used to perform actions both before/after a class (e.g. setup/tear down an external resource) and between tests (e.g. reset the external resource),

我想使用此功能在文件中的所有测试开始时初始化资源,在每次测试之间对资源进行一些清理,并在所有测试完成后处理它。此资源当前由扩展 ExternalResource 的 class 表示。

在我的 beforeafter 方法中,如何区分 "before/after all tests" 和 "before/after each test"?我是否需要使用 TestRule 的 different/custom 实现来完成此操作?

注释为@Before@After的方法每次测试前后为运行,注释为@BeforeClass@AfterClass的方法为运行 分别在 class 中的 first/last 测试前后。

@Rule的before/after方法在每次测试前后执行,而@ClassRule的before/after方法是运行before/after整个测试class.

您可以将 ExternalResource 用于 @Rule@ClassRule 情况,只要处理程序方法对这两种情况都能做出正确反应。据我从文档中可以看出,没有办法在规则 class 方法中区分两个规则类别。如果您对这两种情况都使用规则 class,则将对两者应用相同的规则。

您无法区分 @BeforeClass@Before@AfterClass@After。有关添加此功能的原因的更多详细信息,请参阅 pull request

您可以创建自己的规则来实现 TestRuleMethodRule:

public SharableExternalResource implements TestRule, MethodRule {

  public final Statement apply(
      final Statement base, Description description) {
    return
        new Statement() {
          @Override
          public void evaluate() throws Throwable {
            beforeClass();

            List<Throwable> errors = new ArrayList<Throwable>();
            try {
                base.evaluate();
            } catch (Throwable t) {
                errors.add(t);
            } finally {
                try {
                    afterClass();
                } catch (Throwable t) {
                    errors.add(t);
                }
            }
            MultipleFailureException.assertEmpty(errors);
          }
        };
  }

  public final Statement apply(
      Statement base, FrameworkMethod method, Object target) {
    return
        new Statement() {
          @Override
          public void evaluate() throws Throwable {
            before();

            List<Throwable> errors = new ArrayList<Throwable>();
            try {
                base.evaluate();
            } catch (Throwable t) {
                errors.add(t);
            } finally {
                try {
                    after();
                } catch (Throwable t) {
                    errors.add(t);
                }
            }
            MultipleFailureException.assertEmpty(errors);
          }
        };
  }

  public void beforeClass() throws Exception {
    // do nothing
  }

  protected void before() throws Exception {
    // do nothing
  }

  protected void after() throws Exception {
    // do nothing
  }

  public void afterClass() throws Exception {
    // do nothing
  }
}

您可以实施 TestRule#apply 并使用 DescriptionisTestisSuite 方法来确定哪种 Statement 您的 TestRule正在应用于。

这是一个示例界面,您可以构建该界面以提供具有完整 beforeafterverifybeforeClassafterClassverifyClass 类型行为:

public interface CombinedRule extends TestRule {
    default Statement apply(Statement base, Description description) {
        if (description.isTest()) {
            return new Statement() {
                public void evaluate() throws Throwable {
                    before();
                    try {
                        base.evaluate();
                        verify();
                    } finally {
                        after();
                    }
                }
            };
        }
        if (description.isSuite()) {
            return new Statement() {
                public void evaluate() throws Throwable {
                    beforeClass();
                    try {
                        base.evaluate();
                        verifyClass();
                    } finally {
                        afterClass();
                    }
                }
            };
        }
        return base;
    }

    default void before() throws Exception {
        //let the implementer decide whether this method is useful to implement
    }

    default void after() {
        //let the implementer decide whether this method is useful to implement
    }

    /**
     * Only runs for Tests that pass
     */
    default void verify() {
        //let the implementer decide whether this method is useful to implement
    }

    default void beforeClass() throws Exception {
        before();
    }

    default void afterClass() {
        after();
    }

    /**
     * Only runs for Suites that pass
     */
    default void verifyClass() {
        verify();
    }
}