JUnit: модульное тестирование в Java и test-driven development
Изучаем главный инструмент тестировщика приложений на Java и пишем первую программу по методологии TDD.
Иллюстрация: Оля Ежак для Skillbox Media
Пока приложение или сайт небольшие, для контроля качества достаточно мануальных тестировщиков, проверяющих работу «руками». Но что делать, когда в приложении уже сотни файлов и десятки тысяч строк кода? Тестировать вручную становится долго и дорого.
Для таких случаев используют специальные тестовые фреймворки. Один из них — JUnit, фреймворк для модульного тестирования Java-приложений. Он позволяет проводить автоматизированные юнит- и интеграционные тесты.
Из этой статьи вы узнаете:
- что такое JUnit;
- какие аннотации в нём используются;
- в чём разница между JUnit4 и JUnit5;
- как установить JUnit;
- как он работает;
- что такое test-driven development;
- как написать приложение по принципам TDD.
Что такое JUnit
JUnit — фреймворк для автоматического юнит-тестирования приложений. Он содержит специальные функции и правила, которые позволяют легко писать и запускать тесты, то есть проверять, что каждый блок кода, или модуль, ответственный за определённую функцию программы, работает как надо. Такой вид тестирования называют модульным, или юнит-тестированием.
Последняя версия фреймворка — JUnit 5. Она состоит из трёх модулей: JUnit Platform, JUnit Jupiter и JUnit Vintage.
JUnit Platform — основной модуль для управления тестами.
JUnit Jupiter — модуль, который использует новые возможности Java 8. Он предоставляет API на основе аннотаций и позволяет работать с модульными и динамическими тестами.
JUnit Vintage — модуль для поддержки тестов, написанных с использованием JUnit 3 и JUnit 4.
JUnit удобен тем, что разработчик может гибко указывать условия тестирования. Например, объединять тесты в группы, распределяя их по функциональности, тестируемым модулям или уровню критичности, прописывать условия запуска для каждого блока кода и анализировать результаты по отдельности. Всё это облегчает работу программиста или QA-инженера.
Читайте также:
Аннотации в JUnit
Аннотации в JUnit — это специальные метки, которые Java-разработчик размещает перед методами в тестовом классе. Они позволяют настраивать процесс тестирования, указывая фреймворку, как именно их следует обрабатывать. Например, можно явно указать, какие из методов являются тестовыми случаями, какие из них выполнять перед тестами и после и так далее.
Вот несколько базовых аннотаций.
@Test. Эту аннотацию ставим перед методами, которые относятся к тестовым случаям. JUnit поймёт, что их следует выполнять в качестве теста, а по завершении проверить результат.
@Before. Используется для методов, которые должны быть выполнены перед каждым тестовым случаем. Например, если у нас есть несколько тестов, которые требуют одних и тех же начальных условий, мы можем обозначить метод с аннотацией @Before, задав необходимые условия тестирования один раз.
@After. Эту аннотацию используем перед методом, который должен быть выполнен после тестового случая.
@BeforeClass, @AfterClass. Методы с аннотацией @BeforeClass выполняются перед запуском первого теста в классе, а методы с аннотацией @AfterClass — после завершения всех тестов в классе.
@Ignore. Используется перед методом, чтобы отключить его выполнение в тесте. Это может быть полезно, если мы не уверены в работоспособности отдельных тестов и не хотим их использовать, но должны оставить в коде.
@BeforeEach и @AfterEach. Аналоги @Before и @After в JUnit 4.
Полный список аннотаций с подробными объяснениями и примерами использования можно прочесть в документации.
Вот как аннотации выглядят в коде:
JUnit 4 и JUnit 5
JUnit 4 и JUnit 5 — это две последние версии фреймворка JUnit, которые сейчас распространены в разработке. Между собой они различаются функциональностью, синтаксисом и возможностями.
JUnit 4
Тестовые методы в JUnit 4 помечаются аннотацией @Test. Для определения методов, выполняющихся до и после тестового случая, используют аннотации @Before и @After.
В этой версии фреймворка поддерживаются параметризованные тесты с использованием аннотации @RunWith(Parameterized.class). Это значит, что в тест можно передать параметры, необходимые для тестирования.
Тесты, написанные на JUnit 4, могут выполняться в JUnit 5 с использованием JUnit Vintage.
JUnit 5
В пятой версии фреймворка появились новые модули: Jupiter — для тестирования с использованием возможностей Java 8, и Platform — модуль для запуска тестов.
Также в JUnit 5 появилась возможность писать собственные расширения для тестов и запускать их по аннотации @ExtendWith. По аналогии с JUnit 4 поддерживаются параметризованные тесты с аннотацией @ParameterizedTest.
В примерах с кодом мы будем использовать JUnit 5 — это современная версия фреймворка, которая поддерживает Java 8 и JUnit 4.
Устанавливаем JUnit
Всё просто — добавляем необходимую зависимость в конфигурационный файл сборщика.
Для Maven:
- Зайдите в файл pom.xml.
- Найдите секцию <dependencies>.
- Добавьте внутрь блок:
- Сохраните изменения.
Для Gradle:
- Зайдите в build.gradle.
- Найдите секцию dependencies.
- Добавьте внутрь блок с кодом:
Важно, что при работе с Gradle необходимо указать версию фреймворка. Мы рекомендуем использовать наиболее актуальную. Посмотреть её можно на главной странице сайта под заголовком Latest Release.
- Сохраните изменения.
Как работает JUnit
Напишем на Java простой калькулятор:
Для модульного тестирования калькулятора нам требуется написать отдельные тесты для сложения, вычитания, умножения и два теста для деления. С JUnit код будет такой:
Разберем его:
import org.junit.jupiter.api.Test; — здесь мы импортировали аннотацию Test из фреймворка JUnit. Она помечает методы как тестовые случаи, определяя их выполнение во время запуска тестов.
import static org.junit.jupiter.api.Assertions.*; — импортировали статические методы утверждений (assertions) из класса Assert — assertEquals(expected, actual). Они сравнивают ожидаемые и фактические результаты тестов. Если результаты не совпадают, то тест считается не пройденным.
public class CalculatorTest {… } — определили класс для наших тестов.
Далее мы прописали тестовые методы, например testAddition(), testSubtraction(), testMultiplication(), public void testDivision(). Внутри каждого метода тестируем конкретную арифметическую операцию. Для этого мы сравниваем результат работы калькулятора с заранее подобранным правильным ответом с помощью assertEquals.
Для каждого теста создали экземпляр класса Calculator, который будет использоваться для их проведения.
В этом примере мы сначала написали программу, а потом — тесты для неё. Но иногда разработчики используют другой подход.
Test-driven development
Test-driven development (TDD) — это подход к разработке программ, при котором разработчик сначала описывает тесты для функции, которую хочет создать, а затем пишет сам код, который проходит эти тесты.
При таком подходе главные издержки разработки — время на рефакторинг и исправление ошибок — снижаются. А значит, уменьшаются и затраты на создание и поддержку продукта.
Упрощённо TDD можно представить в виде нескольких шагов:
- Написание теста. Разработчик начинает работу с создания теста, который будет проверять работоспособность кода.
- Запуск теста. При первом запуске тест не должен быть пройден, так как функционального кода программы ещё нет.
- Написание кода. Разработчик пишет код с минимальной функциональностью, которая позволяет успешно пройти тест.
- Повторный запуск теста. При повторном запуске тест должен быть пройден удачно.
- Рефакторинг. После успешного завершения тестов разработчик может приступить к рефакторингу — улучшению и оптимизации кода. Важно, что после каждого изменения запуск теста повторяется.
Каждый функциональный модуль, который добавляется в приложение, должен пройти эти этапы. Таким образом, разработчик уже в процессе написания отдельных частей программы подтверждает, что они успешно проходят тесты.
Создаём приложение по принципам test‑driven development
Используя подход TDD, создадим простое приложение — программу для вычисления факториала числа. Сначала напишем тесты, а затем функциональный код.
Работать будем в среде разработки IntelliJ IDEA с Maven на борту. Как создать и инициализировать проект в Maven и подключить JUnit, смотрите выше. А мы перейдём к коду.
Пишем тест
Создайте файл для тестов в папке test\java. У нас он будет называться NumberUtilTest.java.
Напишите тест для функции вычисления факториала, по аналогии с тестированием калькулятора:
Основываясь на тесте, пропишите в папке main/java класс с названием NumberUtil. Класс пока что оставьте пустым:
Запускаем тест и дополняем код
Запустим наш тест:
Тест не пройден. Пока так и должно быть — наш класс ничего не содержит:
Теперь реализуйте функцию factorial в классе NumberUtil, чтобы тест прошёл успешно:
Запустите тест снова. Если вы всё сделали правильно, то он пройдёт успешно:
Поздравляем! Вы написали приложение, используя метод TDD для тестирования и разработки.
Что дальше?
Лучший источник информации о JUnit — официальная документация. Она постоянно обновляется и содержит практические примеры написания тестов, включая новые возможности пятой версии фреймворка.
Углубиться в тему можно с помощью книг:
- «Модульное тестирование программного обеспечения. Профессиональный базовый курс с практикой на JUnit» Евгения Пышкина;
- JUnit in Action Каталин Тьюдос;
- Java Unit Testing with JUnit 5: Test Driven Development with JUnit 5 Рахула Шармы.
Больше интересного про код — в нашем телеграм-канале. Подписывайтесь!