2013-12-24 5 views
20

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

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

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

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

Спасибо, Роуэн

+1

См. Также [JUnit issue # 793] (https://github.com/junit-team/junit/issues/793) на GitHub, где я задал этот же вопрос. – Rowan

ответ

10

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

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

См. the release notes и my pull request, если вас интересует более подробная информация.

+0

Это было выпущено как часть JUnit 4.12 – NamshubWriter

5

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

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

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

+0

В коде 'Rule', как вы дифференцируетесь, когда он запускается как' @ ClassRule' (настраивает внешний ресурс в вашем примере) и когда он запускается как '@ Rule' (сброс ресурса в этом примере)? – Mureinik

+0

Это будет зависеть от точной реализации правила. В моем случае внешний ресурс может быть опрошен, чтобы узнать, запущен ли он. Если это так, мы действуем как '@ Rule', поэтому мы запускаем базовый' Statement' и затем перезагружаем. Если нет, мы действуем как '@ ClassRule', поэтому мы настраиваем ресурс, запускаем базовый' Statement' и затем закрываем ресурс. – Rowan

+0

«Описание», переданное в «TestRule», может быть проверено, чтобы проверить, не является ли текущий узел тестовым (т.е. без детей) или набором (хотя бы один ребенок). – NamshubWriter

2

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

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

2

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

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

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

+0

. Мой пример использования - это в основном пример, который я дал в вопросе: У меня есть внешний ресурс (конкретный экземпляр [WireMock] (http://wiremock.org/)), который я бы хотел настроить и отменить при запуске/окончании класса и сбросить между тестами. – Rowan

+0

Я думаю, что его невозможно настроить с помощью одного правила.Основываясь на примере использования, который вы опубликовали в GitHub, я бы предложил включить эти правила в некоторый базовый класс (чтобы разработчики не забывали об этом и не дублировали код) – user1459144

1

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

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

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

Смежные вопросы