«1. Введение

Тестовые классы часто содержат переменные-члены, относящиеся к тестируемой системе, макетам или ресурсам данных, используемым в тесте. По умолчанию и JUnit 4, и 5 создают новый экземпляр тестового класса перед запуском каждого тестового метода. Это обеспечивает четкое разделение состояний между тестами.

В этом уроке мы узнаем, как JUnit 5 позволяет нам изменять жизненный цикл тестового класса с помощью аннотации @TestInstance. Мы также увидим, как это может помочь нам в управлении большими ресурсами или более сложными отношениями между тестами.

2. Жизненный цикл тестов по умолчанию

Давайте начнем с рассмотрения жизненного цикла тестового класса по умолчанию, общего для JUnit 4 и 5:

class AdditionTest {

    private int sum = 1;

    @Test
    void addingTwoReturnsThree() {
        sum += 2;
        assertEquals(3, sum);
    }

    @Test
    void addingThreeReturnsFour() {
        sum += 3;
        assertEquals(4, sum);
    }
}

Этот код вполне может быть тестовым кодом JUnit 4 или 5, за исключением отсутствует общедоступное ключевое слово, которое не требуется для JUnit 5.

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

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

3. Аннотации @BeforeClass и @BeforeAll

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

JUnit 4 решает эту проблему с помощью аннотации @BeforeClass:

private static String largeContent;

@BeforeClass
public static void setUpFixture() {
    // read the file and store in 'largeContent'
}

Следует отметить, что мы должны сделать переменные и методы, аннотированные аннотацией @BeforeClass JUnit 4, статическими.

JUnit 5 предлагает другой подход. Он предоставляет аннотацию @BeforeAll, которая используется в статической функции для работы со статическими членами класса.

Однако @BeforeAll также можно использовать с функцией экземпляра и членами экземпляра, если жизненный цикл тестового экземпляра изменен на для каждого класса.

4. Аннотация @TestInstance

Аннотация @TestInstance позволяет настроить жизненный цикл тестов JUnit 5.

@TestInstance имеет два режима. Одним из них является LifeCycle.PER_METHOD (по умолчанию). Другой — LifeCycle.PER_CLASS. Последнее позволяет нам попросить JUnit создать только один экземпляр тестового класса и повторно использовать его между тестами.

Давайте аннотируем наш тестовый класс аннотацией @TestInstance и используем режим LifeCycle.PER_CLASS:

@TestInstance(LifeCycle.PER_CLASS)
class TweetSerializerUnitTest {

    private String largeContent;

    @BeforeAll
    void setUpFixture() {
        // read the file
    }

}

Как мы видим, ни одна из переменных или функций не является статической. Нам разрешено использовать метод экземпляра для @BeforeAll, когда мы используем жизненный цикл PER_CLASS.

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

5. Использование @TestInstance(PER_CLASS)

5.1. Дорогие ресурсы

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

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

5.2. Преднамеренное совместное использование состояния

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

При совместном использовании состояния для последовательного выполнения всех тестов JUnit 5 предоставляет нам аннотацию @TestMethodOrder уровня типа. Затем мы можем использовать аннотацию @Order для тестовых методов, чтобы выполнять их в выбранном нами порядке.

@TestMethodOrder(OrderAnnotation.class)
class OrderUnitTest {

    @Test
    @Order(1)
    void firstTest() {
        // ...
    }

    @Test
    @Order(2)
    void secondTest() {
        // ...
    }

}

5.3. Совместное использование некоторого состояния

Проблема с совместным использованием одного и того же экземпляра тестового класса заключается в том, что некоторые члены могут нуждаться в очистке между тестами, а некоторые могут поддерживаться в течение всего теста.

«Мы можем сбросить переменные, которые необходимо очищать между тестами, с помощью методов, аннотированных с помощью @BeforeEach или @AfterEach.

6. Заключение

В этом руководстве мы узнали об аннотации @TestInstance и о том, как ее можно использовать для настройки жизненного цикла тестов JUnit 5.

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

Как всегда, код этого руководства можно найти на GitHub.