Юнит тесты и TDD

В очередной раз Joel Spolsky, автор отличных книг из серии Joel on Software и одноименного блога JoelOnSoftware, написал потрясающую статью. В этот раз он рассуждает про Test Driven development и, как обычно, делает это без всякого уважения к авторитетам и современным тенденциям.

Должен признаться, что моё отношение к TDD в точности совпадает с тем, что он описал в этой статье. И это радует и успокаивает.

Попробую в этой статье раскрыть мое отношение к юнит тестам и TDD.

Для начала немного теории

Юнит тестыЮнит тесты — это тесты, которые можно запустить на этапе компиляции программы. Их задача — тестирование функций и классов, а не работающей программы. Фактически, юнит тесты полностью описывают и проверяют минимальные блоки из которых строится программа.

TDD — это метод разработки, ставящий написание юнит тестов на первое место. TDD имеет очень простой алгоритм действий, описывающий всю суть TDD. Вот он:

  1. Пишем тест(ы)
  2. Если тесты не сработали — пишем код. Нельзя писать код, если все тесты срабатывают.
  3. Удаляем дублирование (рефакторинг). После каждого небольшого изменения билдим код и проверяем, что тесты срабатывают.
  4. Переходим к пункту 1.

То есть, чтобы написать код (исправить баг или добавить фичу), программист сначала должен написать тест, который будет фейлиться и только потом исправить код так, чтобы этот тест стал срабатывать. Запрещено писать новый код или править старый, если нет зафейлившихся тестов.

100% покрытие кода юнит тестами

Итак, поводом для статьи Джоеля стали письма от людей, которые призывали его добавить 13-ый пункт в его знаменитые 12 шагов, чтобы писать код лучше. Его призывали добавить пункт Юнит тесты, 100% вашего кода покрыто юнит тестами.

И ведь это сейчас практически индустриальный стандарт — почти все профессиональные книги пишут про то, что вы должны иметь 100% покрытие кода тестами, что писать новый код можно только используя TDD. Что если вы этого не делаете, то обречены вечно создавать дорогие, некачественные и плохо расширяемые программы.

Консультанты твердят то же самое. И только разработчики чувствуют себя ущербными, так как НЕ МОГУТ сделать 100% покрытие кода тестами. Они читают книги про TDD, слушают консультантов, тратят время, но 100% покрытия достичь все равно не могут. Вы, например, можете?

Все эти книги и консультанты концентрируются на идее, что требования и код будут меняться и тесты помогут изменять код почти безболезненно, улучшая качество программы. Многие даже пишут, что юнит тесты — это гарантия качества программы. Но на самом деле это не так. Юнит тесты не имеют ничего общего с качеством — это просто инструмент, позволяющий проверить, что вы не сломали ничего из прошлой функциональности. И это всё, что дают юнит тесты. При этом они далеко не бесплатны — обычно уходит чудовищно много времени на написание юнит тестов, особенно для старого (legacy) кода.

Любой юнит тест — это дополнительные расходы.

Еще, что идеологи TDD не указывают обычно — это то, что, если вы имеете 100% покрытие кода тестами, то изменение любого куска кода требует изменения и всех тестов для него. И изменение тестов со временем будет занимать все больше и больше времени. То есть, юнит тесты становятся со временем теми самыми кандалами, которые МЕШАЮТ вам делать рефакторинг или небольшие изменения — ведь для самого простого изменения интерфейса класса или функции вам придется изменить не только клиентов класса или функции, но и все тесты, которых может быть гораздо больше, чем клиентов (мест использования).

Например, представьте себе обычное меню приложения и пусть код, реализующий это меню, покрыт тестами. И есть еще куча сторонних тестов, использующих код этого меню для вызова каких-то функций. И дизайнер GUI вдруг решил убрать это меню или переместить его в другое место. Если бы у вас не было тестов — изменение было бы простым. Но если у вас есть 100 тестов, использующих это меню — вам придется изменить, а то и переделать их все и добиться их срабатывания. На это может уйти в десятки раз больше времени, чем на изменение самого кода.

Почему же так происходит и что делать?

Авторы, пишущие книги по теории программирования и консультанты — это обычно не те люди, которые много занимаются программированием. Они зарабатывают на другом и их можно назвать теоретиками. Даже если у них был когда-то большой опыт программирования, то сейчас они пишут только небольшие тестовые программки, чтобы проверить свои теории и доказать их работоспособность. Но все мы знаем, как огромная разница между программой на 100 строк и на 1 000 000 строк. И экстраполировать на большие проекты методологии, прекрасно работающие на маленьких проектах — это нелепо. Но это то, чем сейчас занимаются все Agile и TDD консультанты и авторы книг.

Они экстраполируют. Они утверждают, что есть огромные проекты, покрытые на 100% юнит тестами, но видел ли кто-нибудь когда-нибудь такие проекты? Я — нет. Зато я видел множество других проектов, где встречались юнит тесты, но процент был далек от 100. Обычно он 0-5%. Причем 5% — это уже очень много и, уверен, достаточно для большинства проектов.

Никто из разработчиков не хочет тратить месяцы и годы для покрытия тестами кода, который работает стабильно, уже 5-10 лет не менялся и еще 10 лет не будет меняться. Но руководство нанимает консультантов и те убеждают, то без 100% покрытия тестами невозможно повысить качество продукта. Ставится такая задача, все работают в поте лица, а качество программы падает каждый день.

Почему?

  • Во-первых написание тестов для legacy кода вносит в него ошибки. От этого не уйти — приходится изменять код, а значит добавлять в него баги.
  • А во-вторых, повторюсь — юнит тесты не увеличивают качество кода! Они просто позволяют его в будущем дешевле изменять.

И это нам позволяет сделать главный вывод: 100% покрытие юнит тестами вредно. Даже так: сама идея о 100% покрытии тестами вредна! Покрывать надо только те куски кода, которые будут меняться, причём несерьезно, так как при серьезных изменениях придется переделывать и все тесты, а значит старые станут бесполезной обузой.

А много ли кода, подпадающего под это определение обычно в программе? Это и есть те самые 5-10%. Так что обычно достаточно покрыть тестами 5-10% кода для получения всех преимуществ юнит тестов. Если юнит тестов становится больше, то они перестают приносить пользу, но увеличивают расходы на поддержание их работоспособности.

Например, Michael Feathers в своей книге Working Effectively with Legacy Codeне призывает покрывать все 100% кода тестами.

Мало того, он даже не призывает всегда покрывать тестами код, который вы собираетесь изменять. Он призывает думать. Каждый раз делать осознанный выбор — нужны ли вам в данном месте юнит тесты или нет. Всем советую купить и прочитать эту его книгу. Эта книга — лучшее описание теории и практики написания юнит тестов, что я встречал. Причем именно с точки зрения практики — большая часть книги посвящена описанию стандартных приемов работы со старым кодом. А сам автор имеет огромный практический опыт по улучшению legacy кода.

Моё отношение к юнит тестам и TDD

Возможно у вас сложилось ощущение, что я противник TDD и юнит тестов. Но на самом деле — нет. Я обожаю юнит тесты и некое ощущение правильности, которое они дают мне. И я обожаю TDD, когда использую его на небольших проектах или обособленных небольших модулях.

Но я противник стопроцентного покрытия кода тестами и применения TDD для работы с legacy кодом.

Для каждой методологии есть свое применение. И нет никакой серебряной пули, помогающей всегда.

Оставить комментарий