Junit - запустить метод настройки один раз

120

Я создал класс с парой тестов, и вместо использования @Beforeя хотел бы иметь метод настройки, который выполняется только один раз перед всеми тестами. Возможно ли это с Junit 4.8?

Bober02
источник
1
Взгляните на RunListener: stackoverflow.com/a/14773170/548473
Григорий Кислин

Ответы:

205

Хотя я согласен с @assylias, что использование @BeforeClass- классическое решение, оно не всегда удобно. Метод, помеченный значком, @BeforeClassдолжен быть статическим. Это очень неудобно для некоторых тестов, которым нужен экземпляр тестового примера. Например, тесты на основе Spring, которые используются @Autowiredдля работы со службами, определенными в контексте Spring .

В этом случае я лично использую обычный setUp()метод, аннотированный @Beforeаннотацией, и управляю своим настраиваемым static(!) booleanФлагом:

private static boolean setUpIsDone = false;
.....
@Before
public void setUp() {
    if (setUpIsDone) {
        return;
    }
    // do the setup
    setUpIsDone = true;
}
AlexR
источник
10
Добавление к комментарию Кенни Кейсона о том, почему он должен быть статичным. Он должен быть статическим, потому что JUnit создает новый экземпляр тестового класса для каждого метода @Test. Переменная экземпляра будет сброшена на значение по умолчанию (false) для каждого экземпляра, если оно не статично. См. Дополнительную информацию: martinfowler.com/bliki/JunitNewInstance.html
dustin.schultz
2
Это работает, за исключением случая, когда setUp()метод находится в суперклассе - ниже был опубликован ответ, пытающийся решить эту проблему.
Стив Чемберс,
4
Я не решаюсь сказать это кому-нибудь с репутацией в 84 тыс., Но BeforeClass фактически не отвечает на вопрос: BeforeClass запускается в начале каждого тестового класса. Но OP попросил тот, который запускается «только один раз перед всеми тестами». Предлагаемое вами решение могло бы сделать это, но вам нужно было бы сделать так, чтобы все ваши тестовые классы расширяли класс "CommonTest" ...
Майк грызун
1
@mikerodent, IMHO OP спросил обо всех тестах в его тестовом примере, а не обо всех тестах в целом. Итак, ваш комментарий менее актуален. Кстати, не бойтесь говорить что-либо любому человеку, даже если у него высокая репутация. По крайней мере, я так делаю :). И моя репутация была значительно ниже в августе 2012 года, когда я ответил на вопрос.
AlexR
В моем случае не работает, переменные, инициализированные в настройке, сбрасываются после каждого теста, поэтому запускать только один раз бессмысленно.
Aphax
89

Вы можете использовать в BeforeClassаннотации :

@BeforeClass
public static void setUpClass() {
    //executed only once, before the first test
}
assylias
источник
12
Я не могу использовать это, у меня есть несколько методов настройки, основанных на нестатических компонентах, таких как getClass ()
Bober02
1
@ Bober02 BeforeClass действительно должен быть статичным. Если вы не можете использовать это, другой ответ предлагает обходной путь.
assylias
2
Уверены, что вы не можете использовать TheClassYouWant.classвместо вызова getClass ()? Это актуально Java: String.class.getName().
stolsvik
1
@mikerodent Я понял вопрос как «все тесты в классе» - но вы правы, это может быть не то, что хотел OP.
assylias
29

JUnit 5 теперь имеет аннотацию @BeforeAll:

Обозначает, что аннотированный метод должен выполняться перед всеми методами @Test в текущем классе или иерархии классов; аналогично @BeforeClass из JUnit 4. Такие методы должны быть статическими.

Аннотации жизненного цикла JUnit 5, похоже, наконец-то все поняли! Вы можете догадаться, какие аннотации доступны, даже не глядя (например, @BeforeEach @AfterAll)

