在JUnit 4.11中结合@ClassRule和@Rule

时间:2013-12-24 22:43:40

标签: java junit junit4 junit-rule

在JUnit 4.10及以下版本中,可以将规则注释为@Rule和@ClassRule。这意味着规则在类之前/之后以及每次测试之前/之后被调用。这样做的一个可能原因是设置昂贵的外部资源(通过@ClassRule调用),然后便宜地重置它(通过@Rule调用)。

从JUnit 4.11开始,@ Rule字段必须是非静态的,而@ClassRule字段必须是静态的,因此上述内容不再可能。

有明确的解决方法(例如明确地将@ClassRule和@Rule职责分开到单独的规则中),但是必须强制使用两个规则似乎是一种耻辱。我简要介绍了使用@Rule并推断它是否是第一个/最后一个测试,但我不相信这些信息是可用的(至少,它不能直接在描述中使用)。

在JUnit 4.11的单个规则中是否有一种干净整洁的@ClassRule和@Rule功能组合方式?

谢谢, 罗文<​​/ P>

5 个答案:

答案 0 :(得分:12)

从JUnit 4.12开始(在撰写本文时尚未发布),可以使用@Rule@ClassRule注释单个静态规则。

请注意,它必须是静态的 - 使用@Rule@ClassRule注释的非静态规则仍然被视为无效(因为任何带注释的@ClassRule在类级别工作,所以只有真的作为静态成员有意义。)

如果您对更多细节感兴趣,请参阅the release notesmy pull request

答案 1 :(得分:5)

另一种可能的解决方法是声明静态@ClassRule并在声明非静态@Rule时使用该值:

@ClassRule
public static BeforeBetweenAndAfterTestsRule staticRule = new BeforeBetweenAndAfterTestsRule();
@Rule
public BeforeBetweenAndAfterTestsRule rule = staticRule;

这意味着您不必重构任何现有规则类,但仍需要声明两个规则,因此它不能很好地回答原始问题。

答案 2 :(得分:2)

另一种可行的解决方法是声明一个非静态的@Rule,并让它对静态协作者起作用:如果协作者尚未初始化,则@Rule知道它是第一次运行(因此它可以设置它的合作者,例如启动外部资源);如果它们被初始化,@ Rule可以进行每次测试工作(例如重置外部资源)。

这样做的缺点是@Rule不知道它何时处理了最后一次测试,所以不能执行任何后级操作(例如整理外部资源);但是,可以使用@AfterClass方法来执行此操作。

答案 3 :(得分:2)

问题的答案如下:没有干净的方法(仅同时设置两个规则)。 我们尝试为自动测试重试实现类似的任务,并且结合了两个规则(用于此任务)并且实现了这样丑陋的方法: Tests retry with two rules

但是如果更准确地考虑必要的任务(需要实现),可以使用jUnit自定义Runner来使用更好的方法:  Retry Runner

因此,为了获得更好的方法,最好知道您的具体用例。

答案 4 :(得分:1)

我遇到过类似的问题,有两种解决方法。我不喜欢任何一个,但他们有不同的权衡:

1) 如果您的规则公开了清理方法,您可以在@Before方法中手动调用清理。

@ClassRule
public static MyService service = ... ;

@Before
public void cleanupBetweenTests(){
     service.cleanUp();
}

这样做的缺点是你需要记住(并告诉团队中的其他人)总是添加@Before方法或创建一个测试继承的抽象类来为你做清理。

2)有2个字段,一个是静态的,一个是非静态的,指向同一个对象,每个字段分别用@ClassRule@Rule注释。如果未公开清理,则需要这样做。当然,缺点是你必须记住同时拥有@ClassRule和@Rule指向看起来很奇怪的东西。

 @ClassRule
 public static MyService service = ... ;

@Rule
public MyService tmp = service ;

然后在您的实现中,您必须区分测试套件或单个测试。这可以通过检查Description是否有孩子来完成。根据哪一个,您可以创建不同的Statement适配器来处理清理:

@Override
protected void after() {

    //class-level shut-down service
    shutdownService();
}


@Override
protected void before() {

    //class-level init service
    initService();
}

@Override
public Statement apply(Statement base, Description description) {

        if(description.getChildren().isEmpty()){
            //test level perform cleanup

            return new CleanUpStatement(this,base);
        }
        //suite level no-change
        return super.apply(base, description);

    }

以下是每次测试前要清理的自定义Statement类:

private static final class CleanUpStatement extends Statement{
        private final MyService service;

        private final Statement statement;



        CleanUpStatement(MyService service, Statement statement) {
            this.service = service;
            this.statement = statement;
        }



        @Override
        public void evaluate() throws Throwable {
            //clear messages first
            myService.cleanUp();
            //now evaluate wrapped statement
            statement.evaluate();
        }

    }

在完成所有这些之后,我会更倾向于选项1,因为它更具意图揭示并且维护的代码更少。我还担心其他人试图修改选项2中的代码,认为有一个错误,因为相同的字段指向两次。额外的努力,代码评论等都不值得。

无论如何,您仍然可以使用模板方法将锅炉盘复制并粘贴到任何地方或抽象类。