«1. Обзор

В этом руководстве мы рассмотрим функцию Rules, предоставляемую библиотекой JUnit 4.

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

Чтобы узнать больше о тестировании с помощью JUnit, ознакомьтесь с нашей обширной серией статей о JUnit.

Обратите внимание, что если вы используете JUnit 5, правила были заменены моделью расширения.

2. Знакомство с правилами JUnit 4

Правила JUnit 4 предоставляют гибкий механизм улучшения тестов за счет запуска некоторого кода вокруг выполнения тестового примера. В некотором смысле это похоже на аннотации @Before и @After в нашем тестовом классе.

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

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

3. Использование правил JUnit 4

Так как же мы можем использовать правила? Мы можем использовать правила JUnit 4, выполнив следующие простые шаги:

    Добавьте общедоступное поле в наш тестовый класс и убедитесь, что тип этого поля является подтипом интерфейса org.junit.rules.TestRule Аннотируйте поле @ Аннотация к правилу

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

4. Зависимости Maven

Во-первых, давайте добавим зависимости проекта, которые нам понадобятся для наших примеров. Нам понадобится только основная библиотека JUnit 4:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

Как всегда, мы можем получить последнюю версию от Maven Central.

5. Правила, представленные в дистрибутиве

Конечно, JUnit предоставляет ряд полезных предопределенных правил как часть библиотеки. Мы можем найти все эти правила в пакете org.junit.rules.

В этом разделе мы увидим несколько примеров их использования.

5.1. Правило TemporaryFolder

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

@Rule
public TemporaryFolder tmpFolder = new TemporaryFolder();

@Test
public void givenTempFolderRule_whenNewFile_thenFileIsCreated() throws IOException {
    File testFile = tmpFolder.newFile("test-file.txt");

    assertTrue("The file should have been created: ", testFile.isFile());
    assertEquals("Temp folder and test file should match: ", 
      tmpFolder.getRoot(), testFile.getParentFile());
}

Как мы видим, сначала мы определяем правило TemporaryFolder tmpFolder. Затем наш тестовый метод создает файл с именем test-file.txt во временной папке. Затем мы проверяем, что файл был создан и существует там, где должен. Действительно красиво и просто!

Когда тест завершится, временную папку и файл следует удалить. Однако это правило не проверяет успешность удаления.

В этом классе есть еще несколько интересных методов, о которых стоит упомянуть:

    newFile() Если мы не указываем имя файла, то этот метод создает новый файл со случайным именем. newFolder(String… folderNames) Чтобы создать рекурсивно глубокие временные папки, мы можем использовать этот метод.
    newFolder() Аналогичным образом, метод newFolder() создает новую папку со случайным именем.

Стоит отметить приятное дополнение: начиная с версии 4.13, правило TemporaryFolder позволяет проверять удаленные ресурсы:

@Rule 
public TemporaryFolder folder = TemporaryFolder.builder().assureDeletion().build();

Если ресурс не может быть удален, тест завершается с ошибкой AssertionError.

Наконец, в JUnit 5 мы можем добиться той же функциональности, используя расширение Temporary Directory.

5.2. Правило ExpectedException

Как следует из названия, мы можем использовать правило ExpectedException, чтобы убедиться, что некоторый код выдает ожидаемое исключение:

@Rule
public final ExpectedException thrown = ExpectedException.none();

@Test
public void givenIllegalArgument_whenExceptionThrown_MessageAndCauseMatches() {
    thrown.expect(IllegalArgumentException.class);
    thrown.expectCause(isA(NullPointerException.class));
    thrown.expectMessage("This is illegal");

    throw new IllegalArgumentException("This is illegal", new NullPointerException());
}

Как видно из приведенного выше примера, мы сначала объявляем ExpectedException правило. Затем в нашем тесте мы утверждаем, что выбрасывается исключение IllegalArgumentException.

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

Чтобы получить подробное руководство по тестированию исключений с помощью JUnit, ознакомьтесь с нашим превосходным руководством о том, как подтвердить исключение.

5.3. Правило TestName

«Проще говоря, правило TestName предоставляет текущее имя теста внутри данного метода тестирования:

@Rule public TestName name = new TestName();

@Test
public void givenAddition_whenPrintingTestName_thenTestNameIsDisplayed() {
    LOG.info("Executing: {}", name.getMethodName());
    assertEquals("givenAddition_whenPrintingTestName_thenTestNameIsDisplayed", name.getMethodName());
}

В этом тривиальном примере, когда мы запускаем модульный тест, мы должны увидеть имя теста в выводе:

INFO  c.baeldung.rules.JUnitRulesUnitTest - 
    Executing: givenAddition_whenPrintingTestName_thenTestNameIsDisplayed

~~ ~ 5.4. Правило тайм-аута

В следующем примере мы рассмотрим правило тайм-аута. Это правило предлагает полезную альтернативу использованию параметра тайм-аута в отдельной тестовой аннотации.

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

@Rule
public Timeout globalTimeout = Timeout.seconds(10);

@Test
public void givenLongRunningTest_whenTimout_thenTestFails() throws InterruptedException {
    TimeUnit.SECONDS.sleep(20);
}

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

Когда мы запустим этот тест, мы должны увидеть сбой теста:

org.junit.runners.model.TestTimedOutException: test timed out after 10 seconds
...

5.5. Правило ErrorCollector

