JavaRush /Java блог /Архив info.javarush /JUnit для JavaRush или немного о тестировании в домашних ...
Sdu
17 уровень

JUnit для JavaRush или немного о тестировании в домашних условиях.

Статья из группы Архив info.javarush
Надоело десятки раз вбивать в консоль тестовые данные чтобы проверить свою задачу? Добро пожаловать под кат, я расскажу что можно с этим сделать. Конечной целью данного материала будет автоматизация запуска решаемой задачи с различными параметрами и проверкой результатов без внесения изменений в ее исходный код. Как Вы уже наверное поняли из заголовка, главным нашим помощником в этом достаточно простом деле будет JUnit. Если Вы еще не слышали о модульном тестировании и юнит-тестах, предлагаю Вам немного отвлечься и самостоятельно познакомиться с этими понятиями, благо в интернете информации достаточно. Нет, не хотите? Ну и ладно, думаю большой проблемой для понимания происходящего это не станет. Ведь Вы же знаете что такое тест и тестирование вообще? Вы же занимаетесь этим каждый раз, когда запускаете свою задачу, вводите начальные данные и сравниваете получившийся результат с тем, что ожидали увидеть.
Hello, world JUnit!
Что же такое JUnit? На оф.сайте проекта мы можем прочитать вот такое описание:
JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks.
Для нас это означает возможность писать специальным образом оформленные классы, методы которых будут взаимодействовать с нашей программой, сверять получаемый результат с эталонным и информировать нас если они не совпали. Для понимания принципа рассмотрим простенький пример. Предположим у нас есть вспомогательный класс, один из методов которого принимает две переменные типа int и возвращает их сумму: JUnit для JavaRush или немного о тестировании в домашних условиях. - 1 Вот этот функционал мы и попробуем протестировать. К счастью, наша любимая IDEA уже имеет все необходимое для быстрого создания тестов, все что нам нужно, это установить курсор в строке объявления класса, нажать "Alt + Enter" и в контекстном меню выбрать "Create Test": JUnit для JavaRush или немного о тестировании в домашних условиях. - 2 После того, как Вы уточните где стоит создать тест, IDEA предложить выбрать библиотеку тестирования (в данном материале я использую JUnit4, для того, чтобы классы библиотеки были подключены к проекту необходимо нажать кнопку "Fix"), тестируемые методы и дополнительные опции. JUnit для JavaRush или немного о тестировании в домашних условиях. - 3 IDE создаст шаблон тестового класса: ИмяКласса = имяТестируемогоКласса + "Test" имяМетода = "test" + ИмяТестируемогоМетода JUnit для JavaRush или немного о тестировании в домашних условиях. - 4 Нам остается только наполнить тело метода. В этом на помогут так называемые "Assertions \ Утверждения", методы предоставляемые JUnit. Упрощенно их работа выглядит следующим образом: в метод .assert* передается ожидаемый результат и результат вызова тестируемого метода, для удобства первым параметром можно добавить поясняющее сообщение. Если во время выполнения теста параметры не совпадут, Вы будете проинформированы об этом. Запускать тестовый класс на выполнение можно как и обычный класс, я предпочитаю использовать комбинацию клавиш Ctrl+Shift+F10 JUnit для JavaRush или немного о тестировании в домашних условиях. - 5
Конкретизируем задачу
В теории все просто и красиво, но в разрезе предложенного примера не особо то и нужно, сложить два числа мы можем доверить компьютеру. Нас больше интересует как будут обстоять дела с реальными задачами, решаемыми студентами JavaRush, для примера я предлагаю взять горячо любимую level05.lesson12.bonus03.
/* Задача по алгоритмам Написать программу, которая: 1. вводит с консоли число N > 0 2. потом вводит N чисел с консоли 3. выводит на экран максимальное из введенных N чисел. */
Нам необходимо написать три теста, для положительных, отрицательных чисел и смешанного набора.
Чем дальше в лес...
Вот тут нас и ожидают некоторые сюрпризы: public class UtilApp { public static void main(String[] args) throws Exception { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); //напишите здесь ваш код int n; int maximum; /* Конечно же я не буду размещать решение задачи ;) Код приведенный тут переработан для наглядности, и не в коем случае не означает что он должен присутствовать в "правильном решении" */ System.out.println(maximum); } }
  • Логика программы размещается в метода main()
  • Исходные данные не передаются в метод, а вводятся с клавиатуры.
  • Метод main() не возвращает результат, а выводит его в консоль.
Если первый пункт не особенно и проблематичен (мы можем вызывать метод main() как обычно), то два последующих заставляют углубляться в тему и напрягать извилины. Я нашел несколько вариантов решения возникшей проблемы:
  1. Вынесение логики нахождения максимума в отдельный метод.
    • Плюсы: Правильный подход с точки зрения рефакторинга
    • Минусы: Программа обрастает кодом, лишними структурами, как минимум добавляется массив или ArrayList(на вкус и цвет...). Тестируется только механизм нахождения максимума, ввод данных, как и вывод, не проверяются.
  2. Написание оберток-wrapper'ов для System.in/System.out.
    • Плюсы: Не используем сторонние библиотеки.
    • Минусы: Путь не для новичков. Относительная сложность реализации теста, объем кода в тесте может быть больше чем в тестируемой задаче.
  3. Использование для тестов дополнительных библиотек.
    • Плюсы: Чистота кода в тестах, относительная простота написания теста. Не изменяется исходный код тестируемого класса.
    • Минусы: Необходимость подключить к проекту сторонние библиотеки.
Признаться честно, третий вариант мне понравился больше всего, вот его и попробуем реализовать.
System Rules
Непродолжительные поиски привели меня на страницу http://stefanbirkner.github.io/system-rules/, и сразу стало понятно, что это, что мне нужно.
A collection of JUnit rules for testing code that uses java.lang.System.
Итак, скачиваем библиотеку. Скачиваем необходимую для работы system rules библиотеку Commons IO. Подключаем обе библиотеки к нашему проекту (File -> Project Structure -> Libraries -> + -> Java) и начинаем ваять: После запуска наша задача просит ввести с консоли N+1 чисел, где первое число сообщает о том, сколько чисел последует за ним. В System Rules для этих целей служит класс TextFromStandardInputStream, первоначально нам необходимо добавить в наш тестовый класс поле этого типа и пометить его аннотацией @Rule: @Rule public final TextFromStandardInputStream systemInMock = emptyStandardInputStream(); Затем, непосредственно в тестовом методе указываем необходимые данные: systemInMock.provideText("4\n2\n6\n1\n3\n"); Как видите, числа передаются в текстовом виде и разделяются знаком переноса строки "\n". Исходя из этого, получается что N у нас будет равно 4, а искать максимум мы будем из чисел {2, 6, 1, 3}. Далее нам необходимо создать экземпляр тестируемого класса и вызвать метод main(). Наша программа считает данные из systemInMock, обработает их и распечатает результат, а нам остается только считать его и сравнить с эталоном. Для этого, system rules предоставляет нам класс StandardOutputStreamLog. Добавляем поле указанного типа: @Rule public final StandardOutputStreamLog log = new StandardOutputStreamLog(); Считать распечатанные данные можно с помощью метода .getLog(), при этом нужно учитывать наличие символов перевода строки, окончательные варианты могут быть такие: assertEquals("{2, 6, 1, 3}, max = 6", "6", log.getLog().trim()); // или assertEquals("{2, 6, 1, 3}, max = 6", "6\r\n", log.getLog()); Между тестами, во избежание наслоения данных необходимо очищать log log.clear(); Полный текст моего тестового класса: import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.StandardOutputStreamLog; import org.junit.contrib.java.lang.system.TextFromStandardInputStream; import static org.junit.Assert.*; import static org.junit.contrib.java.lang.system.TextFromStandardInputStream.emptyStandardInputStream; public class UtilAppTest { @Rule public final TextFromStandardInputStream systemInMock = emptyStandardInputStream(); @Rule public final StandardOutputStreamLog log = new StandardOutputStreamLog(); @Test public void testAddition() throws Exception { systemInMock.provideText("4\n2\n6\n1\n3\n"); UtilApp utilApp = new UtilApp(); utilApp.main(new String[]{}); assertEquals("{2, 6, 1, 3}, max = 6", "6", log.getLog().trim()); systemInMock.provideText("5\n-100\n-6\n-15\n-183\n-1\n"); log.clear(); utilApp.main(new String[]{}); assertEquals("{-100, -6, -15, -183, -1}, max = -1", "-1", log.getLog().trim()); systemInMock.provideText("3\n2\n0\n-1\n"); log.clear(); utilApp.main(new String[]{}); assertEquals("{2, 0, -1}, max = 2", "2", log.getLog().trim()); } } Запускаем и наслаждаемся. -=!!! ВАЖНО !!!=- Данный материал предоставлен ТОЛЬКО в ознакомительных целях, я не гарантирую успешного прохождения тестирования задачи на сервере в случае наличия постороннего класса в пакете с задачей. Прежде чем отправлять задачу на проверку на сервер удаляйте все постороннее: лишние файлы, лишние классы, закоментированый код. Удачное прохождение придуманных Вами тестов не гарантирует успешного прохождения тестов на сервере. Я умышленно не разжевывал теоретический материал: теорию модульного тестирования, аннотации JUnit, assert и прочее, весь материал есть в предложенных в тексте ссылках. Возможно у Вас есть свои способы тестирования задач, я с радостью обсужу их с Вами в комментариях.
Комментарии (5)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Kosolap Уровень 27
5 марта 2015
Надо будет почитать, но из описанного как-то не очевидно, чем это удобней «оперативного» тестирования.
Olegator3 Уровень 37
4 марта 2015
Спасибо, очень информативно, пошел составлять тесты к существующим задачам :)