Брайан
источник
6
У него та же проблема @BeforeClass, что и должна быть static. Решение IMO @ AlexR лучше.
zengr
@zengr склонен согласиться с вами: как я сказал AlexR, его решение требует, чтобы все тестовые классы были подклассами от класса CommonTest, если он должен запускаться только один раз. Но это настолько просто, насколько это возможно, и ИМХО вам, вероятно, не следует использовать "модное" решение, поставляемое фреймворком, когда простой механизм доступен из языка. Если, конечно, нет на то веской причины. Кроме того, использование такой простой вещи, как его, с хорошим названием типа «делает то, что написано на жестяной коробке», помогает улучшить читаемость.
Майк грызун
Сказав это, опять же ИМХО, кажется, гораздо больше оправданий для аннотации «AfterAll»: было бы очень сложно и надумано разработать механизм для определения того, когда все тесты были выполнены. И наоборот, конечно, пуристы, вероятно, скажут, что вам никогда не следует делать «окончательную очистку», т.е. что каждый «tearDown» должен оставлять все ресурсы в первозданном состоянии ... и они, вероятно, правы!
Майк грызун
Работает ли это с Maven, где есть несколько модулей, каждый со своими тестами?
Марк Бун
@mike rodent, в моем случае установка и удаление тестовых файлов в файловой системе до / после каждого теста, похоже, приводит к тупикам в файлах. На данный момент я самостоятельно пришел к решению AlexR по настройке один раз. У меня есть два статических флага, уже установленный и грязный. setup () вызывает cleanup (), если изначально обнаружено «грязное» состояние или если сбой установки приводит к «грязному» состоянию. Чтобы очистить после запуска тестов, я запускаю их снова. Грязный, совсем не идеальный, не в нашем процессе сборки. Все еще ищу способ получше (jUnit 4.12).
Ребекка
9

Когда он setUp()находится в суперклассе тестового класса (например, AbstractTestBaseниже), принятый ответ можно изменить следующим образом:

public abstract class AbstractTestBase {
    private static Class<? extends AbstractTestBase> testClass;
    .....
    public void setUp() {
        if (this.getClass().equals(testClass)) {
            return;
        }

        // do the setup - once per concrete test class
        .....
        testClass = this.getClass();
    }
}

Это должно сработать для одного нестатического setUp()метода, но я не могу создать его эквивалент, tearDown()не попадая в мир сложных отражений ... Награда указывает всем, кто может!

Стив Чемберс
источник
3

Изменить: я только что узнал при отладке, что класс также создается перед каждым тестом. Думаю, аннотация @BeforeClass здесь лучше всего.

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

public UT () {
    // initialize once here
}
@Test
// Some test here...

Ctor будет вызываться перед тестами, потому что они не статичны.


источник
0

Попробуйте это решение: https://stackoverflow.com/a/46274919/907576 :

с аннотацией @BeforeAllMethods/ @AfterAllMethodsвы можете выполнить любой метод в классе Test в контексте экземпляра, где доступны все введенные значения.

radistao
источник
Полагается на стороннюю библиотеку.
Эндрю
0

Мое грязное решение:

public class TestCaseExtended extends TestCase {

    private boolean isInitialized = false;
    private int serId;

    @Override
    public void setUp() throws Exception {
        super.setUp();
        if(!isInitialized) {
            loadSaveNewSerId();
            emptyTestResultsDirectory();
            isInitialized = true;
        }
    }

   ...

}

Я использую его как базу для всех своих тестов.

Оби Два
источник
открытый класс TestCaseExtended расширяет TestCase {частное статическое логическое значение isInitialized = false; частный статический TestCaseExtended caseExtended; частный int serId; @Override public void setUp () выдает исключение {super.setUp (); если (! isInitialized) {caseExtended = новый TestCaseExtended (); caseExtended.loadSaveNewSerId (); caseExtended.emptyTestResultsDirectory (); isInitialized = true; }}
Оби Два
0

Если вы не хотите принудительно объявлять переменную, которая устанавливается и проверяется в каждом подтесте, тогда добавление этого в SuperTest может сделать:

public abstract class SuperTest {

    private static final ConcurrentHashMap<Class, Boolean> INITIALIZED = new ConcurrentHashMap<>();
    protected final boolean initialized() {
        final boolean[] absent = {false};
        INITIALIZED.computeIfAbsent(this.getClass(), (klass)-> {
            return absent[0] = true;
        });
        return !absent[0];
    }
}



public class SubTest extends SuperTest {
    @Before
    public void before() {
        if ( super.initialized() ) return;

         ... magic ... 
    }

}
ммм
источник
0

Я решил эту проблему так:

Добавьте в свой базовый абстрактный класс (я имею в виду абстрактный класс, в котором вы инициализируете свой драйвер в методе setUpDriver () ) эту часть кода:

private static boolean started = false;
static{
    if (!started) {
        started = true;
        try {
            setUpDriver();  //method where you initialize your driver
        } catch (MalformedURLException e) {
        }
    }
}

И теперь, если ваши тестовые классы будут расширяться от базового абстрактного класса -> метод setUpDriver () будет выполняться перед первым @Test только ОДИН раз за запуск.

Sergii
источник
0

Используйте метод Spring @PostConstruct для выполнения всей работы по инициализации, и этот метод запускается до выполнения любого из @Test.

Абхишек Чаттерджи
источник