Объединение @ClassRule и @Rule в JUnit 4.11

В JUnit 4.10 и ниже можно аннотировать правило как @Rule, так и @ClassRule. Это означает, что правило вызывается до / после класса и до / после каждого теста. Одной из возможных причин для этого является создание дорогого внешнего ресурса (через вызовы @ClassRule), а затем его дешевый сброс (через вызовы @Rule).

Начиная с JUnit 4.11, поля @Rule должны быть нестатичными, а поля @ClassRule должны быть статическими, поэтому вышеописанное невозможно.

Существуют очевидные обходные пути (например, явное разделение обязанностей @ClassRule и @Rule на отдельные правила), но, кажется, стыдно требовать использования двух правил. Я кратко рассмотрел использование @Rule и определил, является ли это первым / последним тестом, но я не верю, что информация доступна (по крайней мере, она недоступна непосредственно в Description).

Есть ли аккуратный и опрятный способ объединения функциональности @ClassRule и @Rule в одном правиле в JUnit 4.11?

Спасибо рябина

5 ответов

Решение

Начиная с версии JUnit 4.12 (не выпущенной на момент написания), можно будет аннотировать одно статическое правило обоими @Rule а также @ClassRule,

Обратите внимание, что оно должно быть статическим - нестатическое правило с комментариями @Rule а также @ClassRule все еще считается недействительным (как и все аннотированные @ClassRule работает на уровне класса, поэтому имеет смысл только как статический член).

См. Примечания к выпуску и мой запрос на извлечение, если вы заинтересованы в более подробной информации.

Другой возможный обходной путь - объявить статический @ClassRule и использовать это значение при объявлении нестатического @Rule:

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

Это означает, что вам не нужно проводить рефакторинг каких-либо существующих классов правил, но вам все равно нужно объявить два правила, чтобы он не отвечал на первоначальный вопрос особенно хорошо.

Ответ на ваш вопрос следующий: нет чистого способа сделать это (только настроить два правила одновременно). Мы попытались реализовать аналогичную задачу для повторных автоматических тестов, и были объединены два правила (для этой задачи), и был реализован такой уродливый подход: повторные тесты с двумя правилами

Но если более точно подумать о необходимой задаче (которая должна быть реализована), можно использовать лучший подход, используя пользовательский Runner jUnit: Retry Runner.

Таким образом, для лучшего подхода будет полезно знать ваш конкретный вариант использования.

Еще один возможный обходной путь - объявить нестатический @Rule и заставить его действовать на статических коллабораторов: если коллаборатор еще не инициализирован, @Rule знает, что работает впервые (поэтому он может настроить своих коллабораторов, например, запуск внешнего ресурса); если они инициализированы, @Rule может выполнять тестовую работу (например, переустанавливать внешний ресурс).

Это имеет тот недостаток, что @Rule не знает, когда он обработал последний тест, поэтому не может выполнять какие-либо внеклассные действия (например, убирать внешний ресурс); однако для этого можно использовать метод @AfterClass.

У меня были похожие проблемы, есть два обходных пути. Мне не нравится ни один, но у них есть различные компромиссы:

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, думая, что была ошибка, так как одно и то же поле указывалось дважды. Дополнительные усилия, комментарии к коду и т. Д. Не стоят того.

В любом случае у вас все еще есть своя табличка для копирования и вставки везде или абстрактные классы с использованием шаблонного метода.

Другие вопросы по тегам