Стратегия модульного тестирования
Модульное тестирование является одной из ключевых практик методологии экстремального программирования. Сторонники XP приводят следующие доводы в защиту этой практики:
- Написание тестов помогает войти в рабочий ритм
- Придает уверенность в работоспособности кода.
- Дает запас прочности при дальнейшей интеграции или изменениях кода.
Согласен, вхождение в рабочий ритм — благородная задача. Уверенность в работоспособности — тоже хорошо. Но «уверенности в работоспособности» я предпочитаю действительно работоспособный код. Пусть даже при этом я не совсем «уверен».
Ключевой фактор при оценке перспективности любого метода — стоимость проекта. Дополнительная работа по созданию тестов, их кодированию и проверке результатов вносит существенный вклад в общую стоимость проекта. И то, что продукт окажется более качественным не всегда перевешивает то, что он будет существенно дороже.
Известно, что продукт оптимальный по набору бюджет/функциональность/качество получается при применении различных способов обеспечения качества. Бездумное применение тотального модульного тестирования почти гарантированно приведет к получению неоптимального продукта. И никакие «запасы прочности» и «быстрый вход в рабочий ритм» не спасут проект от провала.
На мой взгляд, модульное тестирование оправдано, если оно:
- Снижает время на отладку
- Дает возможность поиска ошибок с меньшими затратами, нежели при других подходах
- Дает возможность дешевого поиска ошибок при изменениях кода в дальнейшем
Суммарный выигрыш от применения модульных тестов должен быть больше, чем затраты на их создание и поддержание в актуальном состоянии.
Если в результате исправления ошибок интеграции меняется исходный код, в нем с большой вероятностью появляются ошибки. Если в результате добавления новой функциональности меняется исходный код, в нем с большой вероятностью появляются ошибки. И искать их лучше с помощью ранее созданных модульных тестов.
Цель модульного тестирования:
Получение работоспособного кода с наименьшими затратами.
И его применение оправдано тогда и только тогда, когда оно дает больший эффект, нежели другие методы.
Отсюда следует несколько выводов:
- Нет смысла писать тесты на весь код. Некоторые ошибки проще найти на более поздних стадиях. Так, например, для ООП данное правило может звучать так: нет смысла писать тесты на класс, который используется только одним классом. Эффективней написать тесты на вызывающий класс и создать тесты тестирующие все участки кода.
- Писать тесты для кода потенциально подверженного изменениям более выгодно, чем для кода, изменение которого не предполагается. Сложная логика меняется чаще, чем простая. Следовательно, в первую очередь имеет смысл писать модульные тесты на сложную логику. А на простую логику писать позднее или вообще тестировать другими методами.
- Для того чтобы как можно реже изменять тесты следует хорошо планировать интерфейсы. То же самое можно сказать и применительно к написанию исходного кода. Действительно, создание хорошей архитектуры часто определяет дальнейший ход проекта. И есть оптимум, на каком этапе архитектура «достаточно хороша». Все так, но я хочу сказать о другом: