вторник, 19 мая 2015 г.

TDD есть опиум для народа


Ваш так называемый TDD действует как опий: он завлекает и приглушает боли вместо того, чтобы придать силы.
(сказал бы Новалис, если бы жил сейчас)

Один пацан писал все через TDD, говорил что нравится, удобно, читабельно.
Потом его в дурку забрали, конечно.



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




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

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


TDD - наглядная агитация 

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

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

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


Тесты как документация 

Отличная мысль, нет правда, когда нет нормальной документации, разбирать функционал по тестам, особенно если они правильно написаны довольно легко. Но загвоздка в том, что по крайней мере unit тесты подразумевают множество тестов на каждую функцию. Мы проверяем "основной" путь, потом нам нужно покрыть каждую ветку кода, потом граничные условия, потом передачу некорректных данных функции…, в итоге разобраться в хитросплетении десятков тестов на одну бедную функцию - легче застрелиться. И никакие названия тестов тут не спасут, даже на чистом русском, даже если с юмором. Когда код тестов в десятки раз больше размеров тестируемого кода - легче прочитать и понять код.

Тест вообще говорит только о том, что именно в этом конкретном случае, с этими конкретными данными код ведет себя вот так. Все! Что бы узнать как работает код в общем случае - нужно, как ни странно, посмотреть на код.

А тем кто все же сомневается и считает тесты хорошей альтернативой документации, рекомендую попробовать поизучать boost, по их тестам. Для не любителей плюсов, судя по отзывам, хорошей альтернативой будет spring framework.

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


100% покрытие

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

В языках старой школы, так нелюбимыми хипстерами, с этим все гораздо лучше, компилятор предоставляет определенные гарантии и не даст вызывать допустим только что удаленный метод. Тут к месту будет упомянуть недавно вышедшего в релиз Rust, где в компилятор встроен весьма продвинутый анализатор кода, в том числе и актуального нынче многопоточного кода. "И увидел Он, что это хорошо" - действительно заставлять человека заниматься тем, что может делать тупая железяка - глупо, в добавок еще и дорого. Этим должны заниматься компиляторы, статические анализаторы кода, санитайзеры и другие инструменты анализирующие код в динамике (типа valgrind и т.п.) Еще раз обращу внимание - речь сейчас идет не о тестировании логики, а о тестировании того, от чего защищаются 100% покрытием - опечаток, ошибок памяти, многопоточности и т.д.

Хотя конечно у web-разработчиков с языком для фронтенда и выбора то особо нет, но они похоже этого не замечают и не страдают. Мало того они стремятся перенести свой слабо типизированный, динамичный язык, со специфичной системой наследования, созданный за неделю на коленке у Айка - java script на server side. Это наверное, что бы в 2 раза больше TDD в мире было. (прим. автора: извините, я не хотел обидеть поклонников nodejs, просто вырвалось, да и сразу - вот этот пост, кстати самый заплюсованный в категории nodejs на хабре, переводил не я)

Да, и что бы 2 раза не вставать замечу, когда тесты покрывают код целиком и полностью, не оставляя живого места, любая попытка изменения функции или ее интерфейса приводит к дикому баттхёрту. И никакая самая продвинутая IDE не поможет менять вслед за кодом тесты легко и просто. А что бы предупредить аргументы о том, что написанный по TDD код никогда переписывать не придется, перейдем к разбору следующего постулата.


С TDD сразу и навсегда 

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

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

Да что там через месяц, два - зачастую на следующий день придя на работу, когда за ночь все в голове утряслось, появилось более глубокое понимание задачи, ты смотришь на свой код и понимаешь - в мусорку. Избранный подход не работает, надо переписать пока не поздно, оставить то, что уже написано - путь в никуда… а тут бах! на тебе! сотни тестов на любое изменение умирают краснея. Совесть взывает одуматься, ведь ты и так вчера пол дня писал не функциональность, а тесты, а тут берешь и все ломаешь. Менеджер, которого только месяц назад уломали на обязательный TDD для всей команды, недобро покачивает головой. Оправдания в стиле: неожиданные обстоятельства, вот только сейчас нюанс выяснился, невозможно было предусмотреть, никого конечно не трогают…

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

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