Далее мы рассмотрим правило ErrorCollector. Это правило позволяет продолжить выполнение теста после обнаружения первой проблемы.

Давайте посмотрим, как мы можем использовать это правило, чтобы собрать все ошибки и сообщить о них сразу после завершения теста:

@Rule 
public final ErrorCollector errorCollector = new ErrorCollector();

@Test
public void givenMultipleErrors_whenTestRuns_thenCollectorReportsErrors() {
    errorCollector.addError(new Throwable("First thing went wrong!"));
    errorCollector.addError(new Throwable("Another thing went wrong!"));
        
    errorCollector.checkThat("Hello World", not(containsString("ERROR!")));
}

В приведенном выше примере мы добавляем две ошибки в сборщик. Когда мы запускаем тест, выполнение продолжается, но в конце тест завершится ошибкой.

В выводе мы увидим обе ошибки:

java.lang.Throwable: First thing went wrong!
...
java.lang.Throwable: Another thing went wrong!

5.6. Правило верификатора

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

Давайте теперь рассмотрим тривиальный пример определения нашего собственного верификатора:

private List messageLog = new ArrayList();

@Rule
public Verifier verifier = new Verifier() {
    @Override
    public void verify() {
        assertFalse("Message Log is not Empty!", messageLog.isEmpty());
    }
};

Здесь мы определяем новый верификатор и переопределяем метод verify(), чтобы добавить дополнительную логику проверки. В этом простом примере мы просто проверяем, не пуст ли журнал сообщений в нашем примере.

Теперь, когда мы запустим модульный тест и добавим сообщение, мы должны увидеть, что наш верификатор был применен:

@Test
public void givenNewMessage_whenVerified_thenMessageLogNotEmpty() {
    // ...
    messageLog.add("There is a new message!");
}

5.7. Правило DisableOnDebug

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

Правило DisableOnDebug делает именно это и позволяет нам пометить определенные правила для отключения при отладке:

@Rule
public DisableOnDebug disableTimeout = new DisableOnDebug(Timeout.seconds(30));

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

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

5.8. Правило внешнего ресурса

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

Мы можем расширить абстрактный класс ExternalResource для настройки внешнего ресурса перед тестом, например файла или подключения к базе данных. На самом деле правило TemporaryFolder, которое мы видели ранее, расширяет ExternalResource.

Давайте кратко рассмотрим, как мы можем расширить этот класс:

@Rule
public final ExternalResource externalResource = new ExternalResource() {
    @Override
    protected void before() throws Throwable {
        // code to set up a specific external resource.
    };
    
    @Override
    protected void after() {
        // code to tear down the external resource
    };
};

В этом примере, когда мы определяем внешний ресурс, нам просто нужно переопределить методы before() и after(), чтобы настроить и снести наш внешний ресурс.

6. Применение правил класса

До сих пор все примеры, которые мы рассматривали, применялись к методам одного теста. Однако иногда нам может понадобиться применить правило на уровне тестового класса. Мы можем сделать это, используя аннотацию @ClassRule.

Эта аннотация работает очень похоже на @Rule, но оборачивает правило вокруг всего теста — главное отличие состоит в том, что поле, которое мы используем для нашего правила класса, должно быть статическим:

@ClassRule
public static TemporaryFolder globalFolder = new TemporaryFolder();

7. Определение пользовательского Правило JUnit

Как мы видели, JUnit 4 предоставляет ряд полезных правил из коробки. Конечно, мы можем определить свои собственные правила. Чтобы написать пользовательское правило, нам нужно реализовать интерфейс TestRule.

Давайте рассмотрим пример определения пользовательского правила логгера имени тестового метода:

public class TestMethodNameLogger implements TestRule {

    private static final Logger LOG = LoggerFactory.getLogger(TestMethodNameLogger.class);

    @Override
    public Statement apply(Statement base, Description description) {
        logInfo("Before test", description);
        try {
            return new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    base.evaluate();
                }
            };
        } finally {
            logInfo("After test", description);
        }
    }

    private void logInfo(String msg, Description description) {
        LOG.info(msg + description.getMethodName());
    }
}

«

«Как мы видим, интерфейс TestRule содержит один метод с именем apply(Statement, Description), который мы должны переопределить, чтобы вернуть экземпляр Statement. Оператор представляет наши тесты в среде выполнения JUnit. Когда мы вызываем метод Assessment(), он выполняет наш тест.

В этом примере мы регистрируем сообщение «до» и «после» и включаем из объекта «Описание» имя метода отдельного теста.

8. Использование цепочек правил

@Rule
public RuleChain chain = RuleChain.outerRule(new MessageLogger("First rule"))
    .around(new MessageLogger("Second rule"))
    .around(new MessageLogger("Third rule"));

В этом заключительном разделе мы рассмотрим, как можно упорядочить несколько тестовых правил с помощью правила RuleChain:

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

Starting: First rule
Starting: Second rule
Starting: Third rule
Finished: Third rule
Finished: Second rule
Finished: First rule

Когда мы запустим наш тест, мы увидим, как цепочка применяется по порядку:

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

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

Во-первых, мы начали с объяснения, что такое правила и как мы можем их использовать. Затем мы подробно рассмотрели правила, входящие в состав дистрибутива JUnit.

Наконец, мы рассмотрели, как мы можем определить наше собственное правило и как объединить правила в цепочку.