Розробка через тестування (TDD) — це підхід до розробки програмного забезпечення, де тести пишуться до написання самого коду. Це перевертає традиційний процес розробки, починаючи з тестів, а не з написання коду і тестування його пізніше. Такий підхід забезпечує правильну структуру коду для проходження тестів з самого початку, що призводить до кращої якості коду, меншої кількості помилок та надійнішої і зручнішої для підтримки кодової бази.
Основні принципи TDD
Пишіть тести першими: Починайте з написання тестів для функціональності, яку ви збираєтеся реалізувати. Це змушує вас детально продумати вимоги та крайні випадки до того, як ви почнете писати код.
Зосередьтеся на малих одиницях: TDD акцентує увагу на тестуванні та розробці малих одиниць коду (зазвичай функцій чи методів). Це дозволяє зберігати фокус і забезпечує правильну поведінку кожної одиниці.
Частий зворотний зв'язок: Оскільки ви часто запускаєте тести, ви отримуєте постійний зворотний зв'язок щодо коректності вашого коду. Якщо щось ламається, вам легше виявити проблему та виправити її.
Рефакторинг без зупинок: TDD заохочує рефакторинг на ранніх етапах і часто. Маючи тести, ви можете впевнено покращувати структуру коду, знаючи, що функціональність перевірена.
Цикл TDD: Червоний-Зелений-Рефактор
Цикл TDD
Процес TDD слідує простому, повторюваному циклу, відомому як Червоний-Зелений-Рефактор:
- Червоний: Напишіть тест для маленької одиниці функціональності. Оскільки функціональність ще не реалізована, тест не пройде.
- Зелений: Напишіть мінімальний код, необхідний для того, щоб тест пройшов. Важливо не зосереджуватися на оптимізації чи дизайні, а просто на проходженні тесту.
- Рефакторинг: Коли тест проходить, покращіть дизайн та структуру коду без зміни його функціональності. Переконайтеся, що тести все ще проходять після рефакторингу.
Цей цикл повторюється для кожної маленької одиниці функціональності, поки не буде розроблена вся функція.
Приклад TDD на Java
Ми будуємо сервіс кешування на Java, який зберігає пари "ключ-значення" і взаємодіє з Redis для зберігання часто використовуваних даних. Сервіс має надавати методи для додавання, отримання та видалення кешованих записів.
Крок 1: Напишіть тест (Червоний)
Спочатку пишемо тест для визначення поведінки нашого кеш-сервісу, хоча сам сервіс ще не існує.
Ось початковий код тесту:
# CacheServiceTest.java
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class CacheServiceTest {
private CacheService cacheService;
@BeforeEach
void setUp() {
cacheService = new CacheService(); // Цей клас ще не існує
}
@Test
void shouldStoreAndRetrieveValue() {
cacheService.add("key1", "value1");
String result = cacheService.get("key1");
assertEquals("value1", result);
}
@Test
void shouldDeleteValue() {
cacheService.add("key1", "value1");
cacheService.delete("key1");
assertNull(cacheService.get("key1"));
}
}
Крок 2: Напишіть мінімальний код для проходження тесту (Зелений)
Тепер реалізуємо мінімальний код, необхідний для проходження тестів.
# CacheService.java
import java.util.HashMap;
import java.util.Map;
public class CacheService {
private Map cache = new HashMap<>();
public void add(String key, String value) {
cache.put(key, value);
}
public String get(String key) {
return cache.get(key);
}
public void delete(String key) {
cache.remove(key);
}
}
Коли ми запустимо тести, вони пройдуть успішно. Це означає, що ми успішно завершили фазу "Зелений", де тести проходять за допомогою найпростішого коду.
Крок 3: Рефакторинг
Тепер, коли тести проходять, ми рефакторимо код для покращення підтримуваності, продуктивності та готовності до майбутніх змін.
Давайте додамо інтеграцію з Redis.
Додайте клієнт Redis (наприклад, використовуючи Jedis):
redis.clients
jedis
4.0.1
Оновіть CacheService
, щоб він взаємодіяв з Redis:
// CacheService.java
import redis.clients.jedis.Jedis;
public class CacheService {
private Jedis jedis;
public CacheService() {
this.jedis = new Jedis("localhost");
}
public void add(String key, String value) {
jedis.set(key, value);
}
public String get(String key) {
return jedis.get(key);
}
public void delete(String key) {
jedis.del(key);
}
}
Після рефакторингу ми знову запускаємо тести, щоб переконатися, що все працює. Якщо тести проходять, це означає, що ми успішно рефакторили код, зберігаючи функціональність.
Крок 4: Додайте більше тестів
Ми можемо додати тести для крайніх випадків, наприклад, для обробки null значень або перевірки збоїв у підключенні до Redis.
@Test
void shouldReturnNullForMissingKey() {
assertNull(cacheService.get("nonExistentKey"));
}
@Test
void shouldHandleNullValues() {
cacheService.add("keyWithNull", null);
assertNull(cacheService.get("keyWithNull"));
}
Результат
У цьому проекті ми використовували Redis як кеш для покращення продуктивності системи. Однак тестування наших стратегій кешування — переконання, що кеш очищається в правильний час — стало великою проблемою.
Саме тут TDD справді допомогло мені. Спочатку я написав простий тест, який записував дані в кеш Redis і потім читав їх назад. Коли я запустив тест, я зрозумів, що мій код поводиться не так, як очікувалося. Кеш не очищався належним чином, і в деяких випадках поверталися застарілі дані. Цей тест допоміг мені швидше знайти проблему і відповідно рефакторити код.
Хоча це може здатися простим прикладом, навіть незначні проблеми з кешуванням у системах, що використовують Redis, можуть значно вплинути на продуктивність. TDD надав мені безпечний спосіб виявити ці помилки на ранніх етапах.
Висновок
Коли я вперше почав працювати з TDD, однією з найбільших проблем було відчуття, що написання тестів — це витрачання часу. Спочатку я надавав перевагу швидкому написанню коду та тестуванню його пізніше. Однак такий підхід затримував виявлення неочікуваних помилок. Хоча на перший погляд цей підхід здавався повільнішим, в довгостроковій перспективі я зрозумів, що розробляю швидше і з меншою кількістю помилок.
TDD принесло мені кілька особистих переваг. По-перше, воно навчило мене писати більш модульний код. Написання тестованого коду означало також написання менших, добре визначених частин функціональності. Це покращило читабельність і підтримуваність коду протягом усього проекту.
По-друге, особливо під час великих рефакторингів, TDD дозволяло мені змінювати код, будучи впевненим, що не виникне проблем з зворотною сумісністю. Запуск тестів після кожної зміни та бачення, що попередня функціональність все ще працює, давав мені велике спокійне відчуття.
TDD стало незамінним інструментом. Кожного разу, коли я додаю нову функціональність чи можливість, моїм першим кроком завжди є написання тесту для неї. Цей процес допомагає мені писати більш зосереджений код, оскільки тести чітко визначають, чого я хочу досягти на кожному кроці.
Іншим прикладом був процес оптимізації продуктивності. Під час покращення продуктивності, особливо в таких процесах, як кешування Redis, я повинен був переконатися, що система продовжує працювати правильно. Завдяки тестам, які я написав, я міг виявити потенційні проблеми на ранніх етапах, вносячи коригування продуктивності.
Для новачків я б сказав, що TDD вимагає терпіння та дисципліни. Спочатку це може зайняти більше часу, але з часом це скоротить цикли розробки і підвищить якість вашого коду. Я можу з упевненістю сказати, що TDD зробить вас кращим розробником.
Розробка через тестування (TDD) заохочує вас брати на себе відповідальність за свій код і більш глибоко обмірковувати те, що ви пишете.
Якщо вам сподобалася ця стаття, не соромтесь ознайомитись зі сторінкою Insider Engineering Medium, щоб дізнатися про найновіші технології розробки програмного забезпечення. Ось деякі з блогів, які вам також можуть сподобатися.
Перекладено з: First Step to TDD