Объединение @ClassRule и @Rule в JUnit 4.11
В JUnit 4.10 и ниже можно аннотировать правило как и @Rule, так и @ClassRule. Это означает, что правило вызывается перед классом до/после класса и до/после каждого теста. Одна из возможных причин для этого - установить дорогостоящий внешний ресурс (через вызовы @ClassRule), а затем дешево reset он (через вызовы @Rule).
Как и в случае JUnit 4.11, поля @Rule должны быть нестатическими, а поля @ClassRule должны быть статическими, поэтому выше это невозможно.
Есть очевидные обходные пути (например, явно разделять обязанности @ClassRule и @Rule на отдельные правила), но, похоже, стыдно иметь мандат на использование двух правил. Я кратко рассмотрел использование @Rule и вывод о том, является ли это первым/последним тестом, но я не считаю, что информация доступна (по крайней мере, она не доступна непосредственно в описании).
Есть ли опрятный и аккуратный способ комбинирования функций @ClassRule и @Rule в одном правиле в JUnit 4.11?
Спасибо,
Роуэн
Ответы
Ответ 1
Как и в случае JUnit 4.12 (невыпущенный во время записи), можно будет аннотировать одно статическое правило как с @Rule
, так и @ClassRule
.
Обратите внимание, что он должен быть статичным: нестатическое правило, аннотированное с помощью @Rule
и @ClassRule
, по-прежнему считается недействительным (поскольку что-либо аннотированное @ClassRule
работает на уровне класса, так что только действительно имеет смысл как статический член).
Смотрите примечания к выпуску и мой запрос на извлечение, если вас интересует более подробная информация.
Ответ 2
Другим возможным обходным путем является объявление статического @ClassRule и использование этого значения при объявлении нестатического @Rule тоже:
@ClassRule
public static BeforeBetweenAndAfterTestsRule staticRule = new BeforeBetweenAndAfterTestsRule();
@Rule
public BeforeBetweenAndAfterTestsRule rule = staticRule;
Это означает, что вам не нужно реорганизовывать существующие классы правил, но вам все равно нужно объявить два правила, поэтому он не отвечает на оригинальный вопрос особенно хорошо.
Ответ 3
Еще одно возможное обходное решение - объявить нестатический @Rule и заставить его действовать на статических соавторах: если соавтор еще не инициализирован, @Rule знает, что он работает в первый раз (так что он может настроить его сотрудников, например, запуск внешнего ресурса); если они инициализированы, @Rule может выполнять эту работу для каждого теста (например, перепродать внешний ресурс).
Это имеет недостаток, который @Rule не знает, когда он обработал последний тест, поэтому не может выполнять какие-либо действия после класса (например, переустанавливать внешний ресурс); однако вместо этого можно использовать метод @AfterClass.
Ответ 4
Ответ на ваш вопрос следующий: нет чистого способа сделать это (установите только два правила одновременно).
Мы попытались реализовать аналогичную задачу для повторных попыток автоматических тестов, и два правила были объединены (для этой задачи), и такой уродливый подход был реализован:
Тесты повторяются с двумя правилами
Но если более точно подумать над необходимой задачей (которая должна быть реализована), то лучший подход можно использовать с помощью jUnit custom Runner:
Retry Runner.
Итак, для лучшего подхода вам будет полезно узнать ваш конкретный вариант использования.
Ответ 5
У меня возникли аналогичные проблемы, есть две работы. Мне не нравится ни один, но у них разные компромиссы:
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, думая, что есть ошибка, поскольку одно и то же поле указано дважды. Дополнительные усилия, комментарии кодов и т.д. Не стоят того.
В то же время у вас все еще есть плита котла для копирования и вставки повсюду или абстрактных классов с использованием метода шаблона.