Несмотря на то, что повсеместные гимны юнит-тестированию отзвучали уже лет 10 назад, и в настоящее время следование практике юнит-тестирования стало составляющей общей грамотности разработчиков, довольно часто приходится встречать команды и отдельных разработчиков, которые этой практике не следуют. Происходит это потому, что навыки грамотного юнит-тестирования относятся к навыкам "с отложенным результатом", т.е. усилия вы предпринимаете сейчас, а результат получаете потом, в процессе сопровождения, доработки и регрессионного тестирования кода. Таким навыкам очень сложно научиться самому, т.к. только на опыте работы команды можно почувствовать балланс между затраченным на юнит-тестирование временем и полученным от него результатом. Именно поэтому внедрение практики юнит-тестирования - задача прежде всего технического лидера команды, а наличие в команде специалиста с предыдущим успешным опытом в этой области ускоряет такое внедрение в разы. Тем не менее, наличие инструктора не отменяет знания основ грамотного юнит-тестирования, чему я и решил посвятить этот пост.
Итак, хорошие юнит тесты:
- Автоматизированы
- Выполняются быстро: выполнение сотни или даже тысячи юнит-тестов должно занимать считанные секунды
- Написаны в рамках эффективного фреймворка
- Независимыми друг от друга
- Результат работы теста воспроизводим и повторяем
- Легко поддаются отладке
- Написаны автором кода
- Оставляют конфигурацию / окружение в неизменном состоянии
- Имеют высокое покрытие кода (code coverage): при создании юнит-теста важно выполнить каждую строчку тестируемого кода и убедиться что все возможные условия протестированы.
- Поддаются хранению и сопровождению
- Тестируют как функциональность, так и аварийные ситуации: т.н. "позитивные" тесты подтверждают, что метод работает корректно в ожидаемых условиях, а "негативные" тесты проверяют что метод корректно обрабатывает нержиданные или критические условия
- Покрывают граничные значения входных параметров: посхольку дефекты часто имеют место на границах, хоршей стратегией есть покрытие значений немного не доходящих до граничных, равных граничным, и немного превосходящих граничные.
Юнит-тесты имеют и некоторые органичения, о которых тоже нужно помнить:
- Юнит-тестирование не находит все ошибки в коде: оно показывает только наличие ошибок, а не их отсутствие
- У юнит-тестирования ограниченные рамки: это тестирование «белого ящика»
- Юнит-тестирование не покрывает сценарии и интеграцию между компонентами
Одной из наиболее важных техник, которые позволяют создать хорошие юнит-тесты, является техника использование mock-объектов (mock objects). Mock-объект - это симуляция реального объекта там, где использование реального объекта невозможно или нарушает принципы юнит-тестирования, например когда реальный объект:
- предоставляет недетерминированные результаты (например текущее время или температуру)
- имеет трудно воспроизводимые состояния (например ошибка сети)
- медленный (например полная база данных или файл на диске)
- ещё не существует или может изменить поведение
- должен содержать информацию или методы, необходимые только для тестирования