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