Если сравнить разработку микросервисов с возведением небоскрёба, то можно сказать, что юнит-тесты — это проверка надёжности каждой кирпичной кладки. Представьте, что один из кирпичей оказался бракованным: небоскрёб может рухнуть. Вот и с микросервисами похожая история — даже небольшая ошибка в сервисе может привести к крупным проблемам на этапе интеграции или эксплуатации.
Юнит-тесты помогают:
- Убедиться, что базовая бизнес-логика работает корректно. Например, банковский сервис не должен просто взять и подарить клиенту пару миллионов.
- Изолировать ошибки. Если тест упал, вы сразу видите, какой метод или модуль виноват.
- Облегчить рефакторинг кода. Меняете реализацию? Убедитесь, что старые тесты всё ещё проходят.
- Сохранить разработчика в здравии. Нет ничего хуже, чем отлавливать баг, который вылезет после деплоя.
Основные инструменты для юнит-тестирования
Для написания юнит-тестов в экосистеме Java вам не нужно изобретать велосипед. Всё уже давно придумано:
- JUnit 5 — это стандарт де-факто для тестирования в Java. Мощный, гибкий и довольно простой в освоении.
- Mockito — библиотека для создания "моков" (фиктивных объектов), которые позволяют тестировать модули в изоляции.
- AssertJ — для более читаемых и расширенных утверждений в тестах (необязательно, но полезно).
JUnit 5: основы тестирования
JUnit позволяет писать единичные тесты (юнит-тесты), которые проверяют конкретные методы или классы. Наиболее полезные аннотации 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));
}
}
Подходы к написанию юнит-тестов
При тестировании сервисов вы, скорее всего, столкнётесь с различными зависимостями: репозитории, другие сервисы, внешние API. Чтобы тестировать логику изолированно, нам нужно:
- Создать моки для зависимостей.
- Задать их поведение с помощью методов
when(например,when(repository.findById(1L)).thenReturn(mockEntity)). - Проверить, вызывались ли нужные методы с правильными параметрами (используя
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());
}
}
Тестирование бизнес-логики
Юнит-тесты должны проверять не только позитивные сценарии, но и граничные случаи. Например:
- Проверка на ввод некорректных данных. Сервис должен выбросить исключение или вернуть ошибку.
- Проверка на "пустые" данные: пустой список, нулевые значения и т.д.
- Проверка на максимальные и минимальные значения входных параметров.
Пример:
@Test
void testValidateInput_NullValue() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
someService.validateInput(null);
});
assertEquals("Input cannot be null", exception.getMessage());
}
Что тестировать, а что — нет?
Важно помнить, что юнит-тесты не должны:
- Тестировать сторонние библиотеки (вы уверены, что ваш
StringUtils.capitalize()работает? Надеюсь, и так понятно). - Тестировать взаимодействие с базой данных или сетью (для этого есть интеграционные тесты и Testcontainers).
Юнит-тесты должны:
- Тестировать логику вашего приложения: методы, функции, классы.
- Проверять граничные случаи: некорректные данные, крайние значения.
- Проверять, что вызовы зависимостей происходят именно так, как нужно (с помощью Mockito).
Типичные ошибки при написании юнит-тестов
- Тесты зависят от внешнего окружения. Например, если тест обращается к реальной базе данных.
- Отсутствие проверок. Если тест просто выполняется, но ничего не проверяет, это не тест.
- Путаница с моками. Слишком сложная настройка моков может запутать вас и ваших коллег.
- Тесты стали сложнее, чем сам код. Помните, что тесты должны быть простыми и понятными.
Практическое применение
Юнит-тесты не только помогут вам убедиться в работоспособности вашего кода, но и понравятся вашему будущему работодателю. Во время собеседования вас могут спросить: "Как вы пишете тесты?" И тут вы блеснёте своими знаниями JUnit, Mockito и стратегий тестирования.
Кроме того, качественные юнит-тесты делают изменения в коде намного безопаснее. Если кто-то из вашей команды случайно "сломает" бизнес-логику, тесты тут же об этом сообщат.
Ссылки на документацию:
Итак, берём JUnit, Mockito и начинаем писать тесты для нашего микросервисного приложения. Ведь только протестированный код — это хороший код!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