Как определить правило метода JUnit в наборе тестов?
У меня есть класс, который является набором тестовых классов JUnit. Я хотел бы определить правило для набора, чтобы сделать что-то с базой данных до и после каждого модульного теста, если в этом методе теста есть определенная аннотация.
Мне удалось создать @ClassRule в наборах и тестовых классах, которые будут делать это перед каждым классом (что недостаточно), и я смог определить правила тестирования с помощью самих тестовых классов, но это повторяется и не кажется очень сухим.
Можно ли определить правило для каждого метода тестирования в комплекте или я должен добавить их в каждый тест?
Редактировать: чтобы уточнить, я хочу объявить код в наборе, который будет выполняться между (то есть "вокруг") тестовыми методами в тестовых классах.
5 ответов
Это можно сделать, но для этого нужно немного поработать. Вам также нужно определить свой собственный Runner Suite и собственный Runner Test, а затем переопределить runChild() в runner теста. Использование следующих классов Suite и Test:
@RunWith(MySuite.class)
@SuiteClasses({Class1Test.class})
public class AllTests {
}
public class Class1Test {
@Deprecated @Test public void test1() {
System.out.println("" + this.getClass().getName() + " test1");
}
@Test public void test2() {
System.out.println("" + this.getClass().getName() + " test2");
}
}
Обратите внимание, что я аннотировал test1()
с @Deprecated
, Вы хотите сделать что-то другое, когда у вас есть @Deprecated
аннотации к тесту, поэтому нам нужно расширить Suite для использования пользовательских Runner
:
public class MySuite extends Suite {
// copied from Suite
private static Class<?>[] getAnnotatedClasses(Class<?> klass) throws InitializationError {
Suite.SuiteClasses annotation = klass.getAnnotation(Suite.SuiteClasses.class);
if (annotation == null) {
throw new InitializationError(String.format("class '%s' must have a SuiteClasses annotation", klass.getName()));
}
return annotation.value();
}
// copied from Suite
public MySuite(Class<?> klass, RunnerBuilder builder) throws InitializationError {
super(null, getRunners(getAnnotatedClasses(klass)));
}
public static List<Runner> getRunners(Class<?>[] classes) throws InitializationError {
List<Runner> runners = new LinkedList<Runner>();
for (Class<?> klazz : classes) {
runners.add(new MyRunner(klazz));
}
return runners;
}
}
JUnit создает Runner
для каждого теста он будет выполняться. Как правило, Suite будет просто создать по умолчанию BlockJUnit4ClassRunner
все, что мы здесь делаем, это переопределение конструктора для Suite, который читает классы из SuiteClass
аннотации, и мы создаем наших собственных бегунов с ними, MyRunner
, Это наш класс MyRunner:
public class MyRunner extends BlockJUnit4ClassRunner {
public MyRunner(Class<?> klass) throws InitializationError {
super(klass);
}
@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
Description description= describeChild(method);
if (method.getAnnotation(Ignore.class) != null) {
notifier.fireTestIgnored(description);
} else {
if (description.getAnnotation(Deprecated.class) != null) {
System.out.println("name=" + description.getMethodName() + " annotations=" + description.getAnnotations());
}
runLeaf(methodBlock(method), description, notifier);
}
}
}
Большая часть этого скопирована с BlockJUnit4ClassRunner
, Бит, который я добавил:
if (description.getAnnotation(Deprecated.class) != null) {
System.out.println("name=" + description.getMethodName() + " annotations=" + description.getAnnotations());
}
где мы проверяем на существование @Deprecated
аннотации на метод, и сделайте что-нибудь, если он там есть. Остальное оставлено в качестве упражнения для читателя. Когда я запускаю вышеупомянутый пакет, я получаю в качестве вывода:
name=test1 annotations=[@java.lang.Deprecated(), @org.junit.Test(expected=class org.junit.Test$None, timeout=0)]
uk.co.farwell.junit.run.Class1Test test1
uk.co.farwell.junit.run.Class1Test test2
Обратите внимание, что Suite имеет несколько конструкторов в зависимости от того, как он вызывается. Вышеуказанное работает с Eclipse, но я не тестировал другие способы запуска Suite. Смотрите комментарии вместе с различными конструкторами для Suite для получения дополнительной информации.
Вы можете использовать RunListener, который вы добавляете в Suite. Он не дает вам всего, что может сделать правило, но предоставляет класс Description, в котором должны быть доступны аннотации. По крайней мере, я не думаю, что JUnit фильтрует его только по понятным аннотациям.
Разработчик JUnit только что обсудил механизм добавления RunListener в Suite здесь.
Само по себе добавление правила в класс с пометкой @RunWith(Suite.class)
не сделаю трюк. Я считаю, что это потому, что Suite
это ParentRunner<Runner>
а не Runner
такие как BlockJUnit4ClassRunner
который попытался бы очистить правила для классов, которые он запускает. Чтобы запустить своих детей, он говорит ребенку Runners
бежать. Те Runner
s, возможно, создали свои тесты, применяя правила к этим классам, но Suite
бегун не предпринимает никаких специальных действий, чтобы применить правила от самого себя к тестам своего потомка Runner
построить.
Вы пытались использовать "иерархию тестовых классов"? Я часто использую абстрактный класс теста, чтобы поделиться тестом или приспособлением. Например, все мои тесты БД инициализируют встроенный источник данных. Сначала я создаю абстрактный класс "DbTestCase", который обрабатывает логику инициализации. Тогда весь подкласс пойдет на пользу тесту и креплениям.
Однако иногда я сталкиваюсь с проблемой, когда мои тестовые примеры требуют много логики test/fixture, которую я не могу сохранить в одной иерархии. В этом случае только аспектное программирование решает проблему. Маркировка test/fixture-logic через специальные аннотации / интерфейсы, которые может реализовать любой требуемый класс.
При желании вы можете рассмотреть возможность обработки "аспекта" с помощью собственного бегуна, который "внедрит" тестовую / фиксированную логику в зависимости от аннотации / интерфейса тестируемых классов.
Вы можете группировать тесты с TestNG. И вы можете настроить TestNG для запуска некоторой логики @BeforeGroup и @AfterGroup.