Юнит-тест — это изолированная проверка минимальной возможной части приложения, обычно отдельной функции или метода класса. Его цель — убедиться, что эта часть работает именно так, как задумано, при различных входных данных и условиях. Ключевое слово — «изолированно». Тест не должен зависеть от базы данных, файловой системы, сети или других внешних сервисов. Для этого используются заглушки (stubs), макеты (mocks) и другие виды тестовых дублеров.
Первый шаг — выбор инструментов. Для большинства языков есть стандартные решения. В мире Java это JUnit (JUnit 5) в связке с Mockito для мокинга. Для JavaScript/TypeScript — Jest или Mocha с Chai для ассертов и Sinon для мокинга. В Python — unittest или более популярный pytest. Выбор часто зависит от экосистемы и предпочтений команды. Начинайте со стандартного для вашего стека.
Что тестировать? Золотое правило: тестируйте публичный контракт, а не реализацию. Фокус должен быть на поведении (что делает функция при заданных входных данных), а не на внутреннем состоянии или последовательности вызовов. В первую очередь пишите тесты для бизнес-логики — там, где происходят вычисления, преобразования данных, принятие решений. Геттеры/сеттеры, тривиальные делегирующие методы или код, являющийся простой оберткой над сторонней библиотекой, обычно не требуют отдельного юнит-тестирования.
Структура хорошего теста часто описывается паттерном AAA: Arrange, Act, Assert.
- **Arrange (Подготовка):** Настройка тестового окружения. Создание тестовых данных, моков зависимостей.
- **Act (Действие):** Вызов тестируемого метода с подготовленными данными.
- **Assert (Проверка):** Проверка, что результат действия соответствует ожиданиям.
Arrange: a = 5, b = 3, expectedSum = 8.
Act: result = add(a, b).
Assert: assert result == expectedSum.
Важнейший аспект — изоляция. Допустим, ваш класс `UserService` зависит от `UserRepository` для работы с базой данных. В тесте `UserService` вы не должны использовать реальный репозиторий. Вместо этого вы создаете mock-объект `UserRepository`, настраиваете его на возвращение определенных тестовых данных при вызове нужных методов и передаете его в конструктор `UserService`. Таким образом, тест проверяет только логику сервиса, а не работу базы данных или репозитория.
Написание тестов должно идти рука об руку с разработкой. Здесь на помощь приходит методология TDD (Test-Driven Development — разработка через тестирование). Её цикл «Красный — Зеленый — Рефакторинг» дисциплинирует процесс:
- **Красный:** Напишите небольшой тест для новой функциональности, который падает (так как функциональности еще нет).
- **Зеленый:** Напишите минимальный объем кода, чтобы тест прошел.
- **Рефакторинг:** Улучшите код, сохраняя все тесты зелеными.
Интеграция в процесс разработки — ключевой этап. Юнит-тесты должны запускаться автоматически:
- **Локально:** Перед каждым коммитом. Настройте pre-commit hook или просто выработайте привычку запускать тесты.
- **На CI-сервере (Continuous Integration):** При каждом пуше в репозиторий. Сборка не должна считаться успешной, если не пройдут все юнит-тесты. Это страхует основную ветку от попадания сломанного кода.
- **Как часть Definition of Done (DoD):** Фича не считается завершенной, пока для неё не написаны юнит-тесты.
Внедрение культуры тестирования в команде — это эволюционный процесс. Начните с малого: договоритесь писать тесты для нового функционала. Проводите code review тестов так же внимательно, как и основного кода. Со временем вы ощутите преимущества: меньше багов в production, больше уверенности при рефакторинге и, как ни парадоксально, более высокая скорость разработки в долгосрочной перспективе благодаря надежному фундаменту.
Комментарии (11)