О стартапе-ловушке, или Роберт Мартин хочет нам навредить

Original author: Rob Ashton
  • Translation
Я почувствовал, что устои мироздания потрясены, когда сотни хабраюзеров начали яростно спорить по поводу заметки Роберта Мартина о стартапе-ловушке.

Хотите знать, как я обычно участвую в таких спорах?

— Так какие же тесты пишешь ты сам?
— Мнэ-э…

— Когда же ты пишешь тесты?
— Мнэ-э…

— Ты вообще тесты пишешь?
— Мнэ-э…

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

Но как раз сейчас у меня, кажется, есть эта парочка часов.

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

Проблемы.

Это важно. Проблемы. Повторите еще раз — Проблемы. Если вам неприятно, давайте решать эти проблемы.
  • У вас очень много тестов, которые приходится удалять, если какая-то фича оказалась ненужной? Ну так не пишите их так много — вы скорее всего экспериментируете с продуктом, и, возможно, подход «исследуй и стабилизируй» подойдёт вам лучше.
  • Вам трудно вносить изменения в код, потому что при этом сразу всё ломается, и компоненты очень сильно-связаны? Напишите несколько тестов на эту подсистему, попробуйте test-first подход при написании нового кода, и проблемы с сильной связанностью будут решены.
  • Вам приходится танцевать с бубном вокруг маленькой фичи, потому что вы стремитесь к тончайшему юнит-тестированию, а это значит, что приходится добавлять тонны кода там, где хватило бы простых CRUD-операций? Остановитесь, подумайте об интеграционном тестировании и об использовании in-memory БД для ускорения тестов.

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

Когда вы пишете по классическому ТДД, вы теряете большую часть денег вашего заказчика, но если вы не пишете тестов вообще, то вам придется потратить все это время еще раз — в будущем.

Классическое TDD — это отличный теоретический прием, с которым можно поиграться в свободное время или на выходных. Классическое TDD — отличный маркетинговый слоган, если вы — один из тренеров или TDD-коучей. Да, TDD — этот пункт, на который обязательно смотрят при приеме на работу, но TDD — это обычно не тот путь, которым идет отличное приложение, сделанное в срок.

Так какие же тесты ты всё-таки пишешь?
  • Я обычно использую подход «исследуй и стабилизируй» для тех фичей, которые, возможно, будут в итоге исключены из конечного продукта. Таким образом я не трачу время на тесты раньше, чем нужно, и получаю обратную связь по новым возможностям так быстро, как это только возможно.
  • Я обычно начинаю с интеграционных тестов, заменяя все медленные части на их представления, работающие с памятью (например, заменяя БД на in-memory БД в тестах).
  • Я спускаюсь на уровень юнит-тестов, если вижу сложную логику (как ни странно, большинство систем — это обычные CRUD, так что это необязательно)
  • Сценарии использования обычно проверяются с использованием UI как часть приемочных тестов, поскольку это именно то, что видит пользователь.


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

От переводчика: Rob Ashton — известный фриланс-разработчик из Европы, участвующий во многих open-source проектах.
Share post

Similar posts

