Пользователь
0,0
рейтинг
16 июня 2014 в 10:01

Разработка → TDD мертв. Да здравствует тестирование перевод

От переводчика. Давид Хейнемейер Ханссон данной статьей поднял острую тему обязательности использования TDD и, даже, возможного вреда от написания тестов перед написанием кода. Именно эта статья послужила лейтмотивом уже пяти встреч на тему жив ли TDD, на которых Давид, Кент Бек и Мартин Фаулер обсуждают достоинства и недостатки TDD, рамки применимости и ограничения. Для тех у кого восприятие устного английского оставляет желать лучшего, SergeyT публикует краткие саммари в своем G+.


Фундаментализм подхода «тесты вперед» как половое просвещение, ограниченное воздержанием: нереальная и неэффективная нравоучительная кампания полная ненависти к себе и стыда.

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

Подход «тесты вперед» был замечательными поддерживающими колесиками, он учил меня, как думать о тестировании на более глубоком уровне, но достаточно быстро я отказался от него.

Спустя несколько лет, риторика «тесты вперед» стала громче и злее. Более подлой. И время от времени меня засасывало в воронку фундаментализма, я чувствовал себя плохо от того, что я не следую истинному писанию. В такие моменты я пробовал практиковать «тесты вперед» в течение нескольких недель, только чтобы бросить снова, когда моим проектам становилось хуже.

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

Может быть, было необходимо использовать подход «тесты вперед», как таран для разрушения отраслевого «жаль, у нас нет автоматизированного, регрессионного тестирования». Может быть, это была притча, которая не должна была стать буквальным описанием ежедневного стиля программирования. Но как бы это не задумывалось, вскоре все пошло не так. И теперь это используется как молот, чтобы сбивать неверующих, как лакмусовая бумажка, чтобы объявить всех остальных непрофессионалами и непригодными к программированию.

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

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

Так куда мы идем?

Шаг первый: необходимо признать, что есть проблема. Я думаю, что мы сделали это только что. Вторым шагом необходимо сдвинуть спектр тестирования от модуля к системе. Нынешний фанатичный подход к TDD делает основной упор на модульные тесты, потому что, считается, что они способны управлять дизайном кода (изначальное обоснование подхода «тесты вперед»).

Я не думаю, что это верно. Подход «тесты вперед» приводит к чрезмерно сложной структуре посреднических объектов и окольных путей, так как необходимо избежать «медленных» операций Например, обращений к базе данных. Или чтение-запись файла. Или запуска тестов в браузере, чтобы проверить всю систему. В итоге мы приходим к созданию монструозной архитектуры. Густые заросли служебных объектов, команд и чего похуже.

Я редко пишу юнит-тесты в традиционном смысле этого слова, где все зависимости мокаются, и тысячи тестов могут выполнены за несколько секунд. Это просто не было хорошим способом тестирования Rails приложений. Я тестирую модели Active Records напрямую, предоставляя доступ к реальной базе данных. Тесты работают на уровне контроллеров, но я бы пошел дальше и заменил бы эти тесты еще более высокоуровневыми тестами на Capybara.

Я думаю, что это то направление куда мы идем. Меньше внимания на юнит-тесты, потому что мы больше не используем подход «тесты вперед» как проектную практику и больше внимание на медленные системные тесты. (Которые, кстати, вовсе не обязательно должны быть медленными, за счет использования распараллеливания и облачной инфраструктуры).

Rails могут помочь с этим переходом. Сегодня мы ничего не делаем, чтобы поощрять полные системные тесты. У нас нет инструмента по умолчанию в стеке. Но мы собираемся исправить этот недостаток. Но не ждите, пока это произойдет. Попробуйте Capybara уже сегодня, и у вас будет хорошее представление о том, где мы окажемся завтра.

Но сначала сделайте глубокий вдох. Мы ведем наших священных коров на убой прямо сейчас. Это очень болезненно. Методология TDD настолько успешна, что теперь она вплетена в личности большого количества программистов. TDD не только то, что они делают, это, кто они. Мы проходим депрограммирование, чтобы выйти из-под контроля TDD, и это займет некоторое время.

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

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