Юнит тесты хорошо, а другие плохо 

Объясняют это обычно тем, что юнит тесты быстрые, как… (сами закончите в меру своей испорченности) и что важно запускаются даже на вершине Эвереста. И блин не поспоришь ведь, действительно быстрые (про Эверест правда не проверял). Только вот ведь в чем беда, тестируют они только небольшие и несвязанные, строительные кубики кода - там где ошибок в большинстве случаев и нет. Ошибки, по опыту, начинаются после того, как из этих кубиков начинаешь строить что-то большее. Вот тут и вылазят самые коварные, трудноуловимые баги с многопоточностью, с сетью, с БД, т.п. которые пока были замокированы вели себя естественно совсем ни так, как в реальности.

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

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

TDD нам думать и жить помогает 

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

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

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

А вот без повторения слова ТДД вдолбить это в головы программистов никак нельзя? Вот прям если попытаться написать тест после кода, то в классе обязательно в каждом методе будет обращение к БД (причем через драйвер реализованный прям в этом же классе). Все переменные будут глобальными (ну у продвинутых небожителей возможно они будут завернуты в синглтоны). А логи будут писаться http запросами на захардкоженный внутри кода адрес, что ли?

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


Итоги кратко

  • Тесты важные важны, тесты разные нужны.
  • Когда писать тесты - личное дело каждого и на качестве это особо не сказывается, главное не забивать совсем.
  • Тесты спорный суррогат документации.
  • 100% покрытие или близкое к нему только мешает.
  • Тесты далеко не единственный способ обеспечения качества кода, еще есть мозг, а так же статические и динамические средства проверки.

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

  1. Красавчик, я прослезился :) Больше не буду теперь тебя TDD заставлять делать ))))))))))

    ОтветитьУдалить
  2. "Тесты далеко не единственный способ обеспечения качества кода, еще есть мозг"
    Да, да и еще сто раз да - использование мозга и здравого смысла на мой взгляд позволяют избежать гораздо большего количества проблем, чем поведение вроде "мы работаем по эджайл и поэтому у нас идеальный продукт" или "у нас есть юнит тесты, поэтому приложение прекрасно будет работать всегда" )

    ОтветитьУдалить
  3. "Тесты далеко не единственный способ обеспечения качества кода, еще есть мозг"
    Спасибо, кэп! Спасибо, что напомнили.

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

    Конечно, всю работу делает наш мозг. Беда только в том, что наш мозг медленный и быстро устаёт. Даже самые одарённые из нас не способны активно думать слишком долго, запоминать слишком много и постоянно делают ошибки. Мозгу надо помогать. Надо делать какие-то упражнения или как-то перекладывать часть нагрузки на что-то другое. Я знаю только два способа помогать мозгу: это спорт и TDD. Ну, ещё Шерлок Холмс использовал для этой цели кокаин. Знаете ещё способы - делитесь, с удовольствием попробую. Но не надо говорить мне: "просто включите мозг".

    ОтветитьУдалить
  4. Ну и давайте пройдусь подробнее по пунктам.
    Многие вещи вы говорите правильно, но есть нюансы, из которых складываются неверные выводы.

    1. Вы немножко искажаете слова.
    Цитирую: "Заменять документацию только тестами - глупо. Избегать функциональных тестов, только потому что они медленнее и сложнее - неправильно." и ещё: "написанный по TDD код никогда переписывать не придется". И это: "с TDD они познают дзен и вот прям щас с первого раза напишут правильно"
    А никто и не говорит, что только тестами. Никто и не говорит, что функциональных тестов надо избегать. И уж точно никто и никогда не говорил, что код не придётся переписывать. Скорее наоборот, тесты помогают код смелее рефакторить.

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


    3. Вы сами себе противоречите.
    В одном предложении вы говорите, что пишите сложный код (менеджер потоков, инжекты какие-то), а в следующем говорите, что код-то в основном тривиальный. По-вашему, тесты и для сложного кода не написать, и для простого не написать.

    P.S. Тесты для многопоточного кода, конечно, тоже можно писать. Более того, там они ещё более необходимы, т.к. отлавливать ошибки в таких программах на порядки сложнее.

    4. "Но нужно понимать, что бенефиты дает детальная проработка задачи, но никак не написанный заранее тест."
    Спасибо, кэп! Конечно, важна детальная проработка. А тест помогает детально проработать. Не надо противопоставлять. TDD - это не замена мозга. TDD - это способ РАЗРАБОТКИ, т.е. инструмент для той самой детальной проработки. TDD - не замена мозга, TDD - помощник мозга.

    5. Вы неправильно поняли
    "разработка чего-то нового требует эволюционного проектирования и соответственно регулярной переделки кода."
    Совершенно верно! И как раз для эволюционного проектирования тесты и нужны! И регулярная переделка с тестами идёт как по маслу - не боишься сломать.

    6. "Юнит тесты хорошо, а другие плохо"
    Никто не говорит, что другие тесты - это плохо. Другие тесты тоже нужны, но их должно быть на порядок меньше. Другими словами, если что-то можно протестировать юнит-тестом, выбери юнит-тесты. И только если никак (все эти сеть и БД), бери следующий вид тестов.

    7. "Что бы узнать как работает код в общем случае - нужно, как ни странно, посмотреть на код."
    Совершенно согласен! Никто и не говорит, что замена документации - это только тесты. Конечно же, код плюс тесты.

    8. Вы преуменьшаете
    Ваши "исключения" ("иногда по коду не понятно...") - это как вовсе никакие не исключения, а самая что ни на есть ежедневная действительность. И именно в этих случаев тесты и выполняют роль документации. Всё так и есть.

    9. Троллинг динамических языков (особенно JavaScript) - это пять баллов.
    Вот тут я полностью согласен. Действительно, там приходится писать тесты для того, с чем мог бы справиться компилятор. Я их тоже недолюбливаю.

    10. "любой более менее серьезный рефакторинг приведет к переписыванию тысяч тестов."
    Это говорит явное непонимание сути юнит-тестов.
    С какой стати придётся менять тысячи тестов? Если меняется бизнес-логика метода, должны поменяться только те тесты, которые его тестируют - а их немного, ну там, один-пять-десять.

    11. "Вот только легко тестируемый код не дает никаких гарантий качества получившегося приложения."
    Универсальный троллинг. Позволяет затроллить всё что угодно, потому что ничто не свете не может дать никаких гарантий. А документация даёт гарантии? Нафиг документацию. А ваш мозг даёт гарантию? Нафиг мозг.

    Тестируемый код даёт не 100% гарантию, но большую степень уверенности. Тестируемый код даёт хороший дизайн (низкая связанность и всё такое), который позволяет легко менять код в будущем.

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

      Поэтому предлагаю пример задачи: найти среднее арифметическое чисел типа float в векторе. (Я сразу оговорюсь, что сейчас говорю о полноценных приложениях, а не о библиотеках - там правила немного другие)
      Попробуем накидать по ней тесты:
      - Тест с вектором в котором штук 10 случайных чисел
      - Тест с пустым вектором
      - Тест с очень большим вектором
      - Тест с экстремально маленькими числами (ну что бы элементарно ноль в результате не получался)
      - Тест с экстремально большими числами (вдруг переполнение вылезет)
      - Тест с большими и маленькими числами (для увеличения точности имеет смысл отсортировать вектор и складывать от маленьких к большим)
      - Минимум 3 теста на спец. значения NaN, INFINITY и -INFINITY

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

      Ну допустим мы решили что точности float нам не хватает и переделываем функцию на работу с более точным типом - double. Сколько тестов при этом придется менять? Да все, пусть минимально, но абсолютно все, минимум типы чисел поломаются в тестах. Вот про это я и говорю при упоминании "тысяч сломанных тестов". Что ж это за помогающие тесты при рефакторинге, когда вслед за изменением функции я их тут же все переписываю?

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

      Я не к тому, что именно в этой задаче нужно использовать только интеграционные тесты. Я лишь пытаюсь показать, что выстраивать пирамиду типа "100 unit тестов, 10 интеграционный, 1 проверка живого тестировщика" - странная затея, зачастую дешевле и легче проверить код только интеграционными тестами н-р.

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

      Удалить


    2. С TDD: тут приходится в первую очередь - не СОЗДАВАТЬ, а ЛОМАТЬ, только одно это уже выбивает из колеи. Мало того мы ломаем ненаписанный, абстрактный алгоритм - и этим мы помогаем мозгу? Далее - какая-то фрагментарность мышления - вот на вашем видео с примером разработки "виселицы" через TDD, меня разрывался мозг: вот тут мы какую-то заглушку в код добавили, там недоделали, там забыли, при каждом новом тесте - добавлялись какие-то случайные куски кода, а какой-то цельной картины не появлялось. Так и хотелось сказать: все хватит! остановитесь! сейчас мы сядем, подумаем, и все эти недоделки нормально проработаем и закодим. Кстати, та часть, где забыли учесть случай с удвоенной буквой в слове - ИМХО именно из-за такого фрагментарного подхода к задаче - не пришел в голову тест - в коде ошибка, если сесть и помедитировать над кодом - это было очевидно. Вообще такой индуктивный подход - от частных случаев в тестах к общему алгоритму, уж извините больше свойственен нашей прекрасной половине человечества (http://efamily.ru/articles/109/2975), лично мне гораздо проще дедуктивный способ - в общем реализованном алгоритме вычислять и проверять частности в тестах.

      И напоследок: хоть и обещал не придираться к фразам, но не могу не пройтись по "Тестируемый код даёт хороший дизайн", ибо убил в свое время кучу времени на разбор постов Сергей Теплякова: http://sergeyteplyakov.blogspot.ru/2013/04/vs.html, где он делает вывод: "Помните о том, что тестируемость далеко не всегда приведет вас к хорошему дизайну, в то время как хороший дизайн не только обеспечит тестируемость, но даст еще и массу других преимуществ" С чем я согласен.

      Удалить
    3. Пример со складыванием чисел некорректен.
      Потому, что не бывает такого REST-сервиса, который только складывает два числа. Бывает REST-сервис, который выполняет гораздо более сложную задачу - ну там, рассчитывает какой-нибудь график выплат кредита, скажем. Для которого в т.ч. нужно сложить два числа. И использовать ещё несколько других функций. Интеграционный тест на этот REST-сервис тоже безусловно нужен. Но покрыть им все называнные комбинации - сложно, если не невозможно. Это ж надо такой график подобрать, в котором попались бы все эти граничные значения. И если тест упал, ты никогда не знаешь, упал он из-за баги в функции сложения или в какой-то другой функции.

      Ну а если кто-то решит поменять тип данных с float на double, ему по-любому придётся менять очень много кода. Даже без тестов. Придётся менять все вызовы этой функции. По-любому большая работа. Тут плюс-минус девять вызовов погоды не делает. Тем более что такие вещи обычно делает за нас IDE.

      Удалить
    4. Кажется моя основная проблема в том, что для используемых мною языков - IDE еще не достигла такого уровня, что бы делать за меня такие вещи, а значит TDD пока не для меня.

      Удалить
    5. Боюсь, в корне неверный вывод. Если язык и IDE ещё не достигли такого уровня, значит, компилятор и IDE меньше защищают вас от ошибок - значит, наоборот, вам нужно больше тестов.

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

      Удалить
    6. Какой-то замкнутый круг получается, язык/IDE меня плохо защищает, мне нужно больше тестов, но язык/IDE не позволяют легко и просто менять много тестов вслед за кодом - эффективность падает. Хоть на java переходи.

      Удалить
    7. :) Точно-точно.
      А как иначе! Плохой инструмент - всё плохо. Конечно замкнутый круг.

      Удалить
    8. Я всё хожу и думаю вот об этой реплике: "Далее - какая-то фрагментарность мышления - вот на вашем видео с примером разработки "виселицы" через TDD, меня разрывался мозг: вот тут мы какую-то заглушку в код добавили, там недоделали, там забыли, при каждом новом тесте - добавлялись какие-то случайные куски кода, а какой-то цельной картины не появлялось. Так и хотелось сказать: все хватит! остановитесь! сейчас мы сядем, подумаем, и все эти недоделки нормально проработаем и закодим. Кстати, та часть, где забыли учесть случай с удвоенной буквой в слове - ИМХО именно из-за такого фрагментарного подхода к задаче - не пришел в голову тест - в коде ошибка, если сесть и помедитировать над кодом - это было очевидно."

      Честно говоря, это заставило меня задуматься.
      Но я пришёл к выводу, что тут вовсе нет никакой проблемы с TDD. Забытая удвоенная буква - это на самом деле забытое ТРЕБОВАНИЕ к задаче. Я мог бы его точно так же забыть и без TDD. В обычной жизни требования как-то зафиксированы, поэтому так просто не забываются. TDD начинается там, где требование ясно и пора начинать кодить.

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

      И совершенно точно сложные мысли проще сформулировать в виде простых примеров.
      Вот пример из сегодняшней беседы с заказчиком.
      - Должны ли карточные транзакции, отосланные, но ещё не подтверждённые из эквайринга, включаться в сумму "доступно" на обзоре?
      - Ээээ... ХЗ... Дай-те ка подумать... Мммм... Ну, если у меня на счету 100 рублей, а я заплатил два раз карточкой по 20, то в "доступно" должно быть 60.

      Удалить
  5. Я не девелопер, продукшн код не пишу, но мне интересно наблюдать за "за и против ТДД и юнит тестов". Тут есть наброс от опытного, но достаточного провокативного чела, с опиранием на научные исследования этой темы. Первая часть http://www.rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf и вторая http://www.rbcs-us.com/documents/Segue.pdf (во второй части больше по делу, в первой больше воды). Там вот как раз главный упор на думание и проработку задачи. Бонусом примеры как ТДД и настрой на тестабильность кода убивает архитектуру. Местами очень спорно, но как точку зрения стоит изучить. Я отчасти наслышан про проблемы упомянутого там "nordic client" т.к работал в нём.

    Мне кажется радикальные утверждения приведённые во втором абзаце этого поста (Да я согласен, что тесты...) и "100% покрытие" именно радикальные. Всяко есть люди продающие ТДД именно так, но я уверен, что настоящие лидеры и практиканты ТДД этого не делают и не агитируют.

    Как ни странно я согласен с выводами как Владимира так и Андрея :) С одним "но", если интеграционные тесты начинают бегать дольше полу часа - работать с продуктом становится очень неудобно, это неоспоримый плюс тест-пирамиды. НО и это можно было бы решать не только тестами, но и разбиением продукта на более мелкие компоненты. Истина где-то по середине и всегда есть несколько решений :)

    ОтветитьУдалить
    Ответы
    1. Только я не против юнит тестов, когда они к месту - очень даже помогают, я против категоричных утверждений - вот это хорошо, а по другому - не правильно.
      А в остальном - золотые слова, согласен серебрянной пули не существует, есть плюсы и минусы у каждой технологии. В каких-то случаях больше подходит один подход, в каких-то другой. И возможно, если бы я вживую пообщался с кем-нибудь из основателей движения TDD, я бы круто изменил свою точку зрения. Но к сожалению (или к счастью :) ) пока что мой опыт и задачи, которые приходилось решать - плохо ложатся на философию TDD, ну или как говорят его сторонники - я просто не умею его готовить.

      Удалить