Перейти к основному содержимому

Руководство по правилам JUnit 4

· 9 мин. чтения

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.
  • Аннотировать поле аннотацией @Rule

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

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 , чтобы убедиться, что какой-то код генерирует ожидаемое исключение :

@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.foreach.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. Правило верификатора _

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

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

private List messageLog = new ArrayList();

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

Здесь мы определяем новый Verifier и переопределяем метод 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. Использование цепочек правил

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

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

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

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

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

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

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

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

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

Как всегда, полный исходный код статьи доступен на GitHub .