Да здравствует тестирование.
Перевод: David Heinemeier Hansson
Пешков Евгений @GraDea
карма
39,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (36)

  • +9
    Шаг первый: необходимо признать, что есть проблема. Я думаю, что мы сделали это только что.

    Не увидел никакой аргументации против TDD до этой строки. Да и после не особо много аргументов, сплошные эмоции и призыв пользоваться неким тулом.
    • +1
      недостатков два.

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

      второй — если код переписывается даже слегка — юниттесты тоже приходится переписывать
    • +2
      я понял аргументацию так: поскольку наблюдаемая практика регулярно расходится с теорией, значит что-то не так в этой теории — может все это не про нашу реальность, а может ограничения заданы не верно. ведь с момента появления первых упоминаний о tdd уже больше десяти лет прошло, а вопросы все те же.
  • +8
    Выглядит как начало длинной и обстоятельной статьи или даже книги. И вдруг обрывается…
    • +3
      Продолжение в hangout'ах. Их уже пять и не знаю когда они остановятся. Я так понял, что Давид «набросил», чтобы, в том числе, спровоцировать обстоятельное обсуждение накопившихся проблем.
  • +3
    Очень спорно, я считаю. Подход «мокать все и везде» возник тоже не на пустом месте. Лично по моему опыту, поддерживать полное тестовое окружение (с базами данных, очередями и прочими непотребностями) бывает гораздо более болезненно, чем усложненную архитектуру, предполагающую тесты в изолированном окружении.
    Мне вот кажется, что от TDD-подохода «утром тесты, вечером стулья код» можно отказаться лишь тогда, когда у проекта есть не очень большое количество разработчиков и одновременно работа проиходит в 1-5 бранчах. А головняк с поддержкой кучи разных несовместимых версий окружения того не стоит.
  • –2
    По словам автора, TDD не существует без моков. Однако это не так, есть dependency injection и другие подходы, чтобы не использовать моки в юнит-тестах. Я допускаю, что в рельсе у автора есть серьезные проблемы с TDD, но это не повод набрасывать на TDD в целом.
    • +5
      Эм, а одно другое отрицает? Через DI втыкаем или реальный класс, или мокнутный. Или реальный с нужным состоянием.
      Я к тому, что уж DI vs Mock — это уж совсем странное противопоставление. :-)
      • +3
        Не обязательно инжектить класс. Можно инжектить то, что он возвращает. Например у меня в iOS проекте есть синглетон, который держит токен для работы с API. Я мог мокнуть этот синглетон, но вместо этого я просто вынес получение токена в отдельный метод, и банально наследуюсь от тестируемого класса, переопределяя данный метод. И без всяких моков и инжектов класса получаю возможность менять токен на лету.

        Естественно, это конкретика, но странно говорить, что TDD мертв и не обращаться к конкретным примерам. TDD жив!
        • 0
          > и банально наследуюсь от тестируемого класса, переопределяя данный метод.

          Зачем же вы тогда моки используете? Или вы их отрицаете?
          • 0
            В данном случае не использую, но иногда мокнуть проще, чем инжектить. Все зависит от ситуации, и от языка. В тех же рубях например сделать мок намного проще, чем в objective-c, rspec позволяет прямо чудеса творить.

    • 0
      --TDD не существует без моков. Однако это не так

      без моков заменять WIN32 довольно сложно — тока делать оберки в классе
      • +3
        Зря минусуете мне кажется. Я тоже считаю, что DHH (автор) зациклился на том, что TDD это обязательно сплошные моки, а если мол моков нет, то это не TDD.
        Вот, например, ответ Gary Bernhardt'a на это www.destroyallsoftware.com/blog/2014/tdd-straw-men-and-rhetoric

        По hangaut'ам сложилось впечателение, что он уперся рогом и даже не слушает ни Кента ни Мартина. Стоит на ствоем и все тут. В итоге они (Кент и Мартин) пошли на уступки, и «согласились», что TDD в некоторых случаех не так хорошо срабатывает как в других. Но это вовсе не значит, что нужно забыть про TDD именно потому, что Дэвид так решил.
        • +1
          Извините, комментарий хотел на ветку выше оставить.
  • +5
    Во первых TDD и юнит тесты — это все таки не одно и тоже. А автор связал тесно эти два понятия. Например идея написания спецификаций для системных тестов к планнингу перед написанием кода — это по большому счету тоже TDD.

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

    Мне кажется именно в эту ситуацию автор и попал. Со стороны же статья выглядит как «ок — у меня не выходит использовать TDD — поэтому я буду проповедовать, что это зло».

    TDD вовсе не мертво и работает. Просто нужно его уметь правильно готовить и выбирать область применимости.
    • +1
      Конечно, статью и выступления DHH не стоит отвязывать от контекста, ибо скорее всего он имеет ввиду, что мы:

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

      TDD где-то и работает. Но почему-то мне кажется, что статистически, число успешных проектов сделанных без TDD намного выше, чем кол-во проектов сделанных в соответствии с ним.
  • +1
    Многие программисты, написав тесты, перестают думать. Перестают моделировать поведение программы в голове и ловить баги «аналитически». А ведь предположения, допущенные при написании теста, далеко не всегда отражают реальность — то, как этот класс будет использовать клиент и в каком состоянии будет система.
    Зачастую решение состоит из сложного пересечения модулей, где надеятся на юнит-тесты рискованно.
    Действительно IoC решает многие проблемы с моками, но вопрос, нужны ли юнит тесты и насколько? Сколько стоит их поддерживать и оправдывают ли они себя? Или всё-же лучше сделать ударёние на приёмочные и интеграционные тесты — которые медленней, гоняются на сервере — но при этом тестируют интеграцию нескольких компонент, практически не изменяются с кодом и могут рассказать какую-то бизнесс историю, будучи написанными читабельно?
    В каждом проекте по-своему, призыв прост — перестать из ТДД делать священный грааль — часто юнит тесты увеличивают стоимость содержания кода, не давая ничего взамен.
    • +2
      Ну модульные тесты всегда дают что то взамен. Но вопрос окупаемости у них действительно стоит остро.

      Что касается того, что программисты перестают думать — они могут просто думать на более высоких уровнях абстракции вот и все. Классическое тестирование изолированного метода или функции например — каждому программисту будет всегда казаться, что он может смоделировать в уме ее поведение. Но по факту это не так. Он наверняка ошибется. И сам этого не заметит. Заметит пользователь, который поймает багу. Сложные системы — это сложные системы. В их разработке полагаются прежде всего на практики и методологии, а не на ум отдельных разработчиков (ну в этом контексте разговора конечно — аджайл манифест мы помним).

      TDD вообще не так уж часто используют на самом деле. И есть огромное количество людей — которые их боятся применять — потому что не умеют или умеют неправильно. По моему перестать пропагандировать TDD когда нибудь надо будет — но пока еще рано. Попытки его использования позволяют пересмотреть архитектуру, выделить значимую логику, походящую для тестирования и избавиться от незначимой (разные бесполезные маппинги и слои). Пусть это побудет еще немного священным граалем. В моей практике TDD может быть невероятно выгодным. И стоимоть поддержки тестов может окупиться с лихвой. И скорость разработки с TDD через модульные тесты может быть выше чем вообще без тестов — как это парадоксально не звучит.
    • +2
      Многие программисты, написав тесты, перестают думать. Перестают моделировать поведение программы в голове и ловить баги «аналитически».

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

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

      При этом не стоит думать, будто я призываю бросаться в TDD, оголив шашку и ни о чём не думая. Моя практика показывает, что неожиданные ситуации возникают даже тогда, когда я заранее планирую, как должен работать мой код, и строю тест-план на десяток шагов вперёд. И это самое важное. Если бы я точно так же продумал код, но написал его без тестов, то все альтернативные варианты с гораздо большей вероятностью остались бы не обнаруженными и не поддержанными в коде, что могло бы привести (да, впрочем, и приводит) к разнообразным дефектам (в том числе, довольно тупым). И здесь я полностью согласен с предыдущим комментатором: ощущение, будто мы можем удержать систему в голове целиком, со всеми нюансами работы чаще всего бывает не более, чем иллюзией. Системы, с которыми нам приходится иметь дело, как правило, слишком сложны. И программист, который трезво оценивает свои возможности, должен отставить гордыню и взять на вооружение проверенную временем практику — TDD.
    • +2
      Многие программисты, написав тесты, перестают думать. Перестают моделировать поведение программы в голове и ловить баги «аналитически».

      А не надо переставать думать. Причем тут тесты?
  • +2
    Комментатор выше всё написал. Основная проблема тестов на «моках», что они позволяют программисту полностью отстраниться от проблемы. «Если тест на моке прошёл, значит это не проблема моего кода, разбирайтесь дальше сами, что у вас там база/контроллер/внешний сервер чудит».
    • 0
      Тесты как раз и отвечают за код своего приложения. Если чудит внешнее API хостера, то это проблемы хостера. Программист разве что в саппорт может отписать. Разве нет?
      • +3
        Вот тут как раз и зарыт корень проблем. Если тесты проходят, значит чудить может только чужое приложение, радостно отвечает программист и его проблема больше не волнует.

        А это может быть нелепый race condition с записью в базу. Или дедлок на запросах с двумя копиями одного приложения. Или логика приложения и реализация его mock'ов в некоторых экстремальных случаях даёт ошибку, под которую написан код (и которой нет в реальных реализациях в продакшене, так что ошибка возникает уже в продакшене). Или логика двух модулей содержит ошибки.

        Но всё это — «чудит внешнее API, проблемы хостера».

        В этом и есть проблема «чистого TDD». Оно даёт индульгеницю программисту отмахиваться от всего, что не совпадает с его мироощущением, изложенном в mock'ах.
        • 0
          Я предполагал все-таки, что код и моки написаны корректно, потому что разговор идет о самом TDD. Ошибки случаются всегда, и четкой корреляции с TDD тут нет.
          Оно даёт индульгеницю программисту отмахиваться от всего, что не совпадает с его мироощущением, изложенном в mock'ах.

          Скорее оно позволяет быстрее локализовать проблему в большинстве случаев. Хотя точка зрения DHH мне импонирует, часто написание юнит тестов (а особенно стремление к 100% code coverage) себя не оправдывает.
        • 0
          Простите, а как же интеграционные тесты? Я понимаю, что и они всего не покрывают, но увеличивают надежность. Мне кажется нельзя гворить о работоспособности всего приложения, исходя только из того, что работают модульные тесты(у меня лично были ситуации, когда код проходит все юнит-тесты на ура и с хорошим покрытием, а в конфигурации понаписано такое, что оно просто никак не может работать). И мне кажется, создавая код, которые работает с «чужим» приложением (как пример — внешнее API), нельзя не писать интеграционных тестов.
          • –1
            Так речь про это и идёт. Если у нас 'TDD', то когда у «программиста всё работает» (читай, юнит-тесты проходят), то на свалившихся интеграционных тестах программист занимает позицию «это там у вас что-то не так».

            Тут надо понимать, что есть «светлая сторона TDD» — то, как задумывалось, и «тёмная сторона TDD» — то, как его используют. Проблема в том, что TDD позволяет очень ярко провести линию раздела и даёт некую психологическую установку, с которой можно бороться, но которая есть. Выше тред и комментарии как раз про последствия этой установки.
            • +2
              такая установка и без tdd встречается довольно часто.
              • –1
                Но TDD провоцирует и даёт основание.
            • +1
              Sorry, никогда не встречался с таким отношением. По моему опыту: либо проект летит вообще без тестов(ну исторически так сложилось), либо тесты пиуштся более-менее адекватно и по делу.
              • 0
                Тесты пишутся. А я говорю про ситуацию, которая происходит, когда «тесты показывают, что всё ок», а «в продакшене оно как-то странно себя ведёт».
                • 0
                  У нас именно так и было.
                  Юнит тесты не помогают с проблемами интеграции и мультисрединга.

                  Я об этом целый пост написал.
  • +5
    Как-то не увидел аргументов в статье и обстоятельного освещения вопроса, анализа. Есть плюсы, есть и минусы. А так больше похоже на очерк, эссе «Как мне не понравилось ТДД».
  • 0
    А интересно это выступление DHH Unlearn Your MBA на русский переведено?
  • +2
    Вставлю и я свои 5 коепеек:
    Подход «тесты вперед» приводит к чрезмерно сложной структуре посреднических объектов и окольных путей

    Нет, не ведет. Используем моки, для всегор того, что не можем дернуть явно. В языках со динамической типизацией проблем прослоек и окольных путей — нет(пример Groovy), в языках со трогой типизацией 1< фреймворков для тестирования, которые реализуют моки.

    В итоге мы приходим к созданию монструозной архитектуры

    Ну лучший код — пустая строка, ни багов, ни проблем с производительностью… вообще ничего. Ну да хватит филосовствовать.

    Мне кажется, TDD — просто инструмент, как микроскоп — хочешь бактерии рассматривай, не хочешь — гвозди забивай. И каждый инструмент где-то востребован — в стртапе, когда ни денег, ни времени, а альфа-релиз может косячить(и это нормально!) на тесты ни времени, ни сил нет. А вот если пишем биржевое приложение и в случае ошибки рискуем большими деньгами, будьте добры напишите тесты, до или после — сами решайте.

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

    Мой пост на эту тему:
    Опыт работы с TDD и размышления о том, как надо тестировать код.
  • +3
    Помните старую шутку Задача: прострелить себе ногу?

    Я вот подумал, как она будет реализовываться с TDD:

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

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.