Uncle BobПеревод статьи "Дяди Боба". Оригинал

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

Сразу скажу, что существует довольно много программистов, которых я уважаю, но которые либо не практикуют TDD, либо не считают, что это влияет на профессионализм. Например, Джим Коплиен (Коуп), Ричард Хикки и Дэвид Хайнемайер Хенссон (DHH).

  • Как я уже говорил много раз ранее, Коуп является моим героем. Его труды в 90-е и позже оказали огромное влияние на мой образ мышления и на мою карьеру.
  • Ричард Хикки является автором Clojure. Мы с ним общались несколько раз в 90-е, когда он ещё программировал на C++. Сейчас Clojure — это мой основной язык, и я стараюсь читать как можно больше трудов Хикки, и слушать как можно больше его докладов.
  • DHH — это автор Rails; фреймворк, который сделал больше для сообщества Ruby, веб-сообщества и всей индустрии программного обеспечения, чем любой другой, который я могу вспомнить. Его влияние на меня и на отрасль в целом сложно переоценить.

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

Так как же я могу верить в то, что TDD ассоциируется с профессионализмом, когда есть профессиональные программисты, мнение которых я высоко ценю и которые не согласны с этой взаимосвязью?

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

Игнац ЗеммельвейсХочу рассказать историю о венгерском враче Игнаце Земмельвейсе, который в 1847 году достиг шестикратного снижения уровня смертности в своём родильном отделении просто заставив врачей мыть руки перед осмотром беременных женщин. Земмельвейс пытался убедить своих коллег из других больниц ввести мытьё рук как обязательную процедуру. Они сопротивлялись — более 60 лет. Причины их сопротивления звучат похоже на аргументы статей, которые утверждают в последнее время, что TDD мертв.

Врачи в то время не мыли руки. Они не видели никаких причин, чтобы мыть руки. Для них чистота и болезни были совершенно не связаны. Для нас, в 21-м веке, в это трудно поверить; но всего 167 лет назад было так же трудно поверить, что мытьё рук не было просто причудой.

Были ли эти врачи непрофессионалами? Конечно нет! Они работали как можно лучше с теми знаниями, которые у них были. Их недоверие к Земмельвейсу теперь кажется необоснованным, потому что мы сейчас знаем, что Земмельвейс был прав. Но сложно судить тех врачей, которые не верили каждому, кто приходил к ним со странными идеями. Прошло много времени и, к сожалению, случилось много страданий и смертей, прежде чем мытьё рук было принято в качестве обязательной медицинской процедуры.

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

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

Но пациенты умирают! Healthcare.gov. Knight Capital. Toyota. Этот список можно продолжать; и количество жертв увеличивается в прогрессии. Наше общество в целом становится все более и более зависимым от программного обеспечения, которое мы производим; и неудачи с каждым годом становятся всё более значительными.

Что-то должно измениться… или мы придём к огромной катастрофе.

Если я прав… Если TDD является столь значительным для индустрии программного обеспечения, как мытьё рук для медицины и убережёт нас от того, что мы все провалимся в тартарары, то Кент Бек будет провозглашен героем, и использование TDD будет нести всю тяжесть критерия профессионализма. После этого те, кто отказывается практиковать TDD, не будут приниматься в ряды профессиональных программистов. И меня не удивит, если в один прекрасный день TDD обяжут использовать по закону.

А теперь вы можете не согласиться со мной о TDD. Это нормально, и я готов обсуждать это. Но я не буду называть вас непрофессионалом, и я даже не буду думать, что вы непрофессионал. Потому что в данный момент в истории TDD не является необходимым критерием профессионализма, которым, я считаю, он когда-то станет.
Используете ли вы TDD?

Проголосовало 329 человек. Воздержалось 68 человек.

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

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


  1. Davert
    21.03.2016 00:05
    +3

    Как же я люблю дядю боба за эти притянутые за уши аналогии.


    1. Rathil
      21.03.2016 01:21
      -4

      Но согласитесь, красиво же!


      1. Davert
        21.03.2016 02:48
        +10

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

        Вот такая примерно логика получается.


        1. how
          21.03.2016 08:49
          -1

          Ну, у него поближе будет:

          • профессия только в начале становления
          • можно сделать результат работы качественней, что показывают некоторые примеры
          • требует дополнительных усилий
          • многие не верят, что эти усилия того стоят (я не о TDD, а о тестах вообще; не верится, что всего 14% не пишут тесты, как в результатах опроса)

          К вашему примеру — если кормить программистов более качественной едой и заставлять их следить за здоровьем, то да — долгосрочно они будут писать код лучше и улыбаться больше. А печеньки — это как раз наоборот — переход с травы на сено. Но программисты — не коровы, более упрямые и верят в то, что только они знают как правильно ))


  1. Meredian
    21.03.2016 08:15
    +1

    У нас тут с коллегой возник разговор, кто оптимист, а кто реалист. С одной стороны он надеялся, что использующих TDD будет побольше, я же сказал «Вау, ЦЕЛЫХ 3 человека всегда используеют TDD!». С другой — утверждает, что большая часть людей, проголосовавших за «Тестирую не по TDD», используют ручное тестирование — под отладчиком прогнали, ручками потыкали, и вперед. Я же искренне верю, что это пункт про автотестирование, просто тесты пишутся после кода, а не до.

    Было бы классно разделять такие пункты на «пишу тесты, но не по TDD» и «Тестирую без автоматизации».


    1. how
      21.03.2016 08:35

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


    1. grossws
      21.03.2016 14:36

      Я проголосовал за "редко", т. к. использую TDD редко. Но при этом в других местах более часто использую написание тестов после кода.

      Стоило разделить опрос на две части: использование/не использование автоматизированного тестирования и частота использования TDD.


  1. arvitaly
    21.03.2016 09:10
    +1

    Несколько моментов:
    1. Рыбы второй свежести не бывает, но китайский ширпотреб одевает весь мир. Если речь идет о человеческих жизнях, требования на порядки выше. А прототипы можно и на коленке писать за 10 минут.
    2. TDD — методология разработки, попытка переложить всю ответственность на этот процесс скучна. TDD тут не причем, просто бывает недостаточно требований со стороны заказчика или их проверки. С тем же успехом можно верить в то, что scrum поможет избежать приемочного тестирования.


    1. VolCh
      21.03.2016 11:36

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


      1. arvitaly
        21.03.2016 17:08
        +1

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


  1. RPG18
    21.03.2016 12:12

    Тесты и есть максимально формализованные требования.

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


    1. VolCh
      21.03.2016 12:46

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


      1. pushtaev
        21.03.2016 15:10

        Если в требованиях нет описания всех возможных случаев, то нет и требования покрывать их тестами и вообще как-то особо обрабатывать.
        Классы эквивалентности меняются с реализацией. https://habrahabr.ru/company/mailru/blog/274771/ — п2.


        1. pushtaev
          21.03.2016 15:21

          Пардон, должно выглядеть так:

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

          Классы эквивалентности меняются с реализацией. https://habrahabr.ru/company/mailru/blog/274771/ — п2.


  1. SirEdvin
    21.03.2016 12:13

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

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


    1. how
      21.03.2016 12:31

      Я при чтении у себя в голове заменял TDD на "тестирование", поэтому аналогия показалась удачной. Но в переводе так не напишешь, всё-таки статья не моя.


      1. Davert
        21.03.2016 21:05

        Почему дядя Боб в своей голове не провел подобный str_replace, статья стала бы гораздо более жизненной и гораздо менее дискусионной


    1. Hokum
      21.03.2016 12:42

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


      1. SirEdvin
        21.03.2016 13:39

        ТDD — это когда по требованиям пишутся сначала тесты, а потом уже код.

        А писать тест одновременно с написанием кода — это не TDD.


        1. AlexNis
          21.03.2016 20:25
          +1

          Вы не совсем правы. Вот что нам говорит о TDD Википедия

          Разработка через тестирование (англ. test-driven development, TDD) — техника разработки программного обеспечения, которая основывается на повторении очень коротких циклов разработки: сначала пишется тест, покрывающий желаемое изменение, затем пишется код, который позволит пройти тест…
          Именно это, на мой взгляд, и имел ввиду предыдущий оратор.


          1. Hokum
            21.03.2016 21:10

            Спасибо, да это я и имел ввиду. Я тут пробежался по книге "Test Driven Development By Example", Kent Beck. Сообственно в ней есть фраза:

            Which test should you pick next from the list? Pick a test that will teach you
            something and that you are confident you can implement.

            Я ее понимаю, что пишется тест для чего-то, что можно сразу реализовать.

            По началу, я рассматривал TDD именно в контексте проверки на соответсвие ТЗ и мне это не нравилось. Но при очередном осмыслении для чего нужны юнит тесты у меня что-то "щелкнуло" и видение TDD изменилось, на то что я описал в коментарие выше.


            1. SirEdvin
              21.03.2016 23:34

              Такой подход отлично подходит для различных простых/математических алгоримтов, которые понятно как писать.

              А что делать с бизнес-логикой, особенно в случае, если вам приходится раз-два менять требования, потому что в них не учитывалось что-то важное и заказчик захотел полностью пересмотреть логику?


              1. Hokum
                22.03.2016 00:23

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

                При таком подходе, если заказчик изменит требования, то какая-то часть системы с тестами останется, ну а та часть что не вписывается в новые требования — выкидывается, вместе с тестами. В этом плане нет никаких отличий были тесты или нет. Ну, а если заказчик в начале хотел "машину", а потом передумал и заказал "подводную лодку", то выкидывать придется все.

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

                Могу описать как решал тестовую задачу, если интересно.
                Необходимо реализовать дедупликацию данных на стороне сервера. Под сервером понимается исполняемый файл/библиотека, которую мог бы использовать веб-сервер. Работа с самими серверами, либо сетевая часть реализации не предполагается в рамках данного задания.
                Функции которые библиотека должна экспортировать были оговорены в задании, но это особой роли не играет.
                Формально получилась библиотека которая сохраняла данные в файлах, а если приложение перезапускалось, то данные терялись. Но изменение логики работы в моем варианте легко реализуемы.


                1. SirEdvin
                  22.03.2016 02:21

                  В начале я придумать архитектуру решения, на этом этапе кода нет.

                  И первое незадокументированное поведение вашего фреймворка/языка/чего-то еще, с которым вы раньше не сталкивалась приведет вас к изменению архитектуры частично или полностью.

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

                  Тогда зачем я их писал?

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


                  1. Hokum
                    22.03.2016 07:48

                    И первое незадокументированное поведение вашего фреймворка/языка/чего-то еще, с которым вы раньше не сталкивалась приведет вас к изменению архитектуры частично или полностью.

                    Возможно мы живем в разных мирах разработки ПО. Я пишу на C++ и пока были трудности только с реализацией какой-то "плюшки" в GUI, так как это потребует много времени от них отказывались, т.е. логику работы это не затрагивало. Правда для GUI тесты я не пишу и в ближайшее время не собираюсь.

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

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

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

                    Ну и по себе, могу сказать, что до какого-то момента я не понимал зачем тесты нужно — только лишняя трата времени. А потом что-то перевернулось и я увидел, что вот тут можно было написать тест так, а здесь так. А вот в этом месте тест очень пригодился… Но этот все личное :) и осознание этого ушло несколько лет и множество попыток начать писать тесты. Возможно и с тестами для UI будет то же самое :)

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


                    1. SirEdvin
                      22.03.2016 10:11
                      +1

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

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

                      Если у меня уже есть готовый чат и мне нужно добавить к нему, скажем, broadcast, то чем готовое решение будет отличатся от прототипа?

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


                      1. Hokum
                        22.03.2016 12:25

                        Если у меня уже есть готовый чат и мне нужно добавить к нему, скажем, broadcast, то чем готовое решение будет отличатся от прототипа?

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

                        а в некоторых случаях вредно (потому что после реализации нужно будет опять переписывать тесты)

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

                        А то что TDD не везде применимо — да, такое возможно, так как TDD применимо лишь там, где можно писать юнит тесты. Именно юнит тесты, а не функциональные или нагрузочные, они не имеют отношения к TDD. Если юнит тесты написать невозможно, то и TDD применить не получится.

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


  1. M-A-X
    21.03.2016 13:35

    И какой смысл было переводить эту статью? :)
    Сравнили, что ТДД так же важно, как и мытье рук. Круто.

    Сам не использую ТДД.
    Так как считаю, что в вебе оно трудно применимо. Функции можно протестировать, а HTML как?
    И зачастую я не знаю, какая архитектура будет, она сто раз может поменятся.

    Потом.
    Во время написания кода я просто смотрю, что возвращает функция с разными параметрами.
    Своего рода тоже ТДД, только не сохраненное в коде, а удаленное.


    1. how
      21.03.2016 13:51
      -1

      а HTML как

      http://symfony.com/doc/current/book/testing.html#your-first-functional-test

      я не знаю, какая архитектура будет, она сто раз может поменятся

      А как вы без тестов узнаете, что при изменении ничего важного не поломали?

      Во время написания кода я просто смотрю, что возвращает функция с разными параметрами.

      На таком Continous Integration не построишь


      1. M-A-X
        21.03.2016 18:38
        +1

        А как вы без тестов узнаете, что при изменении ничего важного не поломали?

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

        Есть другие виды тестов.

        На таком Continous Integration не построишь

        Ну да, это не автоматизировано. Но при желании можно все тесты сохранять.

        На работе кое-какие части покрыты разными тестами.
        В личных проектах тестов как таковых нет.


  1. pushtaev
    21.03.2016 14:38
    +1

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


  1. fogone
    21.03.2016 15:12
    +3

    TDD "зашибись" потому, что врачи моют руки? Я правильно уловил мысль статьи? Не слишком ли они перегружена содержанием?


    1. pushtaev
      21.03.2016 15:14

      Не сомневайтесь, автор заявляет именно это. «TDD работает, потому что врачи моют руки». Надо себе футболку такую сделать.


    1. how
      21.03.2016 16:53
      -1

      По-моему, он говорит, что тестирование недооценено как раньше гигиена.


  1. Mixim333
    21.03.2016 17:50

    На вопрос ответил: «тестирую, но не по TDD».

    Как-то уже писал на Хабре, что стараюсь писать код так, чтобы зависимости были только «интерфейсными», т.е. класс «A», который использует класс «B», у меня понятия не имеет, как «B» подготавливает для него данные. Методы стараюсь писать такого объема, чтобы одним взглядом было понятно, что он делает (не более 70-100 строк) + даю понятное наименование, соответственно, если метод называется «dec» (декремент), а внутри идет: «return a+1;», то явно что-то не то.

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