Comments 37

    +4
    Ну это же очевидно!

    Спасибо за статью :)
      0
      Вообще странные вещи какие-то обсуждаются и много очевидного. Если вы не используете разработку через тестирование, значит либо вам это не надо, либо вы не хотите саморазвиваться как программист(выкинем отсюда различные «исключительные» ситуации). Я не infected, но все же изложу ниже «мое» виденье всего этого:
      >>Классическое TDD, то самое, которое говорит, что тесты «не должны ничего знать» о реализации, о настоящем коде — умерло, поезд ушел, все кончено
      «Палка о двух концах», да в идеале по XP не должны, но на то и нам нужны моки и стабы чтобы все таки «заглядывать» в реализацию методов, проблема в том что даже с DI и прочим не обойтись без такого «заглядывания», это нормально, ведь unit-tests нужны именно для проверки работоспособности, именно эта цель важна, но она конечно не оправдывает «гавнокода» в тестах если кто-то решил все списать туда)) В идеале для себя я вижу как BDD->TDD, причем интеграционные тесты это именно BDD, ну также можно и начинать для простых сущностей с BDD, но все же прийдете к частному в виде unit-tests, так и должно быть. Я очень не люблю участвовать тоже в таких холиварных топиках, поэтому наверное скажу что каждый нормальный программист, стремящийся к саморазвитию рано или поздно приходит к TDD, затем к BDD. Важная вещь в этом всем это умение и желание развивать себя как программиста)
      P.S. Часто можно увидить людей вопрощающих «научите» — учитесь сами, ведь у вас итак есть куча материала для этого. К тому же что касается php программистов, то ведь у вас же есть тоже такие люди как everzet и davert, у которых есть хорошие инструменты для разработки, почему же вы их не используете? И еще меня коробит когда говорят «пишите тесты», не надо «писать тесты», разрабатывайте через тестирование!) Ну и читайте конечно же книги)) Поэтому если разделять по уровням, то внутри TDD а сверху него внешне BDD. =)
        0
        Важно в этом деле не забывать о здравом смысле :-)
        Имхо, уметь писать тесты, владеть техническим инструментарием должен каждый. Уметь правильно применять — это приходит с опытом, за 5 минут этому не научить. Как и с паттернами, впрочем.
          +1
          Вот у меня почему-то возникает ощущение, что тесты должен писать архитектор ( тимлид, ведущий, называйте как хотите), который представляет как все сверху должно работать… а реализовывать должны некие «кодеры», делать так, чтобы эти тесты заработали. Заработал тест — задача выполнена. Т.е. если задачи раздавать в виде тестов, то это не что-то асбтрактное на словах, а нечно конкретное.
            0
            А я с вами даже соглашусь. Если вообще работать с тестами то их как минимум надо делать на пару — иначе так же как и ручное тестирование — нет смысла делать в одиночку.
              0
              У вас извращенное какое-то понятие о тимлиде. Тимлид может участвовать в разработке напрямую да, и писать код через тестирование, но уж ни как не определять кому какой тест написать. Тимлид (читай у кого скрам-мастер) должен просто следить за тем чтобы работа «спорилась», он не должен своим авторитетом ни в коем случае затыкать мнение джуниоров или мидлов, он наоборот должен заставлять их кооперироваться, обсуждать, и принимать коллективное решение, когда такое невозможно то конечно кто-то должен взять на себя смелость за то или иное решение, и да это часто тимлид делает. «Раздавать задачи в виде тестов» это не верно вообще, как вы сами понимаете. Тимлид должен способствовать раскрытию его подопечных как программистов, это конечно в лучшем случае) но уж ни как не тыкать каждого носом в то какой он именно должен тест писать по мнению тимлида, не забывайте что программирование это отчасти также и творчество, если можно так сказать))
            +7
            Взвешенное, сбалансированное мнение. Минус этой статьи в том, что спорить в комментариях не о чем :) Поэтому расскажу историю.

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

            Интересен культурный аспект этого конфликта. Гибкий, сложно формализуемый подход «здесь тестирую, а здесь нет; здесь unit, а там интеграционно и т. д.» из статьи — это чисто по-русски; догматический же TDD очень подходит немецком менталитету.

            Прошло полтора года. На днях мне пришел отчет с ретры немецкой команды, которая признала (внутри своего коллектива), что была вынуждена отказаться от догматичного следования TDD. Распечатал этот email и повесил над своим столом. Подумывал затребовать официального признания правоты российской стороны, но вряд ли это бы улучшило и без того зыбкое взаимопонимание между командами :) Да и, надо признать, догматичность немцев меня тоже кое-чему научила, заставила серьезнее задумываться о тестировании кода.
              +2
              Привет, Саша! Немцам можешь тоже передать привет при случае :-)
              Ну только ушёл я не из-за категоричности, а из-за банального отсутствия перспектив в сложившемся раскладе. Хотя в контексте истории категоричность смотрится красивее :-)
                0
                Слепое следование методологиям разработки к добру не приводит — я всегда это говорил.
                +3
                Только что подумал,
                Что Боб Мартин просто показал высший пилотаж тонкого троллинга. Такое мог сделать только Мастер.

                Заодно многие подумают о переходе на tdd, что есть гуд
                  0
                  Дискуссии — это прекрасно! :)
                    +2
                    Давно задумывался о применении TDD, даже пробовал написать небольшой проект по этой методологии. Ощущение было двояким — с одной стороны позитив: уверенность в своем коде, понимание что невыявленных тестами ошибок нет, возможность прогнать регрессию и убедиться что ничего не сломано. С другой стороны — понимание что на тесты ушла просто уйма времени (в небольшом-то проекте) и что в более-менее серьезном проекте я просто «закопаюсь» в тестах.

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

                    Думаю что «ванильное» TDD гарантирует высокое качество, но по соотношению скорость разработки/качество продукта все-таки проигрывает «сбалансированному» подходу (который описан в статье). Тем более что во многих случаях качество не является сверхважной характеристикой продукта, важно чтобы в продукте не было критических багов, которые создают серьезные проблемы его пользователям, имеющиеся проблемы могли быть локализованы и устранены с минимальными затратами ресурсов и продукт мог динамично развиваться, добавляя новые возможности для пользователей. Последние две характеристики может обеспечить только продуманная архитектура, которая формируется в том числе и под влиянием тестов. Но 100% покрытия кода тестами (в т.ч. юнит-тест на каждый самый мелкий класс) — все-таки излишество.

                    Еще добавлю такой момент: если в проекте обнаруживается ошибка, то первым этапом её исправления логично сделать написание теста (любого уровня, лучше всего, конечно, юнит-тест), который её воспроизводит и только потом вносить исправления в код.
                      0
                      Вот вы юнит-тесты тут постоянно пишете, а вы функциональные тесты (вручную и на глаз) делаете?
                        +3
                        Тут смешанны в кучу и unit-testing (отдельная дисциплина направленная на то чтобы проверить что unit ведет себя так как положено) и интреграционнное тестирование (опять — же отдельная дисциплина, которая проверяет что несколько слоев системы работают вместе именно так как задумывалось) и TDD ( ПРОЕКТИРОВАНИЕ через тестирование).
                        Это разные вещи.
                        Абсолютно.
                        В них есть общий момент — тесты, но в первых двух практиках — тесты ПОДТВЕРЖДАЮТ работоспособность кода, в TDD же тесты являются исполняемой СПЕЦИФИКАЦИЕЙ.
                        Сама разработка по Agile подрозумевает что спецификации у вас ровно столько сколько нужно для успешной разработки проекта — не больше и не меньше, TDD тесты это лишь способ писать эту спецификацию на том-же языке что вы пишите код и сделать её исполняемой, все.
                        TDD тест на проверку CRUD функциональности может быть ровно 1 если и так все понятно, он просто говорит вам — для данного типа данных мне нужна CRUD функциональность. В принципе он может быть даже параметризован типом которому нужна CRUD функциональность.
                        ИМХО 90% разговоров о TDD не нужен происходят из-за того что люди очень тяжело понимают что такое TDD.
                        То-же самое происходит например и с функциональным и реактивными подходам и к написанию кода, надо только понять как это должно работать.
                          +2
                          А эти термины по жизни смешаны :)
                          Вот мне, например, не кажется, что TDD-тесты описывают спецификацию. Мне кажется, что они описывают поведение мельчайшего юнита, а спецификации обычно описывают поведение системы в целом. И грубо говоря, чтобы написать TDD-тест на CRUD нужно как минимум навесить абстракций над БД.
                          И сам TDD у меня, например, очень тесно связан с понятием test-first. Из вашего описания я этого не уловил.

                          Терминология здесь очень не устоявшаяся, и в этом, тут я с вами согласен, корень множества споров :) Что под одними и теми же словами люди понимают разные вещи.
                            0
                            Если TDD тесты не описывают спецификацию то это не TDD тесты :)
                            И грубо говоря, чтобы написать TDD-тест на CRUD нужно как минимум навесить абстракций над БД

                            Да не надо этого — пишите прямо в базу — никаких проблем если тесты не становятся зависимыми и вас не напрягает время выполнения тестов. Как только появятся зависимые тесты или начнутся проблемы с временем исполнения — прикручиваете абстракцию.
                            А насчет того как понять что такое TDD — почитайте Кена Бека и читая сравнивайте с тем как вы разрабатываете без TDD.
                            Я как раз за собой заметил что планирование разработки в модели без тестов -> рисование диаграмм классов, описание пользовательских историй итд, полностью заменяется написанием TDD тестов. Вывод следующий из этого — написание TDD тестов и есть написание спецификации. Вообщем-то они и есть ваша спецификация.
                              +3
                              Спецификации они разноуровневые. Есть бизнес-спецификации (http://cukes.info, behat.org) для того чтобы описать поведение вашего приложения целиком с точки зрения бизнеса. И есть юнит-спецификации (http://rspec.info, phpspec.net) для того чтобы описать поведение внутрянки вашего приложения с точки зрения разработчика.

                              Для того чтобы полноценно спланировать и разработать успешное приложение вы не можете себя фиксировать только на бизнес или только на юнит уровне. Вам важно уметь говорить, понимать и описывать оба уровня на всем протяжении разработки.
                                0
                                И это никак не противоречит словам автора предыдущего комментария. Он просто сделал акцент, а вы и многие другие, как мне показалось, опять не поняли. TDD и unit тесты это не одно и то же. юнит тесты это ЧАСТЬ инструментария TDD. Писать сперва спецификацию это значит иметь правила на бумаге и переводить их в код. С точки зрения согласия на применение подход уровень абстракции НЕ ВАЖЕН! Написать такие тесты нужно и для уровней бизнес-логики (" фиксировать только на бизнес") и для контроллеров и для маленький Entity и Services («только на юнит уровне») и для связок бизнес-логики.
                                Юнит -тесты это не TDD, а TDD это не юнит тесты. Простите, за гротеск. TDD — это спецификации, а спецификация переводится на удобный язык — языке программирования и выражается удобной методикой — тестами. И тестами модульными, интеграционными и другими, когда и если это необходимо.

                                П.С. TDD нужно изучить не по статьям а-ля «о, опять про юнит-тесты, модули протестировать нужно, покрыть 100%, да читали сто раз уже», а по книгам и с адептами подхода. И ещё нужно чтобы сознание принимало это, а не сопротивлялось, как почему-то у большинства. А если оно сопротивляется, то наступив на каждый камень начинаешь себе говорить — нафигам не это нужно. И раньше нормально жилось. Ай, да ну их.
                                  +1
                                  Пожалуйста, не надо мне рассказывать что такое «TDD» и какие книги мне надо прочитать чтобы это понять. Ну вот, не надо…
                                    0
                                    Они просто не знают кто ты и сколько много ты сделал для php-сообщества для развития нормальной разработки через тестирование и через bdd ))
                            +1
                            Активно применяю модульное тестирование, но тесты не всегда пишу до кода, и иногда в последствии жалею об этом.
                            Особенно тесты помогают для разработки относительно самостоятельных компонентов с ярко выраженным интерфейсом, т. е. при наличии внятной архитектуры (грамотные тесты сами по себе способствуют такой архитектуре). При разработке недавнего крупного компонента существенно рефакторил его раз десять, при этом тесты практически не менялись. Прогон тестов часто помогал найти ошибки.

                            IMHO, только с тестами можно спать спокойно по ночам после рефакторинга.
                              –3
                              Рефакторинг в принципе невозможен без тестов. Если у вас код не покрыт тестами и вы его все-равно на живую правите, это называется как угодно, но не рефакторинг.
                                +4
                                Десятки лет TDD не был популярен, люди спокойно себе рефакторили и тут внезапно появляетесь вы и говорите, что они занимались фигнёй.
                                  –3
                                  Тестироване существовало задолго до TDD и зародилось где-то на заре времен, когда люди и начали задумываться о таких вещах как «рефакторинг».

                                  И снова, если вы правите реальное работающее приложение без тестирования, вы делаете все что угодно но не рефакторинг. Если при этом вы еще и «спокойны», то вы либо идиот либо образец безответственности.
                                    +1
                                    Выполняю в IDE автоматизированные рефакторинги, сплю спокойно.
                                      0
                                      Ну тут тогда идея в следующем — рефакторинг делаете не вы, а IDE. И вопрос как раз в том, тестируют ли эти автоматизированные рефакторинги разработчики IDE (скорее всего да) и насколько вы им в этом доверяете.

                                      Плюс ко всему, переименование класса, переменной и выпиливание кода в метод — это лишь базовые манипуляции, которые разработчики могут совершать при рефакторинге. Всегда будут более сложные процессы, в котором ваша (надеюсь надежно оттестированная) IDE будет беспомощна как котенок.
                                        0
                                        Ага, это не монтажник стены сверлит, а дрель.
                                        Не землекоп копает, а лопата.
                                  +3
                                  Рефакторинг в принципе невозможен без тестов
                                  Извините, но рефакторинг — «процесс изменения внутренней структуры программы, не затрагивающий её внешнего поведения и имеющий целью облегчить понимание её работы».

                                  Другое дело — как вы можете доказать, что внешнее поведение изменилось. Тесты могут сделать вас более уверенным в этом вопросе, но, как известно, никакие тесты не могут доказать корректность кода, лишь его некорректность.
                                    0
                                    Вы намешали вопрос проверки «правильности» кода и вопрос неизменности его поведения. Да, вы не можете доказать что кусок кода остался «100% верными», потому как даже с тестами вы не были на 100% уверены в его полной правильности. Фишка в том, что это никогда и не было важно. Вас интересовало другое — поведение.

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

                                    Вот в вопросе «правильности поведения» и включается тестирование и рефакторинг — вы покрываете ту самую «спецификацию» тестами (ручными или автоматизированными) и только затем производите изменения в структуре, удостоверившись что они не ломают описанное в спецификации поведение. Это дает 100% уверенность, что ваш код все еще работает как вы и система того ожидаете! Работает ли он таким же образом в неизученных ситуациях? Возможно нет, но это абсолютно не важно :) А вот когда станет важно — вы расширите спецификацию.
                                      +1
                                      Я понял вас, но всё же считаю, что тесты (на текущем уровне их развития) вообще не могут выступать в качестве полной спецификации. Если автор c++ функции утверждает, что она не бросает исключений, легко ли будет проверить это юнит тестами в общем случае?

                                      Тесты всегда покрывают лишь часть всевозможных сценариев взаимодействия, и даже при наличии 100% покрытия тестами нельзя гарантировать, что рефакторинг действительно не изменил внешнего поведения. Они просто делают нас немного уверенней в этом факте. Кроме того, редко кто пишет тесты на ожидания кол-ва потребяемой памяти и ассимптотику алгоритмов, а ведь это, по сути, неотъемлимая часть «внешнего поведения».

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

                                      Но в большинстве практических случаев они позволяют убедиться, что типовые сценарии работы системы всё ещё выполняются как ожидалось, а сотня зелёненьких тестов успокаивает больше, чем эфимерные доказательства корректности.
                                  0
                                  Есть еще ручное тестирование, и тест-кейсы никто не отменял. Тестировщикам, собственно, за это и платят.
                                0
                                Очень много зависит от сути тестов. 100% покрытие — это не совсем корректноя формулировка, без уточнения типа метрики. Если говорить о всех возможных сосотояниях программы, как автомата, 100% покрытия по стандартным метрикам оказываются жалкими долями процента. Даже если речь идет о изолированном модуле.
                                Например, я очень редко вижу в тестах проверку на корректность возникновения эксепшенов, error-кодов или работы функций со скоупом более высокого уровня, конфигурациями и т.п.
                                Ошибки окружения часто тоже оказываются за пределами теста, кросслоки файлов, обвал соединения с БД при некоторых операциях и т.п.
                                Не говоря уже о проблемах многопоточности/распределенки вроде состояния гонки или циклических локов, которые сложноуловимы для тестов и неуловимы со 100% гарантией.

                                Так что некорректно воспринимать TDD как некий ортодоксальный подход, который обеспечивает 100% покрытие. Вопрос, условно говоря, об отстутсвии тестов, присутствии выборочных тестов, и присутствии всех тестов, необходимых с точки зрения определенной метрики, от чего возникают некоторые косвенные плюсы.
                                  +2
                                  Эти разговоры сводятся к

                                  — TDD это хорошо!
                                  — TDD это плохо!
                                  — Да вы не умеете его готовить!
                                  — Кого дураком назвал?
                                  — Нет, ты!

                                  Всё потому, что нет(на самом деле есть, но их мало(кстати те, что мне попадались говорили о том, что ТDD и пресловутые 75% покрытия не дают ничего хорошего — цена слишком высока)) исследований по оценке как TDD влияет на стоимость продукта.

                                  А поскольку нет объективных оценок всё это вкусовщина и холивар, годный в основном для посиделок за пивом. Что хорошо отражено в каментах.
                                    +3
                                    Справедливо. Какого, спрашивается, мы должно верить на слово что «с TDD мои волосы стали мягкими и шелковистыми»?
                                      0
                                      Что отражено и в самом посте тоже :)
                                      Именно поэтому автор и говорит, что обычно не участвует в подобных спорах.
                                      –1
                                      А здесь все более менее правильно, особенно в разделе «Так какие же тесты ты всё-таки пишешь?»

                                      единственно надо вычеркнуть пункт — «Я спускаюсь на уровень юнит-тестов, если вижу сложную логику». Его надо заменить — никогда не спускайтесь на уровень юнит-тестов — займитесь рефакторингом и успокойтесь.

                                      Only users with full accounts can post comments. Log in, please.