Для тех, кто не знаком, с методологией TDD, советую предварительно ознакомится с материалами: отличная статья про суть подхода, моя статья с небольшим практическим примером

Эта статья, в формате небольших тезисов, нацелена на открытие дискуссии на тему "Test Driven Development" – методологии разработки через тестирование. На моем текущем месте работы существует несколько мнений: начиная от полного принятия и стремления(к tdd), как к идеальному инструменту написания рабочего и лаконичного кода, вплоть до полного отвержения: TDD не работает, убивает время разработчиков, увеличивая при этом time-to-market.

К каждому тезису, которые я выделил, я буду оставлять свой комментарий, стараясь быть объективным и не транслировать какую-либо позицию.

Жду ваших комментариев, уверен, что во многом я могу быть не прав.

Рассмотрим основные аргументы ЗА:

TDD ведёт к увеличению процента покрытия кода тестами

Это действительно, факт, и с ним сложно спорить. Методология подразумевает написание тестов ДО написания кода, поэтому процент покрытия такого кода скорее всего будет стремиться к 100%.

TDD дает разработчикам большую уверенность в надежности и функциональности кода

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

TDD избавляет разработчиков от страха внесения изменений в код

В целом тезис – продолжение предыдущего. Может все-таки стоит хоть немного бояться?) Страх иногда штука полезная.

Но оговорка: если твои тесты отменного качества, то несомненно. Если вы можете рассчитывать на тесты на все 100%, то тут даже и думать нечего, бегом рефакторить все налево и направо, и функционала нового побольше!

TDD подталкивает разработчиков к более прагматичным решениям

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

TDD позволяет разработчику быстро получить обратную связь от изменений в коде

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

TDD позволяет разработчику сосредоточиться на поставленной задаче

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

TDD способствует четкому пониманию требований до начала написания кода

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

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

TDD = документированный код

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

Рассмотрим основные аргументы ПРОТИВ:

TDD отнимает время

Действительно, с этим фактом не спорит никто, ни сторонники, ни противники TDD. Разброс прироста к длительности процесса разработки составляет 10-35%, в зависимости от квалификации и опыта команды.

 

источник

TDD не гарантирует качество тестов

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

TDD ставит перед разработчиком нереалистичные цели

Позволю цитату:

«в реальной жизни фичи устроены немного сложнее, чем «функция X должна принять имя и вывести приветствие с этим именем». Часто требования к продукту меняются посреди работы или вы вдруг осознаёте, что фича не может работать согласно требованиям. Или вы изначально всё не так поняли, и вам нужно начинать работу с нуля» - тык

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

Изначальные требования могут быть поняты или сформулированы неверно

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

Тесты необходимо поддерживать при изменении требований

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

TDD зависит от конкретного разработчика

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

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

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

На этом, наверное, все. Вывода не будет, решайте все сами.

А как вы относитесь к TDD?

Источники:

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


  1. segment
    29.08.2024 18:48

    Есть ли кардинальная разница между "написать тест -> спроектировать модуль -> довести модуль -> изменить тест" и "спроектировать модуль -> проверить прототип -> довести модуль до рабочего состояния с учетом всех новых данных -> написать тест"?


    1. YegorP
      29.08.2024 18:48

      Кардинальной - нет. На выходе в обоих случаях возможен один и тот же код, покрытый одними и теми же (плюс-минус) тестами.


    1. youngmyn Автор
      29.08.2024 18:48

      Есть мнение, что тесты, написанные после модуля, будут проходить с большей вероятностью, чем тесты написанные заранее.

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


      1. 9982th
        29.08.2024 18:48
        +5

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


    1. breninsul
      29.08.2024 18:48

      да. Если вы приступаете к работе не зная все особенности, подводные камни, то вы шляпу а не тест напишите.

      А если вы приступаете к работе наперед зная все детали - тут вопрос однотипных задач


  1. powerman
    29.08.2024 18:48
    +2

    То, что ставится на первое место, чему уделяется основное внимание - то и получается качественнее. Практикуя TDD мы получаем качество тестов выше качества кода. Ещё бывает Documentation-driven development - там та же проблема. Вряд ли это то, к чему стоит стремиться.


    1. PrinceKorwin
      29.08.2024 18:48

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


      1. powerman
        29.08.2024 18:48
        +2

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


  1. AnROm
    29.08.2024 18:48

    По-моему TDD многие попробовали лет семь назад и отказались от этого подхода. Возможно потому, что неправильно его применили, не знаю. Бывает такое, что пишут тесты, потом код, и осознают, что тесты нужно переделывать. Получается, в этом случае тесты пишутся дважды. Концепция BDD(Behavior-driven development) выглядит более привлекательной и на практике даёт лучше результат. Она как-то лучше прижилась.


  1. Sadler
    29.08.2024 18:48
    +2

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

    На текущем проекте перебиваюсь valgrind, gcc с максимально выкрученными warning'ами, cppcheck, clang-tidy, фаззингом и автогенерируемыми тестами (chatgpt). Это всё тоже даёт какую-то уверенность, хотя и не непробиваемую.


  1. voidinvader
    29.08.2024 18:48

    Разработка через тестирование это примерно как трусы через голову надевать.


  1. dyadyaSerezha
    29.08.2024 18:48
    +1

    Классчиеский спор остро- и тупо- конечников. С какого конца начинать, зависит от ситуации. А иногда надо и с обоих. А иногда и ещё с другого, третьего, четвёртого конца. Наука умеет много гитик.


  1. 0xC0CAC01A
    29.08.2024 18:48

    А вот почему не пытаются ИИ заставить писать код, прогоняя ответы через TDD? Вот где TDD действительно был бы полезен.


    1. Sadler
      29.08.2024 18:48

      Не знаю, как на уровне индустрии, а я таким занимался ещё на заре chatgpt: заставляем писать код с тестами, затем скармливаем результаты тестов и заставляем править код. Проблема лишь в том, что без программиста всё равно не получается: у LLM очень быстро возникает кризис идей, и нужно подсказывать и направлять, куда развивать дальше. Или иногда он упирается рогом, доказывая, что его код истинно верный, а весь остальной мир ошибается. Проще ли эта возня, чем самому написать? А вот фиг его знает, зависит от задачи. Ну, и контекста всегда катастрофически не хватает, каким бы большим он ни был :D


    1. PrinceKorwin
      29.08.2024 18:48

      Эм. Так только так и делают ведь. Делают запрос, сверяют ответ с эталоном, не понравился - меняют веса на сетке, и продолжают цикл пока не получат удовлетворяющий результат.


  1. i360u
    29.08.2024 18:48
    +6

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


    1. Hardcoin
      29.08.2024 18:48

      Далеко не все разработчики хорошие. 95% скорее средние и практика извне - чуть ли не единственный способ поднять качество проекта.


      1. nronnie
        29.08.2024 18:48
        +2

        Вы забываете, что Хороший Тру-Программист это всегда в первую очередь Творец. Сама идея что он будет писать тесты это кощунство и анафема. Разве Пикассо когда-нибудь писал тесты на свои шедевры? :)))


  1. heart
    29.08.2024 18:48
    +1

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

    • «TDD подталкивает разработчиков к более прагматичным решениям»: В рамках ООП- шного языка программирования TDD тесно сотрудничает с SOLID и со всеми соседними прагдигмами. Требует знаний паттернов проектирования. Да и вообще ставит мысли на место.

    • Рефакторинг рано или поздно нужен всем. Как быть уверенным в том, что после рефакторинга можно вообще что то отдавать в прод? Трата времени на тесты после этого - отсутствие седых волос при внедрении. А если приходят новички? Как до них донести крайние тест-кейсы кроме как заранее не занести их все в UT?

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

    • «Само по себе наличие каких-либо тестов, как я выяснил на практике, не гарантирует ровным счетом ничего.» Просто писать тесты ради тестов? Зачем? Если не понимаете сути то, конечно, лучше их не писать до тех пор пока в этом не разберетесь.

    • «Огромное количество тестов, с которыми сталкивался я, не были в состоянии ответить ни на подобный, ни на прочего рода вопросы.» Ну так это не аргумент, а просто факт что те тесты что вы смотрели были ужасными. Мотивировать их не писать он не может. Только наоборот.

    • «TDD отнимает время»: так в любой сфере. Вопрос: чье время и сколько оно стоит? Из опыта: «затраты» потраченное на тесты потенциально меньше разного рода суммы «затрат» на разных следующих этапах включая внедрение.

    • «Тесты необходимо поддерживать при изменении требований»: это преимущество при хороших тестах и минус при плохих. Не более не менее.

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


    1. powerman
      29.08.2024 18:48

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


  1. Zukomux
    29.08.2024 18:48

    «в реальной жизни фичи устроены немного сложнее, чем «функция X должна принять имя и вывести приветствие с этим именем»
    Отсюда следует два вывода:

    1. Либо аналитика работает настолько криво, что задачу невозможно нормально декомпозировать

    2. Либо разработчик настолько некомпетентный, что просто не умеет в тесты.

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


  1. nronnie
    29.08.2024 18:48

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

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

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


    1. AnROm
      29.08.2024 18:48

      Разговор не про то "писать тесты или нет", а про порядок написания кода и тестов. Часто во время разработки пишется код и тесты одновременно.


      1. nronnie
        29.08.2024 18:48

        Часто во время разработки пишется код и тесты одновременно.

        Я всегда так и делаю. Единственное где я иногда пишу тесты вперед - это на контракт метода (т.е. например на проверку входных параметров). Например, делаю пустой метод, потом пишу тест, что он проверяет нужные параметры на null, потом реализую в начале метода эти проверки. А все остальные тесты уже по ходу реализации остальной части метода. Личный опыт показывает что если при этом еще непрерывно вести контроль покрытия (в случае .NET это, например, "coverlet" и ".NET report generator" для визуального просмотра покрытого и непокрытого кода), то все выходит отлично даже без "ортодоксального" TDD.

        Hidden text


  1. nchursin
    29.08.2024 18:48
    +2

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


    1. nronnie
      29.08.2024 18:48

      Я бы указал на еще один аспект. Юнит-тесты заставляют писать более качественный код. Если код нарушает принципы (в порядке важности) "Dependency inversion", "Single responsibility", и "Interface segregation", либо имеет высокую "cyclomatic complexity" то тесты к нему писать крайне тяжело. Это, кстати, еще одна причина чтобы писать тесты если не вперед, то как минимум сразу. Если это дело откладывать на потом, то очень возможно что потом для тестов придется еще и сам код рефакторить.


  1. kenomimi
    29.08.2024 18:48
    +2

    Разброс прироста к длительности процесса разработки составляет 10-35%, в зависимости от квалификации и опыта команды.

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

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


    1. TerraV
      29.08.2024 18:48

      За 15 лет не видел ни одной компании, которая бы использовала TDD. Ни энтерпрайз, ни стартапы (проекты в России, США, Германии, Великобритании, Чехии). TDD продвигается на индивидуальном уровне TDD энтузиастом (как правило джун-мидл, работающий в своей первой-второй компании). Я не знаю ни одного сеньора, который придерживается этого подхода (не путать с покрытием тестами). TDD катастрофически снижает time-to-market, ухудшая качество кода, в сравнении с подходом code first. Большинство сеньоров+ принимают решение сверху, от архитектуры. Среди них наиболее распространенный подход - Domain Driven Development (DDD).

      P.S. Сеньор с опытом меньше 5 лет это оксюморон.


    1. slava0135
      29.08.2024 18:48
      +2

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

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

      По поводу генерации данных для тестов - есть такой подход как Property Based Testing.


      1. nronnie
        29.08.2024 18:48

        По поводу генерации данных для тестов - есть такой подход как Property Based Testing.

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


        1. powerman
          29.08.2024 18:48

          Оно воспроизводимое. Просто добавляется дополнительная морока куда-то сохранять данные, необходимые для их воспроизведения. Особенно когда тесты выполняются на CI.


          1. nronnie
            29.08.2024 18:48

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


    1. nronnie
      29.08.2024 18:48
      +2

      На порядок время отрастает.

      Если делать с одним и тем же качеством то ровно наоборот.

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

      2. Провальный тест даёт моментальный отклик на кучу дурных ошибок (написали < вместо >). Исправить её прямо сразу это моментально, исправлять её даже всего лишь через час это уже в разы накладней, а если эта ошибка уехала в QA, а то и вообще в production, то разница в трудоемкости её исправления возрастает на порядки (особенно если это еще и не твоя собственная ошибка).


  1. Krasnoarmeec
    29.08.2024 18:48

    "Неправильно ты готовишь TDD, дядя Фёдор" © Простоквашино

    TDD отличная методология программирования. Сначала пишешь заготовку функции или класса, так, чтобы она удовлетворяла требованиям, а потом обвешиваешь её Unit тестами. В процессе обвешивания тестами выясняется, что тут что-то работает не так, там выдаёт не то. Ну и правишь код. В результате, за то же время получаются и функция/класс и Unit тесты к ней/к нему. Никакого замедления разработки. Если впоследствии нашлась ошибка, воспроизводишь её в Unit тесте и исправляешь.


  1. lazy_mathematician
    29.08.2024 18:48

    TDD не гарантирует качество тестов

    Ничто не гарантирует качество тестов. По-моему, это не характеристика именно TDD - это просто так и есть. Вне зависимости.

    Изначальные требования могут быть поняты или сформулированы неверно

    Опять же, это не совсем проблема самого TDD. Да, могут быть ошибки и в самих тестах, и при их копипасте, но тесты несут в себе какую-то философию, а философия формулируется еще ДО TDD (по-хорошему)


    1. nronnie
      29.08.2024 18:48
      +1

      Ничто не гарантирует качество тестов.

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

      Hidden text
        <PropertyGroup>
          <CollectCoverage>true</CollectCoverage>
        </PropertyGroup>
        <-- То что ниже вообще из стандартного шаблона проекта добавляется по умолчанию -->
        <ItemGroup>
          <PackageReference Include="coverlet.msbuild">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
          </PackageReference>
        </ItemGroup>