Кожна виконана задача e програмуванні вимагає тестування, тому що від помилок, як відомо, ніхто не застрахований. Найчастіше на цю процедуру йде чимало часу, навіть у простих завданнях у новачків. Ви запускаєте додаток, вводите дані для перевірки і розумієте, що результат не відповідає очікуванням. Потім ви починаєте з'ясовувати, на якому ж етапі сталася помилка, все це у вас забирає дорогоцінні хвилини, які ви могли б витратити на розробку нового функціоналу. А що, якщо ваш додаток великий і в ньому багато що залежать один від одного модулів, класів і компонентів і ваше завдання — змінити поведінку існуючого коду, при цьому не пошкодивши старе.
У чому сенс Unit-тестів?
Саме для цього придумали unit-тести, які дають можливість автоматизувати перевірку додатку. Але ж на написання тестів теж йде час — заперечите ви і матимете рацію. Справа в тому, що з ростом вашого застосування цей час буде скорочуватися, а без тестів ситуація зворотна: чим складніше стає додаток, тим більш трудомістким буде його зміна і процес стеження за вадами. До того ж, не будемо забувати, що інтеграційне тестування, яким користується більшість «смертних» програмістів, вимагає запуску програми, а, як відомо, у Java розгортання та збирання — процес не найшвидший. Пам'ятається мені один банківський проект, в якому я витрачав на це 15 хвилин, але не будемо про сумне.
Для того, щоб перейнятися цією концепцією, пропоную почитати про екстремальне програмування. А поки, давайте розглянемо, які інструменти нам пропонує Java для вирішення цієї проблеми. Найбільш популярні — JUnit і TestNg, і мова сьогодні піде про перший. Він є простим і гнучким фреймворком для тестування.
Рекомендуємо курс по темі
JUnit анотації та їх опис
Для того, щоб підключити JUnit, нам необхідно додати dependency у pom.xml, на момент написання статті найсвіжіша версія 4.12:
Потім представимо, для простоти розуміння, що у нас є клас калькулятор, який може додавати і віднімати:
Unit тест для такого класу буде виглядати так:
Розміщуватися даний клас повинен у папці test, яка спеціально створена для зберігання тестових класів. Назва класу має відповідати одній з масок: * Test, Test *, * TestCase для того, щоб maven surefire plugin зміг знайти ваш тестовий клас. Методи повинні мати повертаємий тип void в сигнатурі і анотацію @Test, яка визначає, що метод є тестовим. Анотація @Before вказує на те, що метод буде виконуватися перед кожним тестованим методом. Також для перевірки результату використовується спеціальний перевіряємий метод assertEquals, який в нашому випадку може приймати повідомлення, яке буде показуватися при невідповідності фактичного і очікуваного результату, потім другий параметр — фактичний результат і третій очікуваний результат.
Далі відкриваємо термінал, перейдемо у папку з нашим проектом і виконаємо mvn test:
За висновком лога консолі видно, що тести пройшли успішно. Якщо ж ми змінимо знак у методі add c + на *, то тест буде failed і лог буде виглядати так:
Таким чином, ми відразу бачимо, який тест у нас не пройшов перевірку, і можемо почати налагодження з потрібної точки.
Рекомендуємо курс по темі
У наступній таблиці наведено огляд наявних у анотації JUnit 4.x.
Анотація | Опис |
@Test public void method() public void method() public void method() public static void method() public static void method() | Анотація @Test визначає що метод method () є тестовим. |
@Before | Анотація @Before вказує на те, що метод буде виконуватися перед кожним тестованим методом @Test. |
@After | Анотація @After вказує на те що метод буде виконуватися після кожного тестованого методу @Test |
@BeforeClass | Анотація @BeforeClass вказує на те, що метод буде виконуватися на початку всіх тестів, а точніше у момент запуску тестів (перед усіма тестами @Test). |
@AfterClass | Анотація @AfterClass вказує на те, що метод буде виконуватися після всіх тестів. |
@Ignore | Анотація @Ignore каже, що метод буде проігнорований у момент проведення тестування. |
@Test (expected = Exception.class) | (expected = Exception.class) — вказує на те, що у даному тестовому методі ви навмисно очікуєте Exception. |
@Test (timeout=1000) | (timeout=1000) — вказує, що тестований метод не повинен займати більше ніж 1 секунду. |
Методи (основні), що перевіряються
Метод | Опис |
fail(String) | Вказує на те, щоб тестовий метод завалився, при цьому виводячи текстове повідомлення. |
assertTrue([message], boolean condition) | Перевіряє, що логічна умова істинна. |
assertsEquals([String message], expected, actual) | Перевіряє, що два значення збігаються. Для масивів перевіряються посилання, а не зміст масивів. |
assertNull([message], object) | Перевіряє, що об'єкт є порожнім null. |
assertNotNull([message], object) | Перевіряє, що об'єкт не є порожнім null. |
assertSame([String], expected, actual) | Перевіряє, що обидві змінні відносяться до одного об'єкту. |
assertNotSame([String], expected, actual) | Перевіряє, що обидві змінні відносяться до різних об'єктів. |
Також я прикріпив приклад з ООП моделлю компанії, у якій підраховуються витрати на зарплату для співробітників по компанії і департаменту. Основні методи покриті unit тестами, код розміщений тут: