Автор: Sergey Teplyakov

В наших с вами тырнетах снова образовался всплеск активности по поводу TDD, о его здоровье и жизненных показателях. Началось все с поста Ian Sommerville Giving up on test-first development, а продолжилось постом Боба я все знаю о дизайне Мартине Giving up on TDD и еще несколькими постами.

Сегодня я хочу рассказать, какую роль в моей практике играют тесты и почему споры о том, жив ли TDD или нет, все же стоит прекратить.

Результат vs. Процесс

Когда речь заходит о TDD (Test-Driven Development), то возникает впечатление, что речь идет о каком-то тайном знании. Знающие индивидуумы рассказывают о своих успехах с хитрым выражением лица и некоторым снисхождением к тем, кто еще не осознал всей прелести этой аббревиатуры. При этом, когда их просят рассказать о выгодах сего процесса, они начинают бормотать о пользе тестов, важности хорошего дизайна, легкости рефакторинга и гибкости получаемых систем. Иногда, в качестве аргументом могут встречаться фразы о самодокументируемом коде, наличиях встроенной в тесты спецификации и высоком покрытии, как о важном артефакте любой вменяемой кодовой базы. А если вы начнете детально обсуждать модульные тесты, то вас перебьют и скажут, что TDD это вообще-то про дизайн (т.е. про проектирование), а не про тестирование.

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

Понимание целей помогает отделить букашек от вкусняшек, поскольку довольно быстро дает понять, что прийти к нужной цели можно разными путями, и пути эти в разных случаях могут и будут разными.

It depends!

Если вы спросите у Кента Бека (того самого автора TDD) о том, стоит ли мне использовать TDD или нет, то он ответит своей любимой фразой: It depends!, и будет прав.

Мы не можем абстрактно рассуждать о том, когда нужно писать тесты, сколько их должно быть, да и нужны ли они вообще в этот момент времени. Любая методология или практика это инструмент, который должен максимизировать получаемый результат. Если вы занимаетесь исследованием или прототипированием, то результатом работы является достижимость (feasibility) решения, а не готовый код. Если вы дизйните кусок новый системы и не имеете понятия, что должно быть на выходе, то наиболее разумно будет нарисовать кружки/квадратики на бумаге, а потом представить свой дизайн на обсуждение нескольким разумным коллегам. А если вы реализуете компонент с десятками граничных условий и сложной бизнес логикой, то отсутствие вменяемого набора тестов будет выглядеть подозрительно не зависимо от приверженности членов команды культу TDD.

Тесты, и даже хороший дизайн это не самоцель, это лишь средство для ускорения процесса разработки и/или повышения качества результата. И даже если вашей целью является высокое качество дизайна и кода, то нет смысла тратить слишком много сил на навязывание собственного способа достижения этого результата.

ПРИМЕЧАНИЕ. История о тестах во время хакатона.
Бытует мнение, что тесты дают выгоду лишь в длительной перспективе. Я с этим в не согласен и хочу поделиться примером из своей практики.
Прошлым летом проходил трехдневных хакатон всея Майкрософт и мы с несколькими ребятами делали code search движок, очень похожий на referencesource.microsoft.com. Я отвечал за бэкэнд, в качестве которого был выбран ElasitcSearch. Я не начинал с тестов, но после создания слоя доступа я написал десяток интеграционных тестов, которые покрывали основные CRUD-операции. В процессе написания тестов коллеги на меня косились с явным недоверием, но уже к концу первого дня тесты начали помогать, поскольку способствовали скорости внесения изменений.

Внутренний цикл vs. Внешний цикл разработки

Одна из мыслей, которая лежит в основе TDD заключается в уменьшении цикла: подумал накодил проверил, что работает, что позволяет держать в краткосрочной памяти меньшее количество концепций и успешно строить систему по принципу снизу вверх из проверенных и хорошо работающих компонентов.

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

Иногда я нахожу полезным сфокусироваться на коде на довольно продолжительное время, сделать каркас функциональности, а потом перейти к написанию пачки тестов, которые покроют текущий функционал, возможно, с некоторым запасом. Мой внутренний цикл разработки обычно длиннее предложенного Кентом в рамках TDD, и я просто не могу переключаться между кодом и тестом каждые 2-3 минуты. Я не говорю, что это плохо, я лишь говорю о том, что я нахожу этот подход менее эффективным.

Я нахожу полезным думать о коде в терминах его клиентов, но при этом я буду думать об обязанностях классов через призму контрактов и возможных тестов, но не обязательно брошусь писать тесты до написания кода.

Но помимо внутреннего цикла, который определяет процесс реализации задачи, очень полезно выделять еще и внешний цикл разработки: жизненный цикл поставленной задачи.

Важно обговорить внутри команды и прийти к общему пониманию того, что же означает фраза задача готова. Для одного девелопера это значит, что кодить я почти закончил, нужно шлифануть код и начать интегрировать полученное чудо в общую систему. А для менеджера эта же фраза означает, что человек уже свободен и может приступать к решению другой задачи.

В большинстве команд, в которых я работал существовали достаточно вменяемые определения завершенной задачи (definition of done):

  • Код должен быть отрецензирован
  • К коду должны прилагаться тесты (юнит, интеграционные и т.п.), покрывающие основные и граничные условия
  • Код должен быть чистым и вменяемым: он должен следовать принятому (формально или нет) стандарту кодирования, должен содержать вменяемое число комментариев и т.п.

Пациент скорее жив, чем мертв!

Я понимаю, почему в наших интернетах не утихают баталии по поводу скрамов и прочих канбанов. Во-первых, там есть тучи консультантов, которые зарабатывают на этом деньги, а во-вторых, подобные методологии коренным образом влияют на процесс разработки в целом.

Но мне не ясно, почему не утихают фанатские споры по поводу TDD. Я рассматриваю индивидуальный процесс разработки, как нечто личное: что-то, что полезное вам, не факт, что подойдет мне. Команда может и должна принять решение о том, что пушить на прод нельзя, что код нужно ревьюить, что любая фича должна иметь вменяемое покрытие юнит и/или интеграционными тестами.

Можно и нужно делиться лучшими практиками и опытом решения сложных проблем. Можно объяснять, почему я пишу тесты вначале, или почему я пишу их в середине работы над задачей, но я не вижу смысла навязывать свою точку зрения, считая другую невалидной. Нужно доказывать, что тесты нужны, но нет смысла перегибать палку и не давать свободу их написания!

Так почему бы не тратить энергию на решение задачи с определенными требованиями к качеству, и не оставить каждому разработчику решать, каким именно способом он/она будут добиваться нужного результата?

Дополнительные ссылки

  • Is TDD Dead?
  • Все что у меня есть по автоматизированным тестам

Помогла статья? Оцените её!
0 из 5. Общее количество голосов - 0
 

You have no rights to post comments

Дмитрий Крикунов

Публикую статьи, обучающие курсы и новости по программированию: алгоритмам, языкам (С++, Java), параллельному программированию, паттернам и библиотекам (Qt, boost).