Java 在JUnit4.11中结合@ClassRule和@Rule

Java 在JUnit4.11中结合@ClassRule和@Rule,java,junit,junit4,junit-rule,Java,Junit,Junit4,Junit Rule,在JUnit4.10及以下版本中,可以将规则同时注释为@rule和@ClassRule。这意味着在类之前/之后以及每次测试之前/之后调用该规则。这样做的一个可能原因是设置一个昂贵的外部资源(通过@ClassRule调用),然后廉价地重置它(通过@Rule调用) 从JUnit4.11开始,@Rule字段必须是非静态的,@ClassRule字段必须是静态的,因此上述操作不再可行 显然有一些变通办法(例如,将@ClassRule和@Rule职责明确地分离为单独的规则),但不得不强制使用两个规则似乎是一

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

从JUnit4.11开始,@Rule字段必须是非静态的,@ClassRule字段必须是静态的,因此上述操作不再可行

显然有一些变通办法(例如,将@ClassRule和@Rule职责明确地分离为单独的规则),但不得不强制使用两个规则似乎是一种耻辱。我简要地看了一下使用@Rule并推断它是否是第一个/最后一个测试,但我不相信信息是可用的(至少在描述中它不是直接可用的)

在JUnit4.11中,是否有一种整洁的方式将@ClassRule和@Rule功能组合到一个规则中

谢谢,
Rowan

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

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

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

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


这有一个缺点,@规则不知道它在什么时候处理了最后一个测试,因此不能执行任何类后操作(例如整理外部资源);但是,可以使用@AfterClass方法来执行此操作。

对于您的问题,答案如下:没有干净的方法来执行此操作(只能同时设置两个规则)。 我们尝试为自动测试重试执行类似的任务,并结合了两条规则(用于此任务),实现了这种丑陋的方法:

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


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

我遇到过类似的问题,有两种解决方法。我不喜欢其中任何一种,但它们有不同的取舍:

(一) 如果规则公开清理方法,则可以在
@Before
方法中手动调用清理

@ClassRule
public static MyService service = ... ;

@Before
public void cleanupBetweenTests(){
     service.cleanUp();
}
这样做的缺点是,您需要记住(并告诉团队中的其他人)始终添加@Before方法,或者创建一个抽象类,您的测试从该抽象类继承来为您进行清理

2) 有两个字段,一个是指向同一对象的静态字段,一个是指向同一对象的非静态字段,每个字段分别由
@ClassRule
@Rule
注释。如果清理未暴露,则需要执行此操作。当然,缺点是您还必须记住@ClassRule和@Rule都指向同一个看起来很奇怪的东西

 @ClassRule
 public static MyService service = ... ;

@Rule
public MyService tmp = service ;
然后在您的实现中,您必须区分测试套件和单个测试。这可以通过检查
说明
是否有子项来完成。根据哪个适配器,您可以创建不同的
语句
适配器来处理清理或不处理清理:

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

    }
下面是自定义的
语句
类,需要在每次测试前进行清理:

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中的代码时会认为存在错误,因为同一字段被指向了两次。额外的努力、代码注释等都不值得

尽管如此,您仍然可以使用模板方法复制和粘贴任意位置或抽象类。

从JUnit 4.12开始(编写时未发布),可以使用
@rule
@ClassRule
对单个静态规则进行注释

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


如果您对更多详细信息感兴趣,请参阅。

规则的代码中,您如何区分它何时作为
@ClassRule
(在示例中设置外部资源)触发以及何时作为
@Rule
(在该示例中重置资源)?这取决于规则的确切实现。在我的例子中,可以询问外部资源以查看它是否已启动并正在运行。如果是,我们将作为
@规则
,因此我们运行base
语句
,然后重置。如果不是,我们将充当
@ClassRule
,因此我们设置资源,运行base
语句
,然后关闭资源。可以检查传递到
TestRule
中的
描述
,查看当前节点是测试(即没有子节点)还是套件(至少一个子节点),请参见GitHub上的,我问了同样的问题。我的用例基本上就是我在问题中给出的示例:我有一个外部资源(具体来说是一个实例),我想在类开始/结束时设置并拆除它,并在测试之间重置。我认为不可能使用一个规则来配置它。基于您在GitHub上发布的用例,我建议将这些规则放入一些基类中(不允许开发人员忘记这一点,也不允许复制代码),这是作为JUnit4.12的一部分发布的