Подтвердить что ты не робот

Как определить правило метода JUnit в наборе тестов?

У меня есть класс, который представляет собой набор JUnit для тестовых классов JUnit. Я хотел бы определить правило для suite, чтобы сделать что-то в базе данных до и после каждого unit test, если определенная аннотация присутствует в этом методе тестирования.

Мне удалось создать @ClassRule в наборах и тестовых классах, которые будут делать это перед каждым классом (что не достаточно хорошо), и я смог определить правила тестирования самими тестовыми классами, но это повторяется и не кажется очень сухим.

Можно ли определить правило для каждого теста в наборе или добавить его в каждый тест?

Изменить: Чтобы уточнить, я хочу объявить код в пакете, который будет работать между (то есть "вокруг" ) методами тестирования в тестовых классах.

4b9b3361

Ответ 1

Это можно сделать, но для этого нужна небольшая работа. Вам также нужно определить свой собственный бегун Suite и собственный тестовый бегун, а затем переопределить runChild() в тестовом бегуне. Использование следующих классов 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 на методе и делаем что-то, если оно есть. Остальное остается упражнением для читателя. Когда я запускаю вышеупомянутый Suite, я получаю вывод:

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 для получения дополнительной информации.

Ответ 2

Вы можете использовать RunListener, которые вы добавляете в Suite. Это не дает вам все, что может сделать правило, но оно предоставит вам класс описания, который должен содержать аннотации. По крайней мере, я не думаю, что JUnit отфильтровывает его только для своих понятных аннотаций.

Разработчик JUnit только что обсудил механику добавления RunListener в Suite здесь.

Ответ 3

Сама по себе добавление правила в класс, аннотированный с помощью @RunWith(Suite.class), не будет делать трюк. Я считаю, что это связано с тем, что Suite - это ParentRunner<Runner>, а не Runner, например BlockJUnit4ClassRunner, который попытается очистить правила от выполняемых им классов. Чтобы запустить его дочерние элементы, он сообщает дочернему элементу Runners выполнить. Те, что Runner, возможно, создали свои тесты, применяя правила для этих классов, но бегун Suite не предпринимает никаких специальных действий для применения правил от себя к тестам своей дочерней сборки Runner.

Ответ 4

Вы пытались использовать "иерархию тестовых классов"? Я часто использую абстрактный класс тестов для обмена тестом или приспособлением. Например, все мои тесты БД инициализируют встроенный источник данных. Сначала я создаю абстрактный класс "DbTestCase", который обрабатывает логику init. Тогда весь подкласс принесет пользу тесту и светильникам.

Однако иногда я сталкиваюсь с проблемой, когда в моих тестовых случаях требуется много тестов/логических устройств, которые я не могу хранить в одной иерархии. В этом случае проблема только в аспекте. Маркировка test/fixture-logic через конкретные аннотации/интерфейсы, которые может реализовать любой требуемый класс.

Вы можете опционально рассмотреть возможность обработки "аспекта" с помощью специального бегуна, который будет "вводить" тестовую/фиксированную логику в зависимости от аннотации/интерфейса тестируемых классов.