понедельник, 18 апреля 2016 г.

Размышления о TDD

В наших с вами тырнетах снова образовался всплеск активности по поводу 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. Я рассматриваю индивидуальный процесс разработки, как нечто личное: что-то, что полезное вам, не факт, что подойдет мне. Команда может и должна принять решение о том, что пушить на прод нельзя, что код нужно ревьюить, что любая фича должна иметь вменяемое покрытие юнит и/или интеграционными тестами.

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

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

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

18 комментариев:

  1. Спасибо, Серега. Хорошая статья.

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

    Но я согласен с основной темой статьи. It depends.

    ОтветитьУдалить
    Ответы
    1. Посмотрите мой definition of done: тесты - это часть результата и они должны быть по завершению задачи. Точка. Вопрос же в том, когда они должны появиться: до, в процессе, или после работы над продакшн кодом.

      Т.е. тесты нужны в подавляющем числе случаев и же даже привел пример, что я нахожу их полезными в краткосрочной перспективе;)

      Удалить
    2. Этот комментарий был удален автором.

      Удалить
  2. Интересная статья. Набросал ты тут на вентилятор :). По делу - можно даже расширить твое утверждение. Если ты реализуешь proof of concept и задача так и называется (на доске) "Выяснить возможность реализации bla-bla-bla" тесты будут waste of time. Для меня основная ценность тестов - фиксация логики работы. Бездумный рефакторинг поломает тесты (а значит - логику). И исправление тестов после рефакторинга - архиважная задача на ревью, лучше в 4 глаза.
    В чем согласен, вопрос - когда, где, сколько - сильно вторичен. Тестов должно быть достаточно, чтобы быть уверенным в коде. Остальное - лирика.

    ОтветитьУдалить
  3. Весьма зрелое и трезвое рассуждение о тестах, спасибо. К сожалению и большому удивлению еще приходится встречать архитекторов, техлидов, менеджеров и т.е. которые яростно "боготворят" TDD без хорошего понимания сильных и слабых его сторон.

    ОтветитьУдалить
  4. Ага, щас!
    * А почему бы не оставить каждому водителю решать, когда пристёгивать ремень безопасности, а когда нет? Пусть он сам решает, каким путём он достигнет безопасности движения.
    * почему бы не оставить каждому ребёнку решать, когда ему есть суп, а когда конфеты? Главное ведь вырасти здоровыми, а уж каким именно способом он/она будут добиваться нужного результата - пусть сами решают.
    * можно ли молодёжи колоть наркотики? Правильно сказал Кент Бек: it depends!

    А вообще, знаете, вы боретесь с выдуманным врагом. Вы тут рисуете неких неадекватных бездумных фанатиков, ничего не видящих вокруг, кроме TDD (более того - не понимающих его сильных и слабых сторон!). Это такое типичное "начальник дурак, ничего не понимает". А их не существует. Представьте себе, они очень даже адекватные, и они вполне себе отдают отчёт в том, что у любого инструмента есть плюсы и минусы.

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

    ОтветитьУдалить
    Ответы
    1. Не бросайтесь в крайности. "It depends" в первую очередь означает наличие контекста, в рамках которого должен (или не должен) применяться тот или иной инструмент/методология/архитектура/etc. Основной посыл статьи был как раз о том, чтобы люди учитывали этот самый контекст и не разводили демагогию на тему "TDD применяем всегда/TDD - пустая трата времени". Исходя из ваших примеров, корректнее было бы сказать, например, должен ли водитель пристегиваться когда паркуется во дворе? А должен ли он пристегиваться, когда едет по автобану на скорости 80+ км/ч? Вот тут как раз и проглядывается контекст, исходя из которого можно дать совершенно разный ответ.

      Удалить
    2. Знатно бомбануло:))

      Но вы передергиваете;), я же не писал, что можно кодить как угодно, компилятор все стерпит. Я написал, что тесты - должны быть, дизайн - должен быть вменяемым, а код - сопровождаемым. Но при этом я оставил свободу выбора того, как эти требования будут реализованы.

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

      Удалить
  5. Good points. Как по мне, основная ценность TDD - в создании положительной обратной связи для нашей limbic system. Все остальные плюшки достижимы и с помощью test after подхода. Хорошая статья на эту тему: http://www.jefclaes.be/2014/12/tdd-as-crack-cocaine-of-software.html

    ОтветитьУдалить
    Ответы
    1. Владимир, спасибо.

      И отдельное спасибо за ссылку!

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

    ОтветитьУдалить
    Ответы
    1. У меня как-то были довольлно вменяемые, но непростые коммуникации с заказчиком по продавливанию тестов, как культурного явления. Было сложно даже при наличии поддержки со стороны ряда тех лидеров заказчика. А если бы этой поддержки не было, то представить себе сложно, что бы из этого вышло.

      Удалить
  7. Было интересно походить по ссылкам в начале статьи и почитать разные точки зрения, а затем и мнение автора этого блога. Спасибо, Серёж.

    ОтветитьУдалить
  8. А я еще вот думаю - в каких таких чудесных компаниях работают люди, что у них есть возможность, а точнее время, работать по ТДД? Мне вот навалят в джиру 20 тасков, которые надо сделать до следующего релиза (например через 2 недели), и если я на каждый таск найду время написать хотя бы 1-2 теста - это уже отличный результат, многие другие разработчики у нас в компании даже этого не делают.
    На вопрос начальнику "а как же рефакторинг и тесты?" - ответ (вполне обоснованный, к сожалению) - "если мы опять просрочим и не выкатим релиз через 2 недели - нас всех уволят, так что сейчас закрываем таски, а рефакторинг и плюшки - потом".

    ОтветитьУдалить
    Ответы
    1. Начальнику, как и любому менеджеру, нужно показать/рассказать/доказать, что будет через пол года/год с таким workflow.
      Показать, как будет увеличиваться количество ошибок в течении этого времени, количество времени необходимое на внесение правок в код и выполнение новых задач в будущем. И показать как будет все, если писать тесты и рефакторить код. Менеджер не будет просто верить на слово, нужны показательные метрики.
      И если он не хочет через пол года увольнения, то задумается.

      Удалить
    2. Присоединяюсь к анонимусу и со своей стороны добавлю, что не всегда нужно об этом говорить: можно вклинивать в работу время на небольшие изменения инфраструктуры и доведения кода до тестируемого состояния.

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

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

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

      Это ситуация когда TDD точно
      совершенно не жив. Можете злиться, отрицать, впадать в депрессию, пытаться торговаться, но в конце будете вынуждены принять очевидное.

      Удалить