22

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

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

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

在 JUnit 4.11 中,是否有一种简洁的方式将 @ClassRule 和 @Rule 功能组合到单个规则中?

谢谢,罗文

4

5 回答 5

14

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

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

如果您对更多细节感兴趣,请参阅发行说明我的拉取请求。

于 2014-06-19T06:30:15.723 回答
6

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

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

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

于 2013-12-25T12:47:13.337 回答
2

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

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

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

于 2013-12-26T15:54:26.037 回答
2

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

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

于 2013-12-26T11:17:43.020 回答
1

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

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

@ClassRule
public static MyService service = ... ;

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

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

2) 有 2 个字段,一个是静态的,一个是非静态的,它们指向同一个对象,每个字段分别由 a@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 中的代码,认为存在错误,因为同一个字段被指向两次。额外的努力、代码注释等是不值得的。

无论哪种方式,您仍然可以在任何地方复制和粘贴样板,或者使用模板方法抽象类。

于 2013-12-31T20:43:17.147 回答