JavaRush /Java блог /Random UA /JUnit для JavaRush або трохи про тестування в домашніх ум...
Sdu
17 рівень

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

Стаття з групи Random UA
Набридло десятки разів вбивати в консоль тестові дані, щоб перевірити своє завдання? Ласкаво просимо під кат, я розповім, що можна з цим зробити. Кінцевою метою даного матеріалу буде автоматизація запуску розв'язуваної задачі з різними параметрами та перевіркою результатів без внесення змін до її вихідного коду. Як Ви вже напевно зрозуміли із заголовка, головним нашим помічником у цій досить простій справі буде JUnit . Якщо Ви ще не чули про модульне тестування та юніт-тести, пропоную Вам трохи відволіктися і самостійно познайомитися з цими поняттями, благо в інтернеті достатньо інформації. Ні, не бажаєте? Ну і гаразд, думаю великою проблемою для розуміння того, що відбувається, це не стане. Адже Ви знаєте що таке тест і тестування взагалі? Ви ж займаєтеся цим щоразу, коли запускаєте своє завдання, вводите початкові дані і порівнюєте результат, що вийшов, з тим, що очікували побачити.
Hello, world JUnit!
Що таке JUnit? На оф.сайті проекту ми можемо прочитати такий опис:
JUnit is a simple framework to write repeatable tests. Це is instance of 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/ , і відразу стало зрозуміло, що це, що мені потрібно.
Збірка JUnit правил для тестування коду, що використовує 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, обробить їх і роздрукує результат, а нам залишається тільки рахувати його і порівняти з еталоном. Для цього система правил надає нам клас 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 та інше, весь матеріал є у запропонованих у тексті посиланнях. Можливо, у Вас є свої способи тестування завдань, я з радістю обговорю їх з Вами в коментарях.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