Основы JUnit

Когда делаешь первые попытки писать юнит-тесты обычно обычно сталкиваешься с проблемой начинания: вроде бы документация прочитана, цель ясна, а с чего начинать — не понятно.
Попробуем вместе написать простой юнит-тест, для более-менее настоящего класса, в котором испытаем почти весь базовый функционал JUnit.

Подготовка

Создадим пустой maven проект:

И добавим в него JUnit:

Зависимость добавлена с  <scope>test</scope> , что говорит maven, что она требуется только при сборке и исполнении тестов.

Класс для тестирования

Поскольку я обещал почти настоящий пример, придётся написать хоть сколько-то полезный класс. Пускай это будем набор утилит для работы со строками:

  • Разделение строки на подстроки
  • Слияние строк из массива
  • Проверка строк на пустоту
  • Преобразование в число и обратно
Не самый полезный набор утилит, но всё же 🙂

Тесты

Тесты в JUnit располагаются в отдельных классах, методы которых, имеющие аннотацию  @Test, и возвращающие  void, и есть сами тесты. Имя класса может быть в принципе любое, но рекомендуется придерживаться шаблона  ИмяТестируемогоКлассаTest , так как это упрощает чтение кода. К тому же обычно средства автоматического запуска тестов, такие как плагин maven  maven-surefire-plugin предполагают, что классы с юнит-тестами оканчиваются на *Test

Maven традиционно располагает тесты в каталоге src/test , в то время как основной исходный код располагается в src/main . Разумеется это всего лишь договорённость, используемая в maven по умолчанию, и тесты и код можно располагать любым удобным образом.

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

Первый юнит-тест

Все юнит-тесты пишутся по единому шаблону: создаются входные данные, создаются эталонные данные (expected), вызывается тестируемый код и
результат его работы(actual) сравнивается с эталонными данными. предоставляет несколько assert*  функций, выполняюших сравнение.

В первом юнит-тесте строка

и есть входные данные, которые мы отдаём в проверяемую функцию.

Эталонные данные определены в следующей строке:

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

Наконец самая главная часть теста, проверка:

assertEquals сравнивает эквивалентность объектов expected и actual и, в случае когда они не эквивалентны, проваливает тест и выводит сообщение "Unexpected string value". Функции assert* можно использовать и без сообщения:

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

Разработчики обычно пишут юнит-тесты только для предусмотренных разработчиком/архитектором/документацией/etc вариантов поведения функции. Для
StringUtils.fromDouble()  документация указывает что функция должна преобразовать цисло с плавающей запятой в строку.
Юнит-тест этой функции покрывает только описанный функционал. Цель юнит-тестирования — убедиться, что функция работает правильно, а не искать условия,
в которых она работает неправильно.

Более того, сам юнит-тест уже является краткой и понятной документацией к функции. В четырёх строках чётко и однозначно написано, как ведёт себя функция: возвращает новый строковый объект, значение которого является переданным ей числом с плавающей запятой, записанное в десятичной системе счисления.

А самый главный бонус юнит-тестирования, это фиксация поведения кода. Вы знаете, прямо сейчас, что функция ведёт себя определённым образом. И код, который её использует, полагается на это поведение. Если Когда вы захотите изменить эту функцию, юнит-тест будет вам гарантировать, что
поведение функции осталось таким же (либо тест провалится). Следовательно остальной код не заметит изменения реализации функции, а это значит, что с этого момента вы можете спокойной менять любую часть кода: юнит-тесты не позволят вам что-нибудь сломать.

Второй юнит-тест

Следующий юнит-тест напишем для обратной функции преобразования строки в число с плавающей запятой:

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

Однако у assertEquals в этом тесте появился дополнительный параметр! Дело в том, что сравнивать числа с плавающей запятой непосредственно друг с другом нельзя, так как они не имеют точного двоичного представления. Обычно числа сравниваются с некоторой погрешностью: можно сказать что 3.1415000000001 эквивалентно
3.1415000000002 с погрешностью до 0.000000000001. И именно эта погрешность передаётся в третий параметр assertEquals для числе с плавающей запятой. Вторая часть теста очевидна — проверяется что для переданного null возвращается NaN.

Третий юнит-тест

Теперь наоборот — у функций assertTrue и assertFalse не хватает одного аргумента. А всё потому, что эти функции проверяют только логические значения (первая ожидает true, вторая, соответственно, false ), которые и не с чем сравнивать.  В самом же тесте проверяется документированние поведение:  строка с значением очевидно не пуста, строка без каких-либо символов в ней — пуста и, наконец, строка с невидимыми символами тоже признаётся пустой.

Четвёртый и пятый юнит-тесты

В последних двух тестах нам придётся работать с массивами. Массивы нельзя проверить через assertEquals, так как для массивов assertEquals ведёт себя как assertSame:
Поэтому в JUnit предусмотрена специальная функция для сравнения массивов assertArrayEquals, которая сравнивает эквивалентность каждого элемента обоих массивов друг с другом. Разумеется сравниваются между собой элементы с одинаковой позицией в массиве и массивы разной длины сразу признаются не эквивалентными. Надо отметить что обратной функции для assertArrayEquals не предусмотрено.

Тест для последней оставшейся функции (joinArray) я предлагаю написать самостоятельно.

Пример теста, для сравнения:

[свернуть]

Исполнение тестов

Проще всего использовать для этого maven:

Но при необходимости можно и вручную:

Нужно всего лишь вручную указать правильный classpath, включающий в себя junit с зависимостиями и ваши классы 😛

Исходный код примера доступен на github