JavaRush /Курси /Модуль 5. Spring /Лекція 262: Unit-тестування для мікросервісів: інструмент...

Лекція 262: Unit-тестування для мікросервісів: інструменти та підходи

Модуль 5. Spring
Рівень 21 , Лекція 1
Відкрита

Якщо порівняти розробку мікросервісів із зведенням хмарочоса, то можна сказати, що unit-тести — це перевірка надійності кожної цегляної кладки. Уяви, що одна цеглина виявилася бракованою: хмарочос може впасти. З мікросервісами та сама історія — навіть невелика помилка в сервісі може призвести до великих проблем на етапі інтеграції або експлуатації.

Unit-тести допомагають:

  • Переконатися, що базова бізнес-логіка працює коректно. Наприклад, банківський сервіс не має просто так подарувати клієнту пару мільйонів.
  • Ізолювати помилки. Якщо тест впав, ти одразу бачиш, який метод або модуль винен.
  • Полегшити рефакторинг коду. Змінюєш реалізацію? Переконайся, що старі тести все ще проходять.
  • Зберегти розробника в здравії. Нічого гіршого, ніж відлавлювати баг, який виліз після деплоя.

Основні інструменти для unit-тестування

Щоб писати unit-тести в екосистемі Java, не треба винаходити велосипед. Все вже давно придумано:

  1. JUnit 5 — це де-факто стандарт для тестування в Java. Потужний, гнучкий і доволі простий у вивченні.
  2. Mockito — бібліотека для створення "моків" (фіктивних об'єктів), які дозволяють тестувати модулі в ізоляції.
  3. AssertJ — для більш читабельних і розширених assertions в тестах (необов'язково, але корисно).

JUnit 5: основи тестування

JUnit дозволяє писати одиничні тести (unit-тести), які перевіряють конкретні методи або класи. Найкорисніші анотації JUnit:

  • @Test — позначає метод як тестовий.
  • @BeforeEach — виконується перед кожним тестом.
  • @AfterEach — виконується після кожного тесту.
  • @DisplayName — задає читабельну назву для тесту.

Приклад простого тесту з JUnit:


import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    @Test
    void testAddition() {
        Calculator calculator = new Calculator();
        int result = calculator.add(1, 2);
        assertEquals(3, result, "1 + 2 має дорівнювати 3");
    }
}

Mockito: створення моків

Mockito — це твій найкращий друг для тестування залежностей. Моки дозволяють ізолювати тестований код, замінюючи справжні залежності на підробні. Вони потрібні, щоб тестувати тільки твій код, без виклику сторонніх сервісів, баз даних і т.д.

Приклад використання Mockito:


import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;

class OrderServiceTest {

    @Test
    void testCreateOrder() {
        // Створюємо мок для репозиторію
        OrderRepository repository = mock(OrderRepository.class);

        // Створюємо OrderService з підробним репозиторієм
        OrderService service = new OrderService(repository);

        // Визначаємо поведінку мока: повертаємо фіктивне значення
        when(repository.save(any(Order.class))).thenReturn(new Order(1L, "Test Order"));

        // Викликаємо метод і перевіряємо результат
        Order order = service.createOrder("Test Order");
        assertEquals(1L, order.getId(), "ID замовлення має бути рівним 1");
        assertEquals("Test Order", order.getName(), "Ім'я замовлення має бути 'Test Order'");

        // Перевіряємо, що метод save дійсно був викликаний
        verify(repository).save(any(Order.class));
    }
}

Підходи до написання unit-тестів

Під час тестування сервісів ви, скоріше за все, зіткнетесь з різними залежностями: репозиторії, інші сервіси, зовнішні API. Щоб тестувати логіку ізольовано, нам потрібно:

  1. Створити моки для залежностей.
  2. Задати їхню поведінку за допомогою методів when (наприклад, when(repository.findById(1L)).thenReturn(mockEntity)).
  3. Перевірити, чи викликалися потрібні методи з правильними параметрами (використовуючи verify).

Приклад з кількома залежностями:


import static org.mockito.Mockito.*;

class PaymentServiceTest {
    @Test
    void testProcessPayment() {
        PaymentGateway gateway = mock(PaymentGateway.class);
        NotificationService notification = mock(NotificationService.class);

        PaymentService service = new PaymentService(gateway, notification);

        when(gateway.process(any(PaymentRequest.class))).thenReturn(true);

        service.processPayment(new PaymentRequest(100));

        verify(gateway).process(any(PaymentRequest.class));
        verify(notification).sendNotification(any());
    }
}

Тестування бізнес-логіки

Unit-тести повинні перевіряти не лише позитивні сценарії, а й граничні випадки. Наприклад:

  • Перевірка вводу некоректних даних. Сервіс має кинути виняток або повернути помилку.
  • Перевірка "порожніх" даних: порожній список, нульові значення тощо.
  • Перевірка максимальних і мінімальних значень вхідних параметрів.

Приклад:


@Test
void testValidateInput_NullValue() {
    IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
        someService.validateInput(null);
    });
    assertEquals("Input cannot be null", exception.getMessage());
}

Що тестувати, а що — ні?

Важливо пам'ятати, що unit-тести не повинні:

  1. Тестувати сторонні бібліотеки (ти впевнений, що твій StringUtils.capitalize() працює? Сподіваюся, і так зрозуміло).
  2. Тестувати взаємодію з базою даних або мережею (для цього є інтеграційні тести і Testcontainers).

Unit-тести повинні:

  1. Тестувати логіку твого застосунку: методи, функції, класи.
  2. Перевіряти граничні випадки: некоректні дані, крайні значення.
  3. Перевіряти, що виклики залежностей відбуваються саме так, як потрібно (за допомогою Mockito).

Типові помилки при написанні unit-тестів

  1. Тести залежать від зовнішнього оточення. Наприклад, якщо тест звертається до реальної бази даних.
  2. Відсутність перевірок. Якщо тест просто виконується, але нічого не перевіряє, це не тест.
  3. Плутанина з моками. Забагато складної налаштування моків може заплутати тебе і твоїх колег.
  4. Тести стали складніші, ніж сам код. Пам'ятай, що тести мають бути простими й зрозумілими.

Практичне застосування

Unit-тести не лише допоможуть тобі переконатися в працездатності коду, але й сподобаються твоєму майбутньому роботодавцю. Під час співбесіди можуть спитати: "Як ти пишеш тести?" І тут ти зможеш вразити знаннями JUnit, Mockito і стратегій тестування.

Крім того, якісні unit-тести роблять зміни в коді значно безпечнішими. Якщо хтось з твоєї команди випадково "зламає" бізнес-логіку, тести одразу про це повідомлять.

Посилання на документацію:

Отже, беремо JUnit, Mockito і починаємо писати тести для нашого мікросервісного застосунку. Адже лише протестований код — це хороший код!

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