“Код без тестів — це код, зламаний за дизайном.” — Розробка через тестування (Test-Driven Development, TDD) — це підхід до розробки програмного забезпечення, де тести пишуться до того, як писатиметься сам код. Ця методологія допомагає розробникам створювати чистий, підтримуваний і надійний код, який відповідає вимогам і в той же час дозволяє уникнути надмірної складності. У екосистемі Java TDD став основною практикою для створення надійних додатків, що підтримується потужними фреймворками для тестування, такими як JUnit та Mockito.
У цій статті ми розглянемо розробку через тестування (TDD) в Java, покриваючи основні принципи, покрокові приклади, кращі практики та те, як ви можете застосувати цей підхід у своїх проектах.
Що таке розробка через тестування (TDD)?
Розробка через тестування (TDD) — це процес розробки програмного забезпечення, який включає такі етапи:
- Напишіть тест: Спочатку пишете тест для конкретної частини функціональності.
- Напишіть мінімальний код: Пишете лише стільки коду, щоб тест компілювався та не проходив.
- Зробіть тест пройденим: Реалізуєте функціональність, щоб тест пройшов.
- Рефакторинг: Очищаєте код, не змінюючи його функціональність.
- Повторюйте: Повторюєте процес для наступної частини функціональності.
Цей цикл зазвичай називають Red-Green-Refactor:
- Red (Червоний): Пишете тест, який не проходить.
- Green (Зелений): Пишете найпростішу реалізацію, яка змушує тест пройти.
- Refactor (Рефакторинг): Покращуєте код, зберігаючи всі тести зеленими.
Чому варто використовувати TDD?
- Покращена якість коду: Написання тестів на початку змушує вас задуматися над дизайном і крайніми випадками.
- Швидкий зворотний зв'язок: Тести виконуються одразу після змін, що скорочує цикл відлагодження.
- Менше часу на відлагодження: Оскільки тести виявляють помилки на ранніх етапах, ви витрачаєте менше часу на відлагодження.
- Сприяє рефакторингу: Оскільки у вас уже є тести, ви можете сміливо проводити рефакторинг коду.
- Забезпечує виконання вимог: Тести пишуться на основі вимог, що гарантує покриття всіх функціональностей.
Налаштування для TDD у Java
Щоб реалізувати TDD у Java, потрібно налаштувати проект з певними інструментами для тестування:
- JUnit 5: Найпопулярніший фреймворк для тестування в Java.
- Mockito: Бібліотека для мокування залежностей.
3.
Maven/Gradle: Інструменти для збірки, які керують залежностями.
Залежність Maven для JUnit та Mockito:
org.junit.jupiter
junit-jupiter-api
5.9.2
test
org.mockito
mockito-core
5.5.0
test
Покроковий приклад TDD в Java: створення калькулятора
Давайте розглянемо приклад реалізації Калькулятора за допомогою TDD.
Крок 1: Напишіть невдалий тест (Червона фаза)
Перший крок у TDD — це написання тесту, який не проходить, оскільки функціональність ще не реалізована.
Тестовий сценарій: наш клас Calculator має виконувати операцію add(int a, int b).
Тестовий код:
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
public class CalculatorTest {
@Test
public void testAddition() {
Calculator calculator = new Calculator(); // Клас ще не існує
int result = calculator.add(5, 3); // Метод ще не існує
assertEquals(8, result, "5 + 3 має дорівнювати 8");
}
}
Виведення:
Помилка компіляції: Клас Calculator не знайдено.
На цьому етапі наш тест не проходить, оскільки клас Calculator і метод add не існують.
Крок 2: Напишіть мінімальний код для проходження тесту (Зелена фаза)
Далі ми пишемо мінімум коду, необхідного для того, щоб тест пройшов.
Реалізація коду:
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
Запускаємо тест знову:
Тест пройшов.
Тепер тест пройшов, оскільки метод add повертає правильний результат.
Крок 3: Рефакторинг (Фаза рефакторингу)
Якщо тест пройшов, можна виконати рефакторинг коду для покращення читабельності або продуктивності, за умови, що тести все ще проходять.
У цьому простому прикладі, можливо, не буде багато що рефакторити, але в більш складних системах ви можете виконати рефакторинг для:
- Читабельності коду: Додавання значущих назв та коментарів.
- Видалення дублювання: Винесення спільної логіки.
- Оптимізації продуктивності: Спрощення циклів або логіки.
Додавання нових тестів
Ми повторюємо цикл TDD для інших операцій (віднімання, множення, ділення):
@Test
public void testSubtraction() {
Calculator calculator = new Calculator();
int result = calculator.subtract(10, 5);
assertEquals(5, result, "10 - 5 має дорівнювати 5");
}
@Test
public void testMultiplication() {
Calculator calculator = new Calculator();
int result = calculator.multiply(4, 2);
assertEquals(8, result, "4 * 2 має дорівнювати 8");
}
@Test
public void testDivision() {
Calculator calculator = new Calculator();
int result = calculator.divide(10, 2);
assertEquals(5, result, "10 / 2 має дорівнювати 5");
}
@Test
public void testDivisionByZero() {
Calculator calculator = new Calculator();
assertThrows(ArithmeticException.class, () -> calculator.divide(10, 0), "Ділення на нуль має викидати виключення");
}
Реалізація коду:
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
public int multiply(int a, int b) {
return a * b;
}
public int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("Неможливо поділити на нуль");
}
return a / b;
}
}
TDD для складних систем: мокування з Mockito
У реальних додатках класи часто залежать від інших класів або сервісів. Наприклад, клас сервісу може залежати від бази даних або API.
У таких випадках можна використовувати Mockito для створення mock об'єктів, які імітують поведінку реальних залежностей.
Приклад: Сервіс користувача з репозиторієм користувачів
Код сервісу користувача:
public class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(int userId) {
return userRepository.findById(userId).orElseThrow(() -> new RuntimeException("Користувача не знайдено"));
}
}
Крок 1: Напишіть тест за допомогою Mockito
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
public class UserServiceTest {
@Test
public void testGetUserById() {
UserRepository mockRepo = mock(UserRepository.class);
User mockUser = new User(1, "Джон Доу");
when(mockRepo.findById(1)).thenReturn(java.util.Optional.of(mockUser));
UserService userService = new UserService(mockRepo);
User user = userService.getUserById(1);
assertEquals("Джон Доу", user.getName());
verify(mockRepo, times(1)).findById(1); // Перевірка, що репозиторій був викликаний один раз
}
}
Крок 2: Запустіть тест
Тест пройшов.
У цьому прикладі Mockito створює mock репозиторій користувачів, що дозволяє тесту фокусуватись лише на UserService без виконання реальних запитів до бази даних.
Кращі практики для TDD в Java
- Зберігайте тести простими: Кожен тест повинен фокусуватися на одній конкретній функціональності.
- Уникайте хардкодингу значень: Використовуйте значущі змінні та уникайте надмірного дублювання.
- Чітко називайте тести: Використовуйте описові назви для тестових методів (наприклад, testAddition, а не test1).
- Тестуйте крайні випадки: Переконайтесь, що ви тестуєте крайні випадки, такі як null значення або великі числа.
- Розумно використовуйте мокування: Уникайте мокування всього — мокувати лише зовнішні залежності.
- Рефакторте постійно: Очищайте як ваші тести, так і код під час фази рефакторингу.
Виклики TDD
- Часові витрати на початку: Написання тестів спочатку може здаватися повільним, але в довгостроковій перспективі це економить час, знижуючи кількість багів.
- Складнощі з тестуванням старого коду: Застосування TDD до старих проектів з тісно зв’язаним кодом і відсутністю тестів може вимагати значного рефакторингу.
- Потрібна дисципліна: TDD вимагає від розробників дотримання циклу Red-Green-Refactor і уникнення пропусків тестів.
Висновок
Test-Driven Development (TDD) в Java сприяє кращому дизайну, чистому коду та надійним додаткам. Написання тестів спочатку дозволяє гарантувати, що ваш додаток відповідає вимогам, обробляє крайні випадки та залишається легким для підтримки. Завдяки таким фреймворкам, як JUnit та Mockito, ви можете безперешкодно застосовувати TDD до маленьких класів, сервісів і навіть складних систем, що залежать від зовнішніх компонентів.
Якщо ви новачок у TDD, почніть з малих проєктів, дотримуйтесь циклу Red-Green-Refactor і спостерігайте, як це змінює ваш підхід до кодування. Пам'ятайте, що в TDD тести — це не лише страховка, вони направляють ваш процес розробки до створення високоякісних, підтримуваних Java-додатків.
Перекладено з: Mastering Test-Driven Development (TDD) in Java: A Comprehensive Guide with Examples