company_banner

The Pros & Cons of Test-Driven Development



    Разговор вёл IvanPonomarev

    Test-driven development (TDD) — практика, известная уже довольно давно. Разработка через короткие циклы «прежде всего пишем юнит-тест, затем код, потом проводим рефакторинг, повторяем» в ряде компаний принята в качестве стандарта. Но обязательно ли команда, достигшая хорошей степени зрелости процесса разработки, должна принимать TDD? Как и для большинства других практик Extreme Programming, споры по поводу TDD до сих пор не стихают. Оправдываются ли первоначальные затраты на обучение и внедрение TDD? Даёт ли TDD ощутимый выигрыш? Можно ли этот выигрыш измерить? Нет ли случаев, когда TDD проекту вредит? А есть ли ситуации, когда без TDD решить задачу просто невозможно?

    Об этом мы поговорили с разработчиками-экспертами Андреем Солнцевым asolntsev (разработчик из таллинской компании Codeborne, который практикует Extreme Programming и придерживается TDD) и Тагиром Валеевым lany (разработчик в JetBrains, также разрабатывает опенсорсную библиотеку StreamEx и анализатор байткода Java HuntBugs; убежден, что TDD — бесполезная практика). Интересно? Добро пожаловать под кат!

    JUG.ru Group:
    – Добрый день, Андрей, добрый день, Тагир! Таллин, Москва и Новосибирск на связи в Skype. И чтобы читателям стала ясна ваша позиция, первый вопрос короткий: работаете ли вы по TDD?

    solntsevА. Солнцев:
    – Да, мы работаем по test-driven development каждый день. То есть мы пишем тест и код. И я считаю, что это очень полезная и правильная практика. В идеале практически все должны так работать, чтобы быть эффективными.



    valeevТ. Валеев:
    – Я не работаю по test-driven development в том смысле, в котором работает Андрей. Я для новой функциональности совершенно точно сперва пишу код, и потом только тесты к нему. В случае с баг-фиксами – 50/50: если у меня уже есть готовый код и мне пришел report, что он где-то падает, я могу написать тест, воспроизводящий эту проблему, и потом исправить, либо могу сперва исправить, а потом написать тест. Но юнит-тестами у меня код все равно оказывается покрыт. Весь новый код, который я пишу, я обязательно покрываю юнит-тестами.

    JUG.ru Group:
    – Test-driven development известен очень давно, с начала двухтысячных годов. Однако в массе своей разработчики не работают по TDD, по моему ощущению. Почему это происходит? Я задавал этот вопрос Николаю Алименкову xpinjection. Он назвал две причины. Первая – просто не умеют. Их никто не учил, как это делать правильно. И вторая – ошибочно себя считают крутыми архитекторами: «Сейчас я быстренько тут нагенерю из паттернов некую структуру, и она сразу будет работать». Ваше мнение каково? Почему TDD не используется массово?

    А. Солнцев:
    – Я согласен с Николаем. Действительно, так и есть. Не умеют. И тут беда в том, что мало прочитать какое-нибудь руководство или книжку, чтобы уметь: так не работает. Ты все равно, когда начинаешь пробовать, что-то делаешь не так. Я, например, когда впервые прочитал про юнит-тесты и в своем рабочем проекте начал их применять, потом постфактум понял, что я делаю это абсолютно, в корне неправильно. И все, что я сделал за год, можно выкинуть.

    JUG.ru Group:
    – Тагир, а почему вы не работаете по TDD?

    Т. Валеев:
    – Я считаю просто, что это не нужно. То есть я понимаю исключительную важность юнит-тестов, важность хорошего покрытия тестами, но писать тесты вперед кода — это лишняя трата времени и ресурсов. Результат от этого не становится лучше, а времени на это будет затрачено, скорее всего, больше.

    Quick fix vs. Code completion


    А. Солнцев:
    – А почему ты думаешь, что это так? Если все равно ты напишешь ровно столько же тестов, то почему, когда их пишешь первыми, то времени уходит больше?

    Т. Валеев:
    – Как минимум, у тебя не будет code completion в IDE. Если ты пишешь тест, к которому нет кода, то ты не можешь его писать достаточно эффективно.

    А. Солнцев:
    – Ну, это легко решается. Ты же не пишешь тест от и до полностью. Ты пишешь сначала обращение к методу, которого нет. Да, тут у тебя нет code completion, и это хорошо, потому что тогда ты продумаешь, как метод будет называться. Написал обращение к методу — можешь нажать Alt+Enter, в IDE сгенерируется пустой метод. Все, дальше у тебя появится code completion.

    JUG.ru Group:
    – Более того, ты продумаешь не просто как он будет называться, а какие параметры у него будут и как его вызвать наиболее удобным образом.

    Т. Валеев:
    – Я не согласен, что для того, чтобы генерировать код, использование quick-fix-ов проще, чем использование code completion. Но, возможно, это вкусовщина.

    А. Солнцев:
    – Не проще, а примерно одинаково и так, и так. Alt + Enter нажать и в том, и в том случае.

    Т. Валеев:
    – Нет, я думаю, что использование quick-fix-ов сложнее. Я потрачу больше времени. И нажать надо не только Alt + Enter. Например, я вызываю в тесте такой-то метод и, скажем, считаю, что с таким-то строковым и с таким-то числовым параметром должен выдавать такое-то значение. Я написал ассерт. Метода у меня еще нет и класса тоже нет. Таким образом, я должен сперва сказать: «Создай класс, которого еще нет». Выбрать для него пакет. То есть я не просто нажал Alt + Enter, я еще в диалоге заполнил пакет, область видимости класса…

    А. Солнцев:
    – Так все эти вещи ты все равно будешь делать так или иначе, без разницы.

    Т. Валеев:
    – Но я их буду писать в текстовом редакторе, а не в диалоге. Хорошо, с классом разобрались, вот имя метода. В этом случае я нажимаю Alt + Enter и получаю диалог, в котором мне предлагают заполнить название параметров. Так как у меня тест содержит строку и число, IDE у меня не отгадает, как у меня эти параметры должны называться. То есть я должен буду в диалоге вводить их названия. Возможно, она также неправильно угадает их типы, особенно, если я предполагаю, что у меня там сложный тип какой-нибудь. Мне гораздо проще вручную набрать это в редакторе.

    А. Солнцев:
    – Я повторюсь еще раз, это одинаково по времени. То есть ты все равно должен будешь в конечном итоге, если у тебя имя параметра состоит из десяти символов, нажать десять клавиш. В любом случае ТDD или не TDD, это будет совершенно одинаково.

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

    А. Солнцев:
    – Абсолютно без разницы.

    Т. Валеев:
    – На мой взгляд, разница есть.

    Удобство использования vs. эффективность реализации


    JUG.ru Group:
    – Но, коллеги, все-таки за TDD говорит еще вот какой факт. Если сначала ты пытаешься написать тест, тебе сначала придется выполнить setup. Тест – это ведь не только вызов метода, это же еще и setup. При помощи конструктора или фабрики ты собираешься создавать тестируемый объект? В конструктор или в свойства ты будешь передавать какие-то параметры? Где ты эти параметры возьмёшь? И этот момент очень ценный, потому что если ты сразу начинаешь думать с позиции того, как это использовать, то у тебя получается более красивое API.

    А. Солнцев:
    – Абсолютно верно, согласен.

    Т. Валеев:
    – Это очень хорошая тема. И здесь как раз можно поспорить, потому что любое API стоит рассматривать как с точки зрения удобства использования, так и с точки зрения удобства реализации. И более того — эффективности и вообще возможности реализации, потому что может оказаться, что API, удобное с точки зрения использования, неудобно с точки зрения реализации потому, что ему просто не хватает данных. Я, например, пишу IDE. Это то, чем я реально сейчас занимаюсь. Мне, например, нужен новый метод, который мне найдет класс. Я хочу, чтобы по имени “java.util.Collection” я нашел класс и узнал, какие там методы. Я, естественно, думаю: «Что мне нужно? Мне нужно имя с типом String». Хорошо, я пишу, допустим, метод findClass(String name), передаю ему строку “java.util.Collection” и проверяю, что он что-то должен найти. Хороший, удобный тест? Куда проще. Но когда вы начнете реализовывать, вы поймете, что “java.util.Collection” – это непонятно что, потому что у вас, например, в разных модулях или в разных проектах может быть подключен разный JDK и это имя может соответствовать разным классам, в которых разное количество методов. Когда вы будете это реализовывать, вы об этом не сможете не подумать, потому что вы сразу поймете, что есть проблема. Нужна привязка к проекту или к какому-нибудь resolve scope. Соответственно, в вашем удобном способе использования просто не хватает данных, чтобы реализовать результат. Так что написание теста не позволит сделать хороший API. В данном случае мы написали тест, нам было удобно им пользоваться, но API получился плохой.

    А. Солнцев:
    – Ну и что? Не хватает данных, так и есть, но я не вижу тут никакой проблемы. Ты начинаешь писать с теста. Написал тест так, как максимально удобно было бы его использовать. По мере реализации ты видишь, что не хватает каких-то данных, ты возвращаешься на шаг назад и понимаешь: «Ок, так невозможно, нужны новые данные». И ты снова дополняешь тест и думаешь, как наиболее удобным способом передать эти данные в этот метод через конструктор, через injection service, как угодно. Соответственно, дополняешь ты тест, чтобы передать эти данные, и начинаешь дальше реализовывать. Я не вижу проблемы. Все так и есть. Evolutionary design.

    Т. Валеев:
    – В том-то и дело, что ты не видишь проблемы, а я не вижу, зачем писать тест, чтобы потом увидеть, что этот тест бесполезен, что так не заработает? Зачем делать эти шаги вперед, шаги назад, когда можно сразу же написать реализацию? И она будет сделана максимально удобным способом и не придется ни разу переписывать тест. Сразу напишешь реализацию, потом тест. Ни одного шага назад ты не делаешь. Это эффективно.

    А. Солнцев:
    – Эффективно для кого? Мы возвращаемся к самым основам: зачем вообще нужен TDD? Он позволяет заранее продумать, как наиболее удобным способом использовать API. Ты сейчас упомянул в твоем примере, что тебе нужно передать, во-первых, имя класса, плюс нужно каким-то образом передать еще дополнительные параметры, скажем, проект, версию Java – какой-то контекст нужно передать. И как его передать — есть разные варианты. Можно параметром метода. Можно его заинжектить в этот класс, в сервис. Можно инжектить через конструктор. Можно сделать так, чтобы он просто дергал какую-то статическую переменную или статический метод откуда-то. Или грузил бы их из базы, черт побери. То есть разные есть варианты. И когда ты начинаешь в тесте делать setup, как Иван упомянул уже, ты начинаешь в этот момент продумывать: а как наиболее удобным способом передать туда все эти вещи?

    Т. Валеев:
    – Кстати, ты очень интересную вещь сказал: «Статические методы». То есть у меня есть какой-то статический где-то контекст снаружи. И может оказаться, что действительно я напишу этот свой метод findClass, у него будет только один строковой параметр, и я подумаю: «Вообще-то в таком режиме с этим методом тоже можно работать». То есть реализация возможна, если я где-то из глобального контекста смогу достать, какой у меня проект, JDK и так далее. Но со временем может оказаться, что поддержание этого глобального контекста несет больше накладных расходов. Просто в каком-то конкретном месте становится непонятно уже, какой сейчас контекст. Тут мультитредовость всплывает: у меня в одном потоке – один контекст, в другом потоке – другой контекст. То есть начав из соображений сделать как удобно использовать, если я буду слишком сильно сильно этому уделять внимание, может оказаться, что реализация станет вообще неподдерживаемой.

    А. Солнцев:
    – Ты все правильно назвал. Есть такие проблемы. Именно так во многих проектах происходит. И я как раз хочу подчеркнуть, что тест позволит это выявить гораздо раньше, как только ты в своем тесте поймешь, что тебе теперь перед запуском теста нужно каким-то образом инициализировать глобальный контекст. Какую-то статическую переменную делать. Ты сразу увидишь: «Ой, это неудобно. А вдруг я запущу два разных теста и каждый должен инициироваться по-своему, например, параллельно?» И вместо того, чтобы статическую переменную инициировать, как-то лучше и удобнее передать это, допустим, как параметр. Тест это выявит моментально. В том-то и дело.

    JUG.ru Group:
    – Да, из моей практики: если код становится трудным для юнит-тестирования, то чаще всего это происходит именно из-за каких-то глобальных контекстов. Тут-то и звучит сигнал, что API мы спроектировали не очень разумно, даже не с точки зрения удобства, а именно с точки зрения общей правильности. Именно так проблемы и возникают. А если мне надо запускать тест для разных глобальных контекстов, а мне трудно их засетапить? Если, чего доброго, окажется, что юнит-тест вообще зависит от того, какое программное окружение установлено на машине у разработчика? Так и вскрывается проблема, что мы что-то не продумали. Что какие-то вещи, которые мы должны бы были передавать как параметры, например, они, действительно, зависят от глобальных контекстов.

    Т. Валеев:
    – Но с последними репликами я совершенно не спорю! Вы не забывайте, что я абсолютно не против юнит-тестов. Я горячо за юнит-тесты. То есть с последними репликами в контексте того, что тесты и код у нас уже есть, и мы в какой-то момент понимаем, что что-то не так — я абсолютно согласен. Тест и код должны быть. Но у нас разногласия лишь по поводу того, в каком порядке они должны появиться. Позиция Андрея, как я понимаю, в том, что если написать сперва тест, то мы немножко сдвинемся в сторону удобства использования. Моя позиция в том, что если сперва не писать тест, то мы будем сдвинуты в сторону удобства разработки.

    А. Солнцев:
    – Не будем. Не будем сдвинуты. Мы не будем никаким образом сдвинуты в сторону удобства разработки. Это неправда. Написание теста раньше никаким образом не мешает разработке, никаким. Это — false assumption.

    Т. Валеев:
    – Хорошо, у тебя мнение такое, а у меня — другое.

    Дешевле, качественнее — или сразу и то, и другое?


    JUG.ru Group:
    – Перед нашим разговором я думал, что он может перейти в такую фазу, когда каждый будет настаивать на своей субъективной позиции. Но возможен ли тут объективный взгляд, можно ли измерить как-то продуктивность команды, работающей так или по-другому? Я поискал в Google и нашел ссылку на исследования, которые проводились в Microsoft и IBM. Две команды заставляли параллельно работать над одним и тем же проектом, одни работали по TDD, другие работали не по TDD. И вывод получился такой, что трудозатраты по TDD чуть выше, а качество по TDD ощутимо выше получается. То есть при несколько более высоких трудозатратах (это важный момент, они отметили, что трудозатраты на разработку по TDD выше), судя по количеству дефектов, которые пришлось потом исправлять, у TDD — значительно выше качество. То есть, возможно, выбор между TDD и не TDD – это обычный выбор между ценой и качеством.

    А. Солнцев:
    – Я прокомментирую насчет цены и качества. Чуть больше затрат было подсчитано на каком-то первом этапе, когда идет разработка. Но не забывайте, что проект на этом не умирает. Проект продолжает жить. Его надо поддерживать, дальше как-то развивать. И проект, который с худшим качеством, потребует гораздо больше времени и сил на поддержку, больше багов будет, сложнее будет рефакторинг и так далее. И поэтому в конечном итоге через некоторое количество времени он окажется дороже. Поэтому говорить о том, что при TDD будут выше затраты – некорректно. В долгосрочной перспективе они меньше. Это то же самое, что прямо здесь и сейчас купить дешевле пальто. TDD – это дорогое пальто. Прямо здесь и сейчас ты купишь без TDD дешевое пальто: да, сейчас кажется, что это дешево, но через два года ты обнаружишь, что ты каждый год должен покупать новое пальто.

    JUG.ru Group:
    – Если только ты не настолько крут, что умеешь сразу написать прекрасный код и без TDD?

    Т. Валеев:
    – Я, конечно, не пишу сразу прекрасный код. Я пишу сразу более-менее неплохой код, потом пишу юнит-тест. И после этого я этот более-менее неплохой код исправляю в соответствии с упавшими юнит-тестами. И потом отправляю его также на code review и дорабатываю после этого.

    А. Солнцев:
    – Но, тем не менее, получается, что многократные какие-то изменения все равно есть так или иначе?

    Т. Валеев:
    – Многократные изменения в любом случае будут. Какая разница? Напишешь ты тест сперва, после этого ты напишешь код. После этого ты все равно должен запустить эту связку. Ты обнаружишь, что тест не проходит, будешь исправлять. То же самое и у меня, просто я сперва пишу код, потом тест. А потом запускаю, дальше то же самое. Думать, что ты пишешь, все равно придется.

    Удержание задачи: «в голове», на бумаге или в тестах?


    А. Солнцев:
    – Я хотел бы сказать, в чем я вижу основную пользу TDD. Я вижу в этом инструмент, который помогает думать. То есть в качестве альтернативы TDD можно, например, заранее продумать в голове, что ты будешь делать. И я полагаю, что у многих людей именно это получается. Они считают: «Зачем мне писать тесты вперед, я ведь могу и в голове сразу все продумать». Может быть, Тагир как раз так считает. И, может быть, это годится до определенного момента, пока не очень много изменений и когда хорошая свежая голова. Действительно, в голове можно довольно детально все продумать, но начиная с какого-то момента голова больше не справляется. Второй вариант – нарисовать на бумажке заранее, и может быть еще вариант — с кем-то обсудить. То есть это — все варианты, позволяющие заранее продумать, что ты будешь писать, но, на мой взгляд, из них юнит-тест самый лучший, потому что он наиболее близок к коду. Все равно: если ты думаешь в голове и тебе кажется, что все продумал идеально, то когда садишься и начинаешь писать код, выясняются нюансы, о которых ты не подумал. То же самое на бумажке: нарисовал все подробно, начинаешь писать код, выясняются нюансы, о которых ты не подумал.

    Т. Валеев:
    – Я пример с findClass(...) как раз и приводил, чтобы показать, что даже если ты напишешь тест, все равно выясняются нюансы, о которых ты не подумал.

    А. Солнцев:
    – Ты прав: да, нюансы, конечно, в любом случае выясняются, бесспорно. И когда выясняется, что ты о чем-то не подумал — вариант какой? Вернуться и назад дальше в голове продумать заново? Голова просто распухнет и сломается, не сможет так много думать. Вариант вернуться и дальше перерисовать все на бумажке? Это вариант, в принципе, но на мой взгляд, гораздо эффективнее и быстрее вернуться к юнит-тесту и его дописать. Это будет проще, чем перерисовывать все на бумажке. Проще, быстрее, эффективнее. Я хочу провести аналогию: юнит-тест, бумажка и продумывание в голове — это примерно одинаковые инструменты.

    Т. Валеев:
    – Если задача слишком сложна, чтобы ее целиком удержать в голове, то я не думаю, что TDD тут возьмет и магическим образом всех спасет. Задачу нужно разбивать на подзадачи. Решил маленькую подзадачу, протестировал ее — хорошо. Этот кусочек у тебя в голове уже не детализирован, он превратился в работающую абстракцию. Дальше ты работаешь над каким-то новым кирпичиком, который использует предыдущий.

    А. Солнцев:
    – Это тоже метод. Я совершенно с этим не спорю. Да, надо разбивать. Никакого противоречия. Но дело в том, что когда ты в свою голову помещаешь очень много таких маленьких кирпичиков за день, голова гораздо быстрее устает. Вопрос именно в том, где держать этот workspace: в голове, на бумажке или в тесте.

    Ассерты в коде — TDD или нет?


    JUG.ru Group:
    – Я сам далеко не все делаю по TDD, хотя и стремлюсь к этому. Но в моей практике случались какие-то сложные задачи, которые я бы не «раскусил», если бы сразу не решал их при помощи TDD. Есть задачи, решение которых, как я себе представляю, выглядит как взаимодействие большого числа маленьких «шестеренок». Алгоритмически или структурно сложные задачи. Сначала я должен быть абсолютно уверен, что каждая «шестеренка» работает правильно. Потом я пытаюсь запустить эти «шестеренки» в какой-то сложный механизм и добиваться их верного взаимодействия на более высоком уровне. Возможно, это вопрос внутренней убеждённости, но я уверен, что некоторые задачи без TDD никак бы у меня не решились. Но это, возможно, у кого как. У кого-то, может быть, решились бы. Вот вы, Тагир, как решаете наиболее алгоритмически сложные задачи?

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

    А. Солнцев:
    – Так это же то же самое. Ассерты – это то же самое, что тесты.

    JUG.ru Group:
    – Если мы продумываем инварианты и превращаем их в ассерты в коде, то это того же рода работа, что создание ассертов в юнит-тестах. А вы используете ассерты в коде?

    Т. Валеев:
    – Я их использую именно в случае алгоритмически сложных задач. И, как правило, я их все-таки удаляю. Но это зависит от ситуации, от общей политики проекта. Например, в своей библиотеке StreamEx у меня были прямо зубодробительные куски, где нужно было распараллеливать, и чтобы все это было правильно. Был миллион частных случаев, когда в каком порядке thread’ы могут прийти к определенной точке. Я расставлял помимо юнит-тестов ассерты, для тестирования не только внешнего API, но и каких-то внутренних кусков. Все это дебажил вдоль и поперек. Когда нужно было делать релиз, я ассерты убирал. Делал я это по некоторым причинам, в том числе потому, что они могут замедлять, если они разрешены в runtime.

    JUG.ru Group:
    – И ещё они могут вносить side effects.

    Т. Валеев:
    – Ну, это плохие ассерты, если они вносят. А вот производительность они могут просадить. Если человек на весь проект держит “-ea”, то зачем ему внутри моего кода сработавший ассерт? Он все равно с него пользы не получит.

    А. Солнцев:
    – Да, я соглашусь с тем, что ассерты – это ведь на самом деле те же самые юнит-тесты, просто в другом месте написанные. Только с юнит-тестами плюс в том, что их не надо удалять, и они никак не влияют на производительность. А раз ты пишешь ассерты, а потом и юнит-тесты, значит, ты двойную работу делаешь?

    Т. Валеев:
    – В процессе прототипирования иногда проще написать ассерты, потому что когда прототипируешь алгоритм, ассерт можно, например, внутрь цикла поставить, а потом уже понять, как, например, тело этого цикла вынести в отдельный метод и его открыть для юнит-теста.

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

    Т. Валеев:
    – Если речь идет не о внешнем API, а о внутренней структуре алгоритма, то как она разбита на методы – это не очень принципиально, потому что этого никто не видит. Главное, чтобы это было корректно и быстро. И вполне может оказаться, что все те места, где ты хочешь поставить ассерты, если ты по этим точкам разобьешь на методы и поставишь юнит-тесты, то код будет слишком превращен в «лапшу» и его будет труднее воспринять. Я говорю про алгоритмически сложный код. Но тут, возможно, тоже вкусовщина. Кому-то кажется, что сто однострочных методов лучше, а кому-то — что десять десятистрочных тоже хорошо.

    Прописывание интерфейсов до их реализации — TDD или нет?


    Т. Валеев:
    – И ещё я хочу добавить относительно проектирования API. Это, казалось бы, такая штука, для которой TDD действительно очень полезен, потому что если создается новый API, то мы сразу же будем думать о том, как его удобно использовать. И из моей практики, если я действительно в голове не понимаю, как удобно, так или эдак, когда у меня есть варианты, то тут мой подход, наверное, ближе всего приближается к классическому TDD, но все равно я не пишу тест вперед. Я пишу исключительно интерфейсы без реализации, и под эти интерфейсы пишу тесты. И эту связку сперва отлаживаю. То есть я смотрю заранее, будет ли тест удобен или не удобен. Тест должен при этом компилироваться вместе с интерфейсом. Естественно, реализации нет. А после этого уже, когда по интерфейсу мне кажется, что удобно, я могу писать реализацию.

    А. Солнцев:
    – Но это вполне допустимо. Мы тоже так примерно и работаем.

    Т. Валеев:
    – Но опять же, все равно интерфейс появляется вперед, если речь идет именно о порядке написания кода.

    А. Солнцев:
    – Это как раз не принципиальный момент. Это просто означает, нажмешь ты Alt + Enter чуть раньше или чуть позже, но, по-моему, от этого сильно ничего не меняется.

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

    А. Солнцев:
    – Да. Может быть, да.

    Бывает ли так, что TDD наносит вред проекту?


    JUG.ru Group:
    – Действительно, к некоторому консенсусу мы пришли. Главное, конечно, что в ответ на вопрос «Бывает ли на проекте вред от TDD? TDD – зло?», все-таки ответ: «Нет, не зло». А в частности, юнит-тесты сами по себе — это абсолютно точно добро. И, по-моему, все должны уяснить, что нет никакого оправдания тем, кто не использует юнит-тесты, и у кого сильно отстает реализация от покрытия юнит-тестами. Вот это — как раз зло, безусловно.

    А. Солнцев:
    – Да!

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

    Бывает ли такое, что TDD наносит вред проекту? Так как я TDD не использую, у меня нет таких примеров, когда наносит вред. На мой взгляд, TDD – это не зло, но TDD избыточен. Мне кажется, что он не нужен, но он не зло. Если кому-то нравится, я совершенно не против. Даже если я буду работать в проекте с человеком, который использует TDD, мне будет абсолютно не жалко.

    А. Солнцев:
    – Я, конечно, точно скажу, что не избыточен. Из сегодняшней беседы я сделал вывод, что ты делаешь как минимум столько же движений, а может быть больше. Поэтому не могу согласиться с тем, что он избыточен. А ответить на вопрос: «Бывает ли, когда TDD наносит вред?» — мне действительно очень интересно. Я так прямо сходу не могу сказать, что у меня такие явные примеры есть, но я могу себе представить, что наверняка TDD несет вред, когда людей заставляют это делать. Например, когда они не умеют, но им сказали: «Надо». Они делали, делали, написали кучу тестов, а в конце взяли и все равно по старинке все сделали. Тогда действительно получится, что TDD – это будут избыточные трудозатраты.

    Т. Валеев:
    – Но это еще та вещь, которую сложно контролировать, если парное программирование не используется. То есть какой код человек написал, можно контролировать на code review. А что он написал сначала, а что потом, если не стоять у него за спиной — как ты это поймешь?

    А. Солнцев:
    – Это невозможно контролировать, но я скажу по моему опыту, что это всегда видно. Когда ты видишь коммит, ты всегда видишь: если тесты писались после кода, он хуже по качеству, больше зависимостей и так далее. Это видно невооруженным глазом.

    Т. Валеев:
    – Не знаю, тут предметно спорить довольно тяжело, наверное.

    Можно ли объективно измерить выгоду или проигрыш от TDD?


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

    JUG.ru Group:
    – Но эксперимент не чистый. Вы же уже один раз решали задачу в автобусе. Теперь следовало бы сначала решить другую задачу по TDD, а потом без TDD, чтобы сравнить результаты.

    А. Солнцев:
    – Это верное замечание, да. Кстати, хорошая идея. Такой эксперимент тоже можно провести. Вообще насчет идеи сделать эксперименты — она замечательная. Очень хотелось бы сделать, но как? Ведь в одну реку не войти дважды? Как можно?

    Т. Валеев:
    – Даже эти результаты, про которые Иван упомянул, что в Microsoft что-то измеряли и оказалось, что TDD дает более качественный код: это все очень трудно объективно как-то сопоставить. Наверняка это были разные команды, разного уровня люди и так далее. Неизвестно, был ли это проект одинаковой сложности. Очень много переменных.

    А. Солнцев:
    – Да, конечно. Давайте подумаем, может быть, на какой-нибудь конференции замутим или где-нибудь еще? Было бы здорово. Эксперимент.

    JUG.ru Group:
    – Эксперимент в этой области – это довольно сложно. Я думал о том, как можно сравнивать производительность команд разработчиков. Если определять производительность как дробь — отношение результата к трудозатратам — то трудозатраты, как раз, можно измерить. И даже категоризировать: сколько я тратил на написание юнит-теста, написание кода, рефакторинг, баг-фиксинг и так далее. А вот с числителем все сложно. К числителю ты линейку или секундомер не приставишь. Так что вопрос о том, у каких команд в среднем выше производительность — вопрос сложный и решить его, может быть, раз и навсегда не удастся. Но вообще идея на конференции сделать что-нибудь типа лайфкодинга, когда один человек решает по TDD, а другой без TDD небольшую задачу — классная. Может быть, это было бы круто.

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

    А. Солнцев:
    – Да, спасибо. Было приятно поболтать.

    Т. Валеев:
    – Спасибо. Взаимно!



    Если вы интересуетесь тестированием, напоминаем, что 10 декабря в гостинице «Radisson Славянская» пройдет конференция Гейзенбаг (регистрация), на которой можно будет послушать следующие доклады.

    JUG Ru Group
    487,52
    Конференции для программистов и сочувствующих. 18+
    Поделиться публикацией

    Комментарии 197

      +7

      Интересно, многие ли дочитали до конца это словоблудие? :-)

        +2
        Сломался посреди предпоследней темы… но после вашего коммента решил таки добить и добил :)
          +2
          Это не словоблудие, а блудословие!
            +1

            Словно ветку комментов с Хабра прочитал :-D Реквестирую статью к этим комментариям!

              0

              По факту так и было. Мы где-то в каментах с asolntsev поспорили на эту тему, и IvanPonomarev предложил нам пофлудить в формате статьи. Почему-то Иван в статье совсем не упомянут, хотя он по факту сделал основную работу — подготовил вопросы и какие-то исследования нашёл. Мы-то ни как не готовились, просто говорили, что в голову придёт.

              +1

              Я дочитал до конца и могу сказать что я совершенно согласен с Андреем Солнцевым. Мой собственный опыт свидетельствует что TDD first — в долгосрочной перспективе, единственная правильная практика. Я всегда сначала пишу модульные тесты, потом пишу интеграционные тесты, потом пишу тесты для UI. Еще ни разу об этом не пожалел и ни разу у меня не было повода предположить что я потратил время зря.


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


              1. Актуальную документацию по коду. Эта документация никогда не врет (в отличие например от комментариев), потому что она исполняемая.
              2. Объективный и что немаловажно автоматический инструмент контроля качества кода.
              3. Облегчает и поощряет повторное использование кода (потому что пункты 1 и 2)
              0
              И все-таки хочу понять, над чем надо «думать», а над чем нет. Т.е. TDD, как правило, отождествляют в принципе с написанием тестов, но если мы сначала пишем код, а потом покрываем его тестами (как это и делает Тагир), значит, это уже не TDD?
              Должен ли я писать тест, на проверку Content-Type, если он всегда text/html? Кто-то скажет — «А ты не можешь знать, что он всегда будет таким?». Если я один занимаюсь проектом, то я точно могу сказать о невозможности внезапного изменения такого поведения. В ином случае, логично бы написать такой тест, хотя бы ради той эмуляции документации, для других разработчиков.
              Т.е. есть достаточно тривиальный функционал, над которым и думать/«думать» то и не нужно, но по TDD нужно «думать», иначе это будет не TDD?
              Сам подход, вернее в том виде, в котором я его вижу в статьях и в не очень свежих книгах, противопоставляется подходу проектирования архитектуры. Но как раз в случае проектирования сложных процессов обработки данных(которые, чаще всего, генерируются динамически, приходят из сети или селектятся из базы), я тоже не вижу плюсов в написании тестов до кода. И гораздо быстрее набросать прототип(не этот, который из Design Patterns, а обычный прототип), понять, что тут не так, и удалить его. Что мы получили? Мы именно подумали над предполагаемыми подводными камнями, не потратив на это время. И делали это достаточно удобно, с code completion и прочими прелестями, любезно предоставляемыми IDE.
              Кто-то скажет — «да это же не про написание кода, а про подход и философию!». Однако, в каждой статье и книге кровью написано — «сперва тест, потом — код», а это уже не подход, а механизм, что ли!

              TL;DR
              Почему малейшее написание кода до тестов не приемлимо для TDD?
              Ничего не могу сказать против TDD, хочу достичь понимания в этом вопросе.
                +1
                Один из ответов — написав тесты до реализации функционала вы будете уверены, что написанный вами функционал действительно можно покрыть тестами.
                  +2
                  Почти так. Увидеть тест красным до написания кода — это единственная гарантия, что этот тест действительно что-то тестирует.
                    +1

                    Есть другой вариант: написать код, написать намеренно ошибочный тест, проверить, что он действительно падает, исправить тест. Получается этакий Code Driven Testing.

                      0
                      Не пойдёт.
                      Так нет гарантии, что этот новый (изменённый) тест что-то тестирует.
                        +1

                        Есть. Давайте я приведу пример:


                        Код:


                        function inc( a : number ) {
                            return a + 1
                        }

                        Неправильный тест:


                        assertEqual( inc( 2 ) , 4 )

                        Проверяем — свалился и ругается, что функция вернула 3, а должна была 4.


                        Правильный тест:


                        assertEqual( inc( 2 ) , 3 )

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

                          0
                          Да, пожалуй, это даёт гарантию того, что тест — тестирует. И, кстати, я сам похожим образом иногда поступаю, если делаю не по TDD.

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

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

                            Это проблема тестового фреймворка — опеспечить тестируемость любого кода.

                              +1
                              Нет. Вы не правы. Приведу утрированный пример: напишем такой код, который ведёт себя по-разному в зависимости от версии ОС. Скажем, под Windows он рисует синий квадрат, под MacOS — жёлтый квадрат.

                              И пусть часть кода, отвечающая за отрисовку квадратов разного цвета полностью инкапсулирована в private. Как мы протестируем способность системы рисовать разные квадраты, если никак не сможем отделить определение ОС от отрисовки квадрата и смоделировать запуск системы на разных ОС?

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

                              Пример утрированный, но проблемы с тестируемостью — из моей практики — примерно такие.
                                –1

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

                            0
                            vintageIvanPonomarev Нет, вы что, такой способ как раз не даёт никаких гарантий.
                            Допустим, у нас есть уже написанный метод `hypotenuse` (и в нём есть баги).

                            По вашей методологии, мы пишем красный тест:
                            assertEqual(hypotenuse(3, 4) * 0 , 99);
                            


                            Потом меняем константу в тесте:
                            assertEqual(hypotenuse(3, 4) * 0 , 0);
                            


                            и вуаля — по-вашему, мы получаем гарантию, что метод правильный и тест его действительно проверяет!
                              0

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

                                0
                                vintage Погодите, я чего-то не понял. Как тест превратиться из красного в зелёный, если функция работает неправильно?
                                  0

                                  В данном случае тест работает не правильно.

                                    0
                                    А я о чём!

                                    Так вот для того, чтобы убедиться, что тест работает правильно, и нужно увидеть его красным до изменения кода, и зелёным — после.
                                      +1

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

                                        0
                                        Я запутался. Не понимаю, о каком примере идёт речь.
                                        В любом случае, вам не кажется, что диалог зашёл в непродуктивное русло? К чему всё это словоблудие? Мысль же понятна, нет?
                      +1

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

                        +1

                        Мутационное тестирование есть еще такое. В контексте TDD хорошо работает.

                          +1

                          Задачка: обычный полный тест-сьют выполняется 5 часов. Сколько будет выполняться pitest? Ну и, кстати, как помнится, он находит непокрытый код, но не находит бесполезные тесты.

                            0

                            мутационное тестирование обычно гоняют по ночам на CI и только для юнит тестов (если у вас юнит тесты 5 часов гоняются значит они не такие уж и юнит). Это единственный адекватный способ "протестировать тесты".

                              +1

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

                                –1

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

                                  +2

                                  Ну, видимо, вы мало знакомы со статическим анализом. Простой пример:


                                  @Test
                                  public void testSomething() {
                                    new Thread(() -> {
                                      assertEquals(foo(), "bar");
                                    }).start();
                                  }

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


                                  // code
                                  String foo() { ... }
                                  
                                  // test
                                  assertNotNull(foo(), "Return value of foo is null");

                                  Это компилируется, но ничего не тестирует, потому что перепутали порядок аргументов метода assertNotNull (проверяется на null константная строка, а результат метода foo() используется в качестве сообщения при исключении). Это тоже легко отлавливается статическим анализом. Таких примеров много.


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

                                  Про ваш контекст ничего не знаю. Не используйте такие языки. Я вот на Java пишу и горя не знаю.

                                    0
                                    Ассершн выбросит исключение, но оно прилетит не в текущий тред, а в другой

                                    Ок, вы привели кейс который подтверждает вашу точку зрения. А теперь представьте что ваш ассерт… тупо ничего не проверят. Или проверят один конкретный кейс. И метрики покрытия кода показывают что хорошо но тесты ничего по сути не проверяют. И статический анализатор говорит что все хорошо.


                                    Вот такие вещи мутационное тестирование выявляет.


                                    Не используйте такие языки.

                                    Уж простите, на вкус и цвет. Java8 при всей своей красоте не удовлетворяет моих нужд. Ладно бы котлин, он мне нравится. Но система типов в java так себе.

                                      +1
                                      И статический анализатор говорит что все хорошо.

                                      Почему? Как раз я о том, что статический анализ это может выявить, причём сразу и указав на конкретное проблемное место в тесте, а не просто сказав, что такой-то мутант не был убит, а почему — сами разбирайтесь.

                                        0
                                        статический анализ это может выявить

                                        Неужто не может быть ситуаций которые проверить можно только в рантайме?


                                        Вообще весь разговор начался с этой цитаты:


                                        обычный полный тест-сьют выполняется 5 часов.

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

                                          +1
                                          А юнит тесты должны выполняться быстро, в пределах пары минут.

                                          Это без UI-тестов. Возможно, вы не понимаете, что такое действительно большой проект.

                                            0
                                            UI-тесты обычно не являются unit-тестами, а TDD/TLD об юнит-тестах, максимум об интеграционных.
                                              0
                                              Это без UI-тестов.

                                              Это без UI тестов. UI тесты как я уже сказал не имеет смысла проверять мутациями просто потому что они не для этого.

                              +2
                              Поддерживаю lany. Мутационное тестирование — красивая идея, но на практике мне не удалось его применить. Именно потому, что
                              1. Оно очень долгое
                              2. Оно даёт много ложных срабатываний. В них приходится бесконечно копаться вручную. Это прост нереально.
                                0

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

                                  +1

                                  Повторюсь. Мутационное тестирование обычно призвано улучшить именно юнит тесты. На интеграционных например запускать мутационное смысла нет особо.

                              0
                              И в результате тесты пишутся ради тестов.
                              Довольно забавно видеть, как некоторые адепты тестирования, пишут тесты ради тестов, возводя TDD/etc практики в абсолют.
                                +2
                                И в результате тесты пишутся ради тестов.

                                Зависит от разработчиков. У большинства в целом так и выходит. Тесты ради тестов. Причем у людей которые пишут тесты после кода это происходит чаще.

                                +1
                                RaaaGEE Мне кажется, это миф. Это говорят те, кто сами тесты не пишет. Придумали проблему.
                                Я вот лично никогда такого не видел, чтобы тесты писались ради тестов.
                                Я вот вижу ежедневно, как тесты пишутся ради надёжного результата и хорошего дизайна кода.
                                  +1
                                  чтобы тесты писались ради тестов.

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


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


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

                          +4
                          Почему малейшее написание кода до тестов не приемлимо для TDD?

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


                          TDD он как бы больше про проектирование.


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


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


                          Другое дело что зацикливаться не стоит. Инфраструктуру к примеру через TDD не особо продуктивно выходит разрабатывать. Можно через ATDD, собственно идея остается той же. Что-то удобно, что-то не очень. А еще есть property based testing, specification by example, BDD…

                            0
                            А можно подробнее про инфраструктуру? И какие еще элементы не TDD-шатся?
                              +2

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


                              Организуется таким образом пирамида тестирования. Логика покрыта быстрыми юнит тестами, инфраструктура — медленными интеграционными и UI логика — медленными тестами. Причем каждый раз можно не выполнять все имеющиеся тесты а например сначала прогонять все быстрые тесты, затем по каждому уровню critical path и т.д. Да и покрывать всю инфраструктуру тестами дорого выходит.


                              Если у нас проект уровня CRUD, мы можем там использовать различные active record и т.д. — тут как бы все просто. Если у нас проект с логикой посложнее мы уже будем заварачивать модель данных в обертки или вообще использовать data mapper, и тестировать уже объектную модель. А потом уже интеграционными тестами проверять что sql запросы например правильно выполняются. И для этого составляются тест кейсы, и покрывать мы должны то что ломаться может часто а не один раз в год.


                              Не нужно тратить время на автоматизацию того что не будет происходить часто.

                            0
                            Должен ли я писать тест, на проверку Content-Type, если он всегда text/html?


                            В общем и в целом практический (не рассматривая идеального коня в вакууме) ответ на это вопрос совпадает с ответом на вопрос: если бы вы не писали тестов вообще, вы хотя бы раз посмотрели бы глазами на raw ответ, чтобы убедиться, что он действительно text/html.

                            Если вы просто хотите получать пользу от покрытия тестами, не добиваясь 100% покрытия каждой строчки в репозитории проекта как самоцели, то тесты нужно желательно писать на все те вещи, которые вы проверяли бы лично руками и глазами, если бы о тестах ничего не знали. Ведь практически наверняка вы запускаете написанный вами код локально перед тем как отдать его тестировщикам/заказчику или, хотя бы, на конвейер CI/CD, чтобы убедиться что он хотя бы запускается, а не крашится сразу.

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

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

                            TDD не противопоставляется проектированию архитектуры. TDD — это методология проектирования в целом или одного из его этапов. TDD — способ, который предлагается использовать при проектировании относительно небольших изолированных, сводимых к понятию «юнит» (есть ещё BDD и прочие test first DD, TDD — про юнит-тесты), компонентов приложения, вернее даже не самих компонентов, а их контрактов, интерфейсов. Грубо, вы можете нарисовать UML-диаграммы, а можете выразить ту же идею в коде теста. Только диаграммы обычно рисуют глобально, в одной итерации описывая множество методов, поведений, потоков данных и т. п., только потом приступая к реализации, а TDD предлагает это делать маленькими итерациями, описывая каждый юзкейс компонента отдельным тестом и тут же реализуя описанное.

                            Почему малейшее написание кода до тестов не приемлимо для TDD?

                            Приемлемо оно. TDD рекомендует писать тесты до кода, если уж вы собрались покрывать этот код тестами. Если вы не собираетесь документировать и фиксировать своё малейшее написание кода, то не пишите тесты на этот код. Но если собираетесь, то TDD рекомендует писать тесты до кода, а не после.
                              0
                              ну почему неприемлимо, xpm init в любом случае создаёт уже что-то
                                0

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

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

                                Нет, правда:  мне кажется очень странным непробиваемая уверенность в абсолютной необходимости TDD, при том, что эта уверенность основывается только на экспертном мнении, и одном-единственном исследовании. При всем уважении к экспертам, экспертное мнение — это самый низкий уровень доверия из тех, что вообще стоит рассматривать (ниже только агенство ОБС). Одно-единственное исследование это, конечно, лучше, чем ничего, но это совершенно точно не дает основания для такой убежденности.  Мне кажется очень странным (если говорить мягко) что люди с инженерным/естественнонаучным образованием сами не замечают — -- и не говорят — на каком зыбком основании они стоят, делая столь уверенные заявления. 

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

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

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

                                А думают все люди по-разному (ваш КО). Особенно по-разному думают люди, когда они, как специалисты, выросли в разных традициях, на разных практиках. Например, по тексту интервью я могу предположить, что когда Тагир думает о «формулировке задачи» (==что надо сделать) — для него эта формулировка выглядит как интерфейс будущего кода. А для Андрея та же «формулировка задачи» выглядит как тест для еще несуществующего кода. Важна ли эта разница? Мне кажется, нет. Мне кажется, важно, чтобы в мозгу инженера вообще была эта мысль «а что за задачу я сейчас решаю». Каким способом эта мысль в мозге разработчика поселилась — дело десятое. TDD предлагает один из методов вдолбить эту мысль в голову разработчика — но это явно не единственный метод. И уж если разработчик уже как-то сам себя приучил думать о формулировке задачи — то зачем ему теперь test first?

                                … Я даже скажу ещё более еретическую мысль: сформулировать заранее что хочешь получить — иногда может быть вредно, потому что зашоривает.
                                  –2
                                  А покажите мне сколько-либо достоверные исследования, доказывающие реальную эффективность «Не TDD»?
                                  Таких исследований тем более нет!
                                  Похоже, вы просто по умолчанию считаете одну позицию правой, в вторая должна вам доказывать свою правоту. :)

                                  На самом деле никто ведь не пытается вам что-то доказать — ни исследованиями, ни мнением экспертов. По крайней мере, в этой статье. Что мы пытаемся — это привести аргументы обеих сторон, чтобы читатели могли сами разобраться. Пытаемся логически объяснить обе позиции. Для этого и нужен диалог. Эта статья — это диалог, вы заметили? :)

                                    +5
                                    Отсутствие доказательной базы для большинства практик проектирования/разработки — не является извиняющим фактором. Да, никто не может объективно подтвердить, что его метод лучше других. Так что теперь, мы будем соревноваться в субъективной убедительности? Мы инженеры, или теологией занимаемся? Если уверенности нет — мне кажется, надо с этого и начинать: «уверенности нет». А не «в идеале практически все так должны работать, чтобы быть эффективными» — хотя на самом деле про эффективность есть только одно исследование.

                                    Не, я не заметил диалога. Я заметил «я думаю, что действий больше» — «я думаю, что меньше». Мой фаворит «Это неправда». Для меня это не диалог, а попытка навязать свои догмы, в ситуации, когда объективных доказательств нет, а хочется быть правым.
                                      +1
                                      cheremin Знаете, в чём отличие между заинтересованным человеком и троллем?
                                      Заинтересованный человек всегда спросит, а как именно это работает. Засчёт чего. Каким образом решает проблему. А тролль требует доказательств.

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

                                      Если мне кто-нибудь предоставит «доказательства» того, что C++ круче Java, оно мне будет до лампочки. Я захочу узнать: а чем именно лучше? Засчёт чего? Как это работает? Решает ли это «лучше» именно мои реальные проблемы, или какие-то другие абстрактные?
                                        +2
                                        Знаете в чем различие между действительно заинтересованным человеком — и фанатиком? Действительно заинтересованный человек сам себя спрашивает «а вдруг это не работает?», «а вдруг — работает вовсе не это?», «а вдруг — работает не во всех условиях?», «а вдруг — работает не все, а часть просто булшит?», «а вдруг — это просто привычка?». А фанатику такие вопросы задают со стороны — а он от них отмахивается, и продолжает цитировать святое писание.

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

                                        А насчет опытов: лично мне в жизни повезло. Я обучался ставить эксперименты в таком контексте, где не было заранее известных «правильных» ответов. Так что я знаю разницу между мотивацией «сдать физпрактикум на 5 за отведенное время», и искренним любопытством.
                                          0
                                          Успешно разрабатывает, говорите?
                                          Ну да, люди на лошадях тоже успешно добирались из одного города в другой.
                                          А кто сказал, что он без TDD работает эффективнее, чем с TDD?
                                          Меня именно это интересовало. Чтобы это понять, я задал кучу вопросов. И из ответов сделал вывод, что не эффективнее. Фанатизм тут не при чём.
                                            0

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

                                              +2
                                              Дело в том, что адепты TDD пытаются впарить нам не автомобиль, как они думают, а другую лошадь.
                                                +1
                                                s-kozlov Да, вы правы, я думаю, что TDD — это автомобиль.

                                                Только во-первых, я никому ничего не впариваю, а во-вторых, обоснуйте вашу позицию? Обоснуйте, почему TDD — это другая лошадь? Давайте эти ваши доказательства!
                                                  0
                                                  Секундочку, а почему я должен что-то доказывать? Люди десятки лет разрабатывали хорошие программы без TDD. Тут приходят какие-то «эксперты» и говорят, что TDD — это «автомобиль». А вот тут нам надо прояснить, что под этим подразумевается. Я понимаю это как «TDD значительно повышает продуктивность». Кто это должен обосновывать?
                                                    +1
                                                    s-kozlov Вы исходите из предположения, что то, что было раньше — по умолчанию лучше.

                                                    Но ведь это нелогично!
                                                    Вся история человечества показывает, что всё, что лучше, было позже. Скорее старой технологии нужно доказывать, что она всё ещё в строю.
                                                      +2
                                                      Вы всё перевернули с ног на голову.

                                                      Вся история человечества показывает, что всё, что лучше, было позже.


                                                      Вот первых, это не так, а во-вторых, это не означает, что всё, что было позже, лучше.

                                                      Скорее старой технологии нужно доказывать, что она всё ещё в строю.


                                                      Как раз наоборот: новая технология, претендующая на полное замещение старой, должна доказывать, что она лучше. Не такая же, а именно лучше. Ибо бремя доказательства лежит на утверждающем.
                                                        0
                                                        > а во-вторых, это не означает, что всё, что было позже, лучше.
                                                        Всё верно! Я вас проверял. Достойный ответ :)

                                                        Ладно, я позволил себя увести в сторону — в дебри споров о доказательствах.
                                                        На самом у меня не было цели кому-либо что-либо доказать.
                                                        Я пришёл на это интервью-беседу, чтобы побеседовать. Рассказать о том, как я это понимаю и как у меня это работает. Я рассказал. Если какую-то тему недостаточно раскрыл — you are welcome, спрашивайте, отвечу с удовольствием. А доказывать кому-либо что-либо я не хочу и не буду. Это бесполезная трата времени.
                                                          0
                                                          Да, думаю, я прекрасно понял, как у Вас это работает. Я даже верю, что у Вас это работает лучше. Но сам я пытался перейти на такую систему и понял, что не мое это — писать тесты до кода. Всё сводится к особенностям мышления человека, который пишет код. Надеюсь, вы в своей конторе не чешете всех под одну гребенку.
                                                            0
                                                            s-kozlov Всё верно, такой способ мышления кажется противоестественным. Не только вам, но и мне, и всем адептам TDD.

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

                                                            P.S. Чешем, ещё как чешем. Мало TDD, мы ещё и практикуем парное программирование. То есть сидеть одному и шпарить «по-своему» по-любому не получится. Мы об этом рассказываем на собеседовании (да собственно, это и на сайте написано), поэтому те, кто не готов меняться и учиться новому, просто изначально не приходят к нам на работу.
                                                              0
                                                              Ну я бы к вам не пошел. У нас в команде главное — какой код ты выдаешь, а не как ты это делаешь. Если мне начнут указывать, в какой IDE мне работать, в каком порядке писать код и посадят рядом напарника, я обновлю резюме и пойду на LinkedIn, пока его не заблокировали.
                                                                –1
                                                                Понятно, что не пошёл бы. Таких на самом деле большинство. Ну и хорошо, нам ведь нужны только целеустремлённые люди.

                                                                На самом деле у нас свобод гораздо больше, чем в других конторах. У нас нет «архитектора» или «тимлида», который указывает тебе, что делать. Ты можешь выбирать проект, фреймворки, даже языки программирования. И IDE тоже. Это то, о чём большинство людей в IT даже не мечтает.

                                                                … Только их надо выбрать вместе с напарником. И это ведь хорошо, не находите? Иначе слишком легко сделать неправильный выбор в одиночку.
                                                                  +2
                                                                  нам ведь нужны только целеустремлённые люди


                                                                  Т.е. те, кого не устраивают ваши процессы, не целеустремленные? Лихо. А если люди стремятся к другим целям? Ну, например, качественный продукт в срок, поддерживаемый код. Вот дураки, да?

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

                                                                  Грамотное руководство нанимает толковых сотрудников, ставит им задачи и организует комфортные условия работы. А «эффективные менеджеры», начитавшиеся книжек людей, которые зарабатывают на жизнь не программированием, а продвижением своих «методологий», занимаются микроменеджментом, т.е. мешают работать.
                                                                    0
                                                                    Ох, и откуда вы этих стереотипов про эффективных менеджеров набрались?
                                                                    Я анекдоты про них слышал, а в жизни никогда не видел.

                                                                    Ну конечно же, для самое главное — это качественный продукт в срок и поддерживаемый код. Только как этого достичь? У нас собрались единомышленники, которые считают, что TDD (ну и ряд других практик) — самый эффективный способ. Эффективные менеджеры тут не при чём.

                                                                    P.S. Про целеустремлённых людей это был лёгкий троллинг, конечно.
                                                                      +1
                                                                      Я анекдоты про них слышал, а в жизни никогда не видел.

                                                                      Я с такими сталкивался.


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

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

                                                                        +1
                                                                        Согласен. Достаточно лишь продумать.
                                                                        Но в том-то и проблема — как этого добиться? Стабильно. Чтобы не было такого, что сегодня продумал, а завтра не продумал — ну настроение не то, не срослось. Лекарства лучше, чем TDD (+парное программирование), человечеству пока неизвестно.
                                                                          0
                                                                          Лекарства лучше, чем TDD (+парное программирование), человечеству пока неизвестно.


                                                                          Ну разве это не фанатизм, не «эффективный менеджмент»? Очередная волшебная таблетка от всех болезней. Впрочем, вспоминая «Idea однозначно лучше, чем Eclipse», я уже не удивляюсь.
                                                                            0
                                                                            s-kozlov Нет, не фанатизм. Это выводы, основанные на многолетнем опыте. Я ведь много лет работал и так, и так. Точно так же как с IDEA и Eclipse. Там, кстати, я тоже приводил совершенно понятные логичные доводы. При чём тут фанатизм?
                                                                              0
                                                                              Да, фанатизм, потому что вы свой пусть и многолетний опыт почему-то считаете настолько репрезентативным и универсальным, чтобы заниматься микроменеджментом.

                                                                              В пулевой стрельбе есть базовая техника, которой обучают новичков. Начинающим не позволяются вольности, просто говорят «делай так». Но все люди разные, и со временем, когда стрелок на полпути к профессионализму, он «изобретает» собственные приемы и «неправильные» стойки. У мастера может быть совершенно индивидуальная, необычная техника, но он к ней пришел с опытом и она ему подходит больше, чем «правильная». Если он не дурак и стал тренером, ему и в голову не придет учить новичков своей стойке. А учить других мастеров — и подавно. А всё потому, что главный показатель успеха — отверстия в мишени, а не «методологии».

                                                                              А насчет Idea vs Eclipse Вам уже в комментах к той статье говорили, что мир не черно-белый и «однозначно лучше» разбивается о суровую реальность. Например, появляется новый язык, в первую очередь делают плагин Eclipse — в этом случае Idea — блокнот.
                                                                                0
                                                                                в первую очередь делают плагин Eclipse

                                                                                Может к VIM?

                                                                                  0
                                                                                  А я вот усматриваю фанатизм в вашем стремлении во что бы то ни стало обвинить меня в фанатизме. :)

                                                                                  Чем тогда этот «фанатизм» отличается от фанатизма прораба, который заставляет всех ходить по стройке в каске? Каждый, кто ходит по стройке, тоже может сказать, что он мастер, что умеет ходить аккуратно, у него своя техника…

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

                                                                                  P.P.S. Статью про IDEA/Eclipse вы, очевидно, читали очень невнимательно. Там в самом первом предложении сказано: «IDEA лучше как IDE для Java». А вы мне говорите — «когда выходит новый язык». При чём тут новый язык? Я же говорю — для Java. А в конце статьи я писал, что есть целый перечень ситуаций, в которых Eclipse вполне может быть лучше. Внимательнее надо быть, товарищи!
                                                                                    0
                                                                                    Чем тогда этот «фанатизм» отличается от фанатизма прораба, который заставляет всех ходить по стройке в каске? Каждый, кто ходит по стройке, тоже может сказать, что он мастер, что умеет ходить аккуратно, у него своя техника…


                                                                                    Ну вообще-то даже для «мастеров» есть границы дозволенного. Мастер не будет стрелять, когда у мишени находится человек. Нет, конечно, есть отдельные экземпляры, но мы же говорим про «правила, написанные кровью».
                                                                                      0
                                                                                      Отлично сказано!
                                                                                      Значит, TDD — это правила, написанные кровью.
                                                                                        0
                                                                                        Не преувеличивайте. TDD — это всего лишь один из многих способов работы. Причем, как мы выяснили в комментариях, не доказано, что он лучший, к тому же он требует «противоестественного хода мышления».
                                                                                          0
                                                                                          s-kozlov Вы упорно не замечаете сходства. По вашей логике и каска — всего лишь один из инструментов. Нигде нет доказательств, что он лучший. И наконец, ходить в каске — сюрприз-сюрприз — противоестественно!
                                                                                            0
                                                                                            Facepalm. Каска — общепризнанный индустриальный стандарт. Кирпич упадет на голову — поймете. TDD даже близко не стоит по «общепризнанности».
                                                                                              0

                                                                                              Обалдеть! Вы все еще здесь саблями машете!? Давайте хотя бы не на словах воевать, а код писать? Возможно в момент, когда кодовая база у нас перерастет порог в 10 тысяч строк, мы приблизимся к просветлению.

                                                                                                0
                                                                                                Пожалуй, Вы правы, эта ветка перестала быть продуктивной.
                                                                                                0
                                                                                                s-kozlov Перед тем как фейспалмить, вы немножко задумайтесь, к чему я клоню. Я говорю, что ваша логика не работает. Все ваши доводы против TDD точно так же распространяются и на каску. Но вот сюрприз — она общепризнанный стандарт. Значит, ваши доводы не работают. Либо каска стала общепризнанным стандартом без всяких доказательств, а просто на основании многолетнего опыта. Так же как у нас TDD.
                                                                                                  0
                                                                                                  Ну и кому я только что говорил, что каска — общепризнанный стандарт, а TDD — нет, поэтому ничего никуда не распространяется
                                                                                                    0
                                                                                                    Предлагаю закончить этот бесполезный спор.
                                                                                    0

                                                                                    Потому придумали BDD, чтобы вывести идею "сначала разберись а потом делай" на другой уровень.

                                                                              0
                                                                              В общем и в целом при парном программировании (даже при фиксированных парах) код будет более поддерживаемым чем при написании его в одиночку. Да, он может оказаться дороже даже больше чем в два раза (потеря времени коммуникации и принятие совместного решения), но, как минимум, с самого начала его смогут поддерживать два человека, а не один. Это факт, с которым сложно спорить на уровне логики. Попытаетесь?
                                                                            +1
                                                                            У нас в команде главное — какой код ты выдаешь

                                                                            А как ваша команда оценивает какой код ты выдаешь? Каковы формальные критерии?

                                                                              0
                                                                              А как ваша команда оценивает какой код ты выдаешь?


                                                                              Код ревью проводим. Помимо прочего, проверяем наличие тестов.
                                                                            0

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


                                                                            Парное программирование это очень прикольно. У меня уже три года не было парного программирования. :-)

                                                                              +2
                                                                              Это замечательно, что вы нашли свой подход. Плохо, когда менеджмент думает, что он единственно верный, и начинает впаривать всем.
                                                                                0
                                                                                Менеджмент может даже не думать, что какой-то подход единственно верный, нормальный менеджер понимает, что способов организации работы множество (можно даже сказать практически бесконечное множество). Но мало какой менеджер может себе позволить сравнивать на практике какой из них лучше и потому выбирает один на основании своих знаний и убеждений, поскольку понимает, что лучше хоть какая-то организация работы, чем её отсутствие. Несогласные с выбранным способом или адаптируются, или ищут других менеджеров.
                                                                  0
                                                                  я никому ничего не впариваю


                                                                  Цитирую профиль asolntsev:

                                                                  Агрессивно практикую extreme programming и вообще Agile: парное программирование, автоматические тесты, личное общение с клиентом.


                                                                  Где агрессия, там и впаривание.
                                                                    +1
                                                                    Вот вообще никакой связи не уловил.
                                                                    «Практикую» и «впариваю» — это прям совсем разные понятия.

                                                                    P.S. И вообще, вы же должны понимать, что это было сказано так, для красного словца.
                                                                  0

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


                                                                  Например помимо классических тестов есть еще property based тестирование, или надо знать о разных уровнях тестов...


                                                                  Лично я воспринимаю идею TDD ближе к тому что преподносится "адептами BDD" — сначала разбираемся что делать и зачем, а потом уже делаем.

                                                                  +1
                                                                  И из ответов сделал вывод, что не эффективнее.

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


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

                                                                    +1
                                                                    А почему надо делать выводы об исходном объекте?
                                                                    Я как раз хочу, чтобы объект изменился :)
                                                                      +1
                                                                      Включать ли это время в подсчёт эффективности?

                                                                      Обязательно. По крайней мере пока (и если, конечно) навыки TDD не будут считаться дефолтными для разработчика.
                                                                      Кто его оплатит?

                                                                      Тот, кто заинтересован в росте вашей эффективности в долгосрочной перспективе. Тот, кто заинтересован в уменьшении стоимости поддержки вашего кода в долгосрочной перспективе.
                                                                        +2
                                                                        Тот, кто заинтересован в уменьшении стоимости поддержки вашего кода в долгосрочной перспективе.

                                                                        «Тот, кто верит, что это уменьшит стоимость поддержки моего кода в долгосрочной перспективе». — починил.

                                                                  –1
                                                                  А тролль требует доказательств


                                                                  Доказательств требует человек, который не верит на слово. А демагог считает слово «обоснуй» ругательным. И обзывает тролями людей, задающих ему неудобные вопросы.
                                                                    +1
                                                                    Да вы всё передёргиваете. Никто и не просит никому верить на слово. Я вообще не хочу, чтобы кто-нибудь верил. Надо не верить, а понимать. А для этого не доказательства нужны, а объяснения. Вот я и пытаюсь объяснить, как я это вижу, почему я считаю это правильным, засчёт чего, по моему мнению, это работает лучше.
                                                                    0
                                                                    Тьфу блин… я уж подумал было, что один человек сам с собой спорит… а это, оказывается, просто аватарки очень похожи +_+
                                                                  –1
                                                                  А покажите мне сколько-либо достоверные исследования, доказывающие реальную эффективность «Не TDD»?
                                                                  Таких исследований тем более нет!
                                                                  Ну, как минимум, есть исследование, показывающее, что TDD не даёт значимой разницы по сравнению с TLD:
                                                                  http://neverworkintheory.org/2016/10/05/test-driven-development.html
                                                                    0
                                                                    Aingis Тогда тем более! Если нет разницы, чего вы вообще требуете тут? Тут собрались специалисты поделиться личным опытом и обсудить качественные различия, а вы требуете количественных доказательств. Не о том разговор.
                                                                      0
                                                                      Так о том и речь что качественные различия не подтверждаются никакими объективными данными. А если нет разницы, то зачем тратить время на свистелки и сопелки?
                                                                        0
                                                                        Типичная логическая ошибка.
                                                                        Отсутствие доказательств не является доказательством отсутствия.

                                                                        Допустим, у нас нет доказательств преимущества TLD или TDD. Что теперь? Не будем ни о чём говорить? Ну не хотите — не говорите, а есть люди, которые хотят поговорить. Обменяться мнениями, рассказать о своём опыте. Я думаю, что это гораздо полезнее для понимания, чем доказательства.
                                                                          0
                                                                          Присутствие не доказано. Это как широко рекламируемые лекарства с недоказанной эффективностью. Если не жалко денег, то можете попробовать, конечно, но обычно все хотят быстрого результата.

                                                                          К тому же, есть сомнения, что болтовня из сабжа добавила кому-то понимания. Я так, и до середины не дочитал, хотя прочёл в своё время полностью «Войну и мир» и «Властелина колец».
                                                                  +2

                                                                  Хорошо сформулировал, спасибо. Прекрасно дополняет моё мнение. Сейчас вот пишу новый и довольно сложный компонент, в том плане, что надо подумать о куче мелких деталей, сделать внутреннюю модель, которая учтёт все тонкости, и адаптировать эту модель к множеству сценариев. Я мыслю именно с точки зрения реализации: сперва начал писать одно, понял, что возникают проблемы в другом месте, начал переделывать модель. По ходу дела приходят мысли, как реализовать ту или эту деталь, надо срочно эти мысли выражать в коде, пока не потерялись. Пока это прёт, на тесты совершенно нет времени, мысли разбегутся. Да и тестировать странно. Компонент снаружи выглядит просто (на входе кусок кода и на выходе кусок преобразованного кода), публичный интерфейс у него уже фиксирован давно (это инспекция в IDE, которых уже тысячи), а кишки я всё равно постоянно переделываю, писать тест на приватные методы, которые постоянно меняются, мало смысла. Как только получается минимальная полная цепочка, которая может корректно обработать хотя бы один возможный входной кусок кода, я пишу тест, который не тестирует кишки, а тестирует вход и выход всего алгоритма. Потом тестов становится всё больше, но до первого теста прошло несколько часов разработки.

                                                                    +3
                                                                    Значит, тебе приходится удерживать в голове очень большой контекст. Ты боишься «отвлечься» ещё и на тесты, потому что тогда уж точно расплескаешь.

                                                                    Но такой подходит не скалируется! Станет контекст чуть-чуть больше — и расплескаешь. Отвлечёшься не на тесты, а на что-то другое — расплескаешь. По-любому расплескаешь.

                                                                    Поэтому я и говорю про концепцию «workspace». Это то, что ты пытаешься одновременно удерживать в голове, и надо стараться его минимизировать. Смотрел доклады Максима Дорофеева? «Джедайская техника пустого инбокса», «Мыслепатроны» — вот это всё. Удерживать что-то в голове — крайне расточительно. Наш мозг очень плохо умеет это делать и тратит на это много энергии — быстро устаёт. Зато мозг хорошо умеет думать — этим и надо его максимально нагружать. А задачу удержания контекста перекладывать на компьютер. То есть случае TDD — на тесты.

                                                                    P.S. Писать тест на приватные методы — очень даже неплохая мысль. Ничего, что они постоянно меняются. Тесты как раз будут помогать их менять. Приватные методы — это тоже API, пусть невидимый снаружи. Это API для самого важного человека во вселенной — для тебя! :) Он тоже должен быть удобным.
                                                                      0
                                                                      Писать тест на приватные методы — очень даже неплохая мысль. Ничего, что они постоянно меняются. Тесты как раз будут помогать их менять. Приватные методы — это тоже API, пусть невидимый снаружи. Это API для самого важного человека во вселенной — для тебя! :) Он тоже должен быть удобным.

                                                                      Тут возникает два вопроса:

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

                                                                      Второй уже больше теоретический — если хочется протестировать что-то приватное, то это разве не прямой сигнал, что пока выделять этот кусок в отдельный класс со своим апи, которое тоже покрывать тестами?
                                                                        +1
                                                                        Я работаю так: методы, которые надо тестировать, но которые не должны быть открыты как public API, открываю до package-уровня и усаживаю юнит-тест в тот же пакет. Но есть и private. Его не тестирую. Но его обычно и не возникает желания тестировать. Николай xpinjection говорит в этом интервью: если возникло желание тестировать private, то это значит, что твой класс слишком многое на себя взял и пора его дробить.
                                                                          0
                                                                          Спасибо! Сам делаю примерно так же, но периодически натыкаюсь на поборников чтрогой чистоты кода, которые начинают ныть «А почему этот метод не приватный? А почему остальные приватные? А давай как-то так, что бы однообразно было?», и начинаааается… =(
                                                                            0
                                                                            На эту тему есть отличный пост Боба Мартина: http://blog.cleancoder.com/uncle-bob/2015/07/01/TheLittleSingleton.html

                                                                            Вкратце содержание таково:
                                                                            «Tests trump Encapsulation.»
                                                                              0
                                                                              if a test needs access to a private variable the variable shouldn't be private

                                                                              А вот уволоку ка я это себе :)
                                                                                0
                                                                                Но с выставлением публичной переменной только потому, что «мы доверяем своей команде» он явно погорячился.
                                                                                  0
                                                                                  What good is encapsulated code if you can't test it?


                                                                                  Вот это даа, вот это мысль!!! )))Пост отличный. Но он не отменяет необходимости постепенно переходить с использования классической реализации синглетонов на Dependency Injection :-))
                                                                            0
                                                                            Второй вопрос: совершенно верно, это прямой сигнал. Часто так и получается. Это и имеют в виду, когда говорит, что TDD приводит к лучшему дизайну.

                                                                            Первый вопрос: тут есть разные мнения. Одни считают, что тестировать надо только публичные методы. Другие считают, что ради теста не грех и убрать модификатор «private».

                                                                            Я лично считаю, что это зависит от сложности метода. Если он простой (комбинаций особо нет) и его легко проверить через какой-то другой публичный метод, то можно через публичный метод.
                                                                            Если же метод сложный, то для него стоит написать много разных тестов с различными комбинациями входных параметров, то лучше убрать «private» и протестировать этот метод отдельно.
                                                                          0
                                                                          Смотрел доклады Максима Дорофеева? «Джедайская техника пустого инбокса», «Мыслепатроны» — вот это всё. Удерживать что-то в голове — крайне расточительно.

                                                                          Смотрел, Дорофеев прикольный, но у меня нет времени на джедайские техники, надо код пилить :D


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

                                                                            0
                                                                            Ха-ха, это классика :)
                                                                            «Что бы такого съесть, чтобы похудеть».

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

                                                                            Все эти слова про разные мозги, разные обстоятельства, уникальность разработчиков и т.д. люди говорят от нежелания меняться. Вот так.
                                                                          +3

                                                                          Да, я тоже примерно так пишу.


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


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


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


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


                                                                          Для меня, например, сам процесс написания кода часто служит "способом думать". Я легко могу проследить, откуда это: я же физик-теоретик, и думать, пока руки, на автомате, выписывают уравнения, для меня очень естественно. Точно так же я думаю, пока руки пишут код. Тесты же я субъективно воспринимаю как аналог математического доказательства. Мало кто начинает доказывать теоремы строчка за строчкой кванторов, как они финально изложены в учебнике — обычно сначала на ощупь ищешь идею, а потом уже ее строго оформляешь. Вот и получается, что в моем способе мышления test first контринтуитивен, подходит разве что для очень простых сценариев, где "доказательство" сразу очевидно, и не надо думать.


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

                                                                            0
                                                                            cheremin Я думаю, что не только для вас способ мышления «test first» контринтуитивен, а вообще для всех. И для меня тоже! Для нас всех более естественно сразу писать код.

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

                                                                              Это хорошо, что вы привели айкидо в пример: у меня много ассоциаций с телесными навыками возникает, но я их не приводил, потому что полагал, что программисты не поймут.


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


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


                                                                              И это мне очень напоминает изложение TDD.

                                                                                0
                                                                                cheremin Я не понял, какие выводы-то?

                                                                                Я привёл айкидо как пример того, что сначала кажется неестественным, а оказывается очень эффективным. Всем же кажется, что дать в табло эффективнее, чем делать какие-то непонятные выпендрёжные движения? Вот точно так же с TLD vs TDD.

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

                                                                                  А я вам вернул ваш же пример: айкидо может быть эффективным, вот только далеко не все, что вас заставят заучивать — действительно важно для этой эффективности. Часть правил — просто порожняк, или лично вам не подходит — а вы тратите своё время и ресурсы нервной системы. Иначе айкидо было бы самым лучшим видом единоборств — а это, очевидно, не так.


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


                                                                                  Проблема с подходом "200 раз попробовать" в том, что человек ко всему привыкает. И через 200 раз уже не понятно: стало удобнее, или ты просто привык к неудобству? Как говорит старая поговорка — да, медведя можно научить ездить на велосипеде. Но будет ли от того медведю польза и удовольствие?

                                                                                    0
                                                                                    Конечно объясняли. Да, и точку приложения сил, и про действующие мышцы.

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

                                                                                    Да, конечно, у подхода «200 раз попробовать» есть такая особенность.
                                                                                    Но если не попробовать 200 раз, то уж точно ничему не научишься.
                                                                                    Поэтому я не вижу иного варианта, кроме как попробовать по 200 раз оба варианта и уже тогда сравнивать. Скажем, 5 лет изучать айкидо, 5 лет карате — и тогда можно предметно обсуждать. А если у вас нет лишних 10 лет, то найдите человека, который так сделал, и послушайте, что он расскажет о своём опыте.

                                                                                    А у нас тут собрались комментаторы, которые не пробовали по 200 раз ни TLD, ни TDD, и сидят осуждают всех. :)
                                                                                      +1
                                                                                      Вы что-то путаете. Никто из тех, кто занимается спортом, не жалуется на то, что он тратит время и ресурсы нервной системы. Люди приходят туда, чтобы научиться новому, прокачать скилы. И кстати, там никого ничего не заставляют.

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


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


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

                                                                                        0
                                                                                        принцип test first — это прежде всего педагогика. Он должен выстроить у человека определенный способ мышления. Если такой способ уже выстроен — то уже неважно, когда именно писать тесты.

                                                                                        полностью поддерживаю.

                                                                        +2
                                                                        При прочтении бровь изумленно улетела вверх на том моменте, когда начали подсчитывать количество нажатых клавиш. Мне всегда казалось, что этот показатель сродни LOC, который сильно колеблется от программиста к программисту просто из-за разного code style. К примеру, кто-то сразу придумывает говорящее название метода и параметров, а кто-то сначала делает «рыбу» метода обращая внимание только на сигнатуру, и только потом размышляет над названиями — количество нажатий совершенно разное, а результат в общем-то один. Эрго — этот показатель совершенно не показателен, а значит и говорить о нем не стоит.

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

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


                                                                          По-моему, такой вывод в диалоге как раз-таки прозвучал!

                                                                          В общем до самого интересного так и не дошли, к сожалению.


                                                                          Что же, по-Вашему, самое интересное в этой теме?
                                                                            +1
                                                                            По-моему, такой вывод в диалоге как раз-таки прозвучал!

                                                                            Если я правильно помню, то звучало что-то вроде «при TDD налететь на эти грабли гораздо проще», что по-моему не так. Возможно я не внимательно прочитал.

                                                                            Что же, по-Вашему, самое интересное в этой теме?

                                                                            Как это что? :) Конечно же применимость и крайние случаи!

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

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

                                                                            В общем собеседники на мой вкус зависли на совершенно не принципиальных вещах так и не добравшись до мякоти вопроса, но это тольк мое мнение.
                                                                              0
                                                                              IvanPonomarev А я поддержу AstarothAst. Мне тоже кажется, что количество нажатых клавиш сравнивать непродуктивно. Я так и сказал: «Абсолютно без разницы!» :)

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

                                                                              P.S. AstarothAst Насчёт сторонних библиотек — TDD тут тоже хорошо подходит. Ты пишешь тест на свой код, который использует библиотеку X. Запускаешь тест и видишь, как эта библиотека ведёт себя. Это ведь гораздо эффективнее, чем экспериментировать с этой библиотекой на живом коде, где после каждого изменения нужно рестартовать/редеплоить/логиниться/и пр.
                                                                                0
                                                                                Насчёт сторонних библиотек — TDD тут тоже хорошо подходит. Ты пишешь тест на свой код, который использует библиотеку X. Запускаешь тест и видишь, как эта библиотека ведёт себя. Это ведь гораздо эффективнее, чем экспериментировать с этой библиотекой на живом коде, где после каждого изменения нужно рестартовать/редеплоить/логиниться/и пр.

                                                                                Нет, я имел ввиду другое. Я реализую класс Сипулька. По TDD я должен сначала написать на Сипульку тесты — пишу. Красивое апи, возвращающее то, что мне надо. Начинаю реализовывать Сипульку, и в какой-то момент использую библиотеку Сипуление. И тут выясняется, что эта библиотека коварно… ну, не знаю… возвращает объекты ошибок вместо того, что бы кидать исключения, что очень и очень плохо ложится в то апи, которое я заложил в тесте. Переосмысляю, и иду переписывать тест с нуля. Смыть, повторить. Такие натыкания на «неожиданное поведение» могут быть постоянными и болезненными, они будут отбрасывать к началу, и так до тех пор, пока инструмент не будет изучен до того уровня, который позволит писать тесты с учетом подводных камней. Это больно! Это тем более больно, если такой источник не очевидного поведения закопан глубоко в кишках реализации. TLD в данном случае эту боль сократит за счет отсутствия тяни-толкая, проблемы будут решаться по мере возникновения, а тестами покроется «то, что выросло», которое потом придется рефакторить.

                                                                                Отчасти тут спасут «учебные тесты», но этому же тоже нужно учить :)

                                                                                Это ведь гораздо эффективнее, чем экспериментировать с этой библиотекой на живом коде, где после каждого изменения нужно рестартовать/редеплоить/логиниться/и пр.

                                                                                Дядя Боб предлагает использовать для этого учебные тесты. Пробовал — и правда помогает!
                                                                                  0
                                                                                  Да, учебные тесты — это хорошо.
                                                                                  Вообще это концепция «спайка». Когда имеешь дело с неизведанной областью (в т.ч. новой библиотекой), надо с ней сначала поэкспериментировать. Пишешь main метод, пишешь там какой угодно говнокод без тестов, экспериментируешь. Когда ты наконец понимаешь, как работает эта библиотека и как её надо будет использовать, стираешь все свои эксперименты и пишешь код с нуля через TDD.

                                                                                  P.S. Если Сипуление коварно возвращает объекты ошибок, то не ваша ли задача скрыть это от вашего пользователя? Возьмите эти ошибки, превратите в нормальные исключения — пусть ваш API будет хорошим. Почему все остальные должны мучаться?
                                                                                    0
                                                                                    Вообще это концепция «спайка».

                                                                                    Вот до этого в заглавной статье и не дошли :)

                                                                                    Если Сипуление коварно возвращает объекты ошибок, то не ваша ли задача скрыть это от вашего пользователя? Возьмите эти ошибки, превратите в нормальные исключения — пусть ваш API будет хорошим. Почему все остальные должны мучаться?

                                                                                    Я ж сказал «например». Может быть все, что угодно же. А «взять ошибки и превратить в нормальные исключения» — это хорошо, но мало что меняет. Беда остается на своем месте — во время написания теста по TDD ты не знаешь, что у тебя буквально тут же возникнет совершенно левая задача по конвертации ошибок в исключения, без реализации которой ты не сможешь приступить к той задаче, к которой хотел. Готово дело — сроки поплыли… :)
                                                                                      0
                                                                                      Так TDD тут не при чём! Это может случиться в любом проекте с любой методологией.
                                                                                      Да вся наша профессия такая: у тебя буквально постоянно возникают совершенно левые задачи. Мы же инженеры, в этом и состоит наша работа, чтобы как-то с ними управиться и не слишком продолбать сроки. TDD тут не при чём.
                                                                          +3
                                                                          добавьте Андрею в заслуги тоже опен-сорсную библиотеку https://github.com/codeborne/selenide, чтобы регалии звучали одинаково :)
                                                                            0
                                                                            Юхуу! Спасибо!
                                                                              +2

                                                                              Что ж у тебя даже бейджа с coverage нету? Давай меряться! У меня 99%. Beat that! :-D

                                                                                0
                                                                                Да я как-то не заморачивался… Как-то не нужно было. В Jenkins же виден анализ покрытия.
                                                                                Ну ладно, попробую прилепить.
                                                                            0
                                                                            наверно тут как и везде надо знать меру, нет смысла писать тесты на то что не может сломаться, тдд не обязательно должно распостряняться на весь проект, как и покрытие кода не должно быть 100% если это не несёт никакой пользы, поддерживать кучу тестов тоже напряжно, я вообще считаю что нужно писать в основном функциональные тесты, причём их писать можно также до написания самого кода, а юнит тесты только в местах где функциональные не дадут преимущества или тесты писать слишком сложно
                                                                              0
                                                                              Так это опасный путь. Никогда не знаешь заранее, что может сломаться. На самом деле сломаться может всё!
                                                                              И вы забываете, что польза от тестов не только и не столько в том, что они обнаруживают ошибки. Они помогают разрабатывать код с хорошим дизайном, служат как документация и т.д.
                                                                                0
                                                                                Да, сломаться может всё, но это же не повод делать покрытие 100%, верно? Вот у меня, допустим, есть бин с полями и нагенерированными геттерами и сеттерами — на них что, тоже тесты надо писать?

                                                                                ИМХО опасный путь — это бросаться в крайности.
                                                                                  0
                                                                                  В условиях задачи было «поддерживать кучу тестов тоже напряжно», что подразумевает изменение поведения. Если эти сеттеры и геттеры начинают менять поведение, то на них и правда нужно писать тесты и это уже далеко не просто сеттеры и геттеры.
                                                                                +1
                                                                                Если «поддерживать кучу тестов напряжно», то это значит, что они ломаются, иначе бы не напрягало. Если они ломаются, то значит вы изменили старое поведение на новое. А если вы изменили поведение, но не стали отражать этого в тестах, то в следующий раз сломаются уже не тесты (которых нет), а код на продакшене. Зачем это надо?
                                                                                  0
                                                                                  Отлично!

                                                                                  Ещё один типичный аргумент против юнит-тестов (не TDD, а вообще юнит-тестов) такой. При каждом изменении кода ломается огромная куча тестов, и на их починку тратится огромная куча времени. Очевидно, неразумно.

                                                                                  Но это говорит только о том, что эти тесты написаны неправильно. При изменении метода А должны ломаться только тесты, проверяющие этот метод — а их должно быть немного. Ну там, 2-5-10. ну может, 20. Поменять эти тесты — не такая уж большая работа.
                                                                                    +1
                                                                                    Но это говорит только о том, что эти тесты написаны неправильно.

                                                                                    Или код — монолитное сильно связанное говноговно…
                                                                                      0
                                                                                      Да. Как правило, эти две факта соседствуют и удачно дополняют друг друга.
                                                                                      0
                                                                                      И всё-таки поменять эти тесты (а бывает проще выкинуть) — это работа, причем нудная. Всё очень плохо, когда проект еще молодой и «живой», мы пробуем разные варианты.
                                                                                • НЛО прилетело и опубликовало эту надпись здесь
                                                                                    0
                                                                                    Задавались же таким вопросом. Судя по всему, нельзя тут получить конкретику в процентах с большой долей уверенности. Слишком много степеней свободы при постановке эксперимента.
                                                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                                                      0
                                                                                      Так это задача того, кто будет реализовывать интерфейс.
                                                                                      У каждой реализации будут свои тесты — они ведь по-разному реализовывают.
                                                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                                                          0
                                                                                          В смысле? А зачем вам тогда разные реализации интерфейса, если они одинаковые?

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

                                                                                            Простой пример: есть интерфейс формата файла, он умеет сериализовывать объект и десериализовывать. Все реализации форматов должны после сериализации и десериализации получать эквивалентный объект.

                                                                                              0

                                                                                              Вы бы пример кода привели что-ли? Какие сигнатуры методов у интерфейса подразумеваются? Такой пойдет?


                                                                                              public interface JsonSerializer<T>
                                                                                              {
                                                                                                  string ToJsonString(T item);
                                                                                                  T ToObject(string json);
                                                                                              }
                                                                                                0
                                                                                                public interface Formatter<T>
                                                                                                {
                                                                                                    string toString( T item );
                                                                                                    T fromString( string stream );
                                                                                                }
                                                                                                  0
                                                                                                  // Начинаем разработку как и полагается с написания модульного теста.
                                                                                                  // Здесь набрасываем предположения о том, как будем тестировать
                                                                                                  // конкретный Formatter<Person> с конкретными проверочными данными.
                                                                                                  [TestClass]
                                                                                                  public class DefaultFormatterTest
                                                                                                  {        
                                                                                                      // Я предполагаю что в простейшем случае, набор тестовых
                                                                                                      // вызовов можно обернуть в команду.
                                                                                                      private Command formatterTestSuit;
                                                                                                  
                                                                                                      [TestInitialize]
                                                                                                      public void setUp()
                                                                                                      {
                                                                                                          // Это эталонная строка, из которой реализация 
                                                                                                          // Formatter<Person> должна будет извлечь 
                                                                                                          // экземпляр модели Person. И наоборот, из эталонного
                                                                                                          // экземпляра модели Person должна будет получаться 
                                                                                                          // такая строка при выполнении обратной операции.
                                                                                                          string text = "{\"FirstName\":\"Jack\",\"LastName\":\"Sparrow\",\"BirthDate\":\"1635/25/11\"}";
                                                                                                  
                                                                                                          // Это эталонная модель, из которой реализация
                                                                                                          // Formatter<Person> должна будет сгенерировать
                                                                                                          // строку, идентичную эталонной. И наоборот, из эталонной
                                                                                                          // строки должна будет получиться такая модель при 
                                                                                                          // выполнении обратного преобразования.
                                                                                                          Person model = 
                                                                                                              new Person{ 
                                                                                                                  FirstName = "Jack", 
                                                                                                                  LastName = "Sparrow", 
                                                                                                                  BirthDate = new DateTime(1635, 11, 25)
                                                                                                              };
                                                                                                  
                                                                                                          // Для удобства, эталонные данные упаковываем в отдельный контейнер.
                                                                                                          FormatterSampleData<Person> sampleData = 
                                                                                                              new FormatterSampleData<Person>{
                                                                                                                  Text = text,
                                                                                                                  Model = model
                                                                                                              };
                                                                                                  
                                                                                                          // Инстанциируем экземпляр нашей, несуществующей еще фабрики
                                                                                                          // для производства строк из моделей, и моделей из строк.
                                                                                                          Formatter<Person> formatter = new DefaultFormatter<Person>();
                                                                                                  
                                                                                                          // И инициализируем наш несуществующий еще тестовый набор,
                                                                                                          // передавая ему в конструктор собственно экземпляр 
                                                                                                          // тестируемого Formatter<Person> и эталонные данные.
                                                                                                          this.formatterTestSuit = 
                                                                                                              new FormatterTestSuit<Person>(formatter, sampleData);
                                                                                                      }
                                                                                                  
                                                                                                      [TestMethod]
                                                                                                      public void Execute()
                                                                                                      {
                                                                                                          // Тестовый метод будет всего один. Он нужен лишь
                                                                                                          // для того, чтобы запустить комплект проверок,
                                                                                                          // упакованных внутрь команды.
                                                                                                          this.formatterTestSuit.Execute();
                                                                                                      }
                                                                                                  }
                                                                                                  
                                                                                                  // Переходим к реализации комплекта собственно модульных тестов,
                                                                                                  // которые я решил упаковать в команду.
                                                                                                  public class FormatterTestSuit<T>:Command
                                                                                                  {
                                                                                                      private Formatter<T> formatter;
                                                                                                      private FormatterSampleData<T> data;
                                                                                                  
                                                                                                      public FormatterTestSuit(Formatter<T> formatter, FormatterSampleData<T> data)
                                                                                                      {
                                                                                                          if (formatter == null)
                                                                                                              throw new ArgumentNullException("formatter");
                                                                                                  
                                                                                                          if (data == null)
                                                                                                              throw new ArgumentNullException("data");
                                                                                                  
                                                                                                          this.formatter = formatter;
                                                                                                          this.data = data;
                                                                                                      }
                                                                                                  
                                                                                                      // Первый тестовый вызов проверяет корректность работы 
                                                                                                      // метода toString любой реализации Formatter<T>
                                                                                                      public void should_make_model()
                                                                                                      {
                                                                                                          T model = this.formatter.fromString(this.data.Text);
                                                                                                  
                                                                                                          Assert.IsNotNull(model);
                                                                                                          Assert.AreEqual(this.data.Model, model);
                                                                                                      }
                                                                                                  
                                                                                                      // Второй тестовый вызов проверяет правильность
                                                                                                      // строки, генерируемой методом toString.
                                                                                                      public void should_compose_string()
                                                                                                      {
                                                                                                          string text = this.formatter.toString(this.data.Model);
                                                                                                  
                                                                                                          Assert.AreEqual(this.data.Text, text);
                                                                                                      }
                                                                                                  
                                                                                                      // Оба тестовых метода, для удобства оборачиваем методом Execute.
                                                                                                      public void Execute()
                                                                                                      {
                                                                                                          this.should_make_model();
                                                                                                          this.should_compose_string();
                                                                                                      }
                                                                                                  }

                                                                                                  Ну а теперь можно собственно реализовывать остальное, чтобы можно было код откомпилировать и выполнить первый неудачный запуск тестов. Для сокращения объема сообщения я не стал проверять в FormatterTestSuit все граничные варианты, предположив тестирование только основного функционала, в идеальных условиях.

                                                                                                    0

                                                                                                    Боже, к чему такие сложности? Нужно всего лишь проверить, что после сериализации и десереализации объект получается эквивалентным. Причём рботать это должно независимо от формата, а не только лишь с json.

                                                                                                      0

                                                                                                      Где вы увидели ограничение форматов в виде "только лишь с json"?


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

                                                                                                        0

                                                                                                        А я вас как раз json-ом не ограничиваю. Json встречается в "запускателе" тестов. Сами тесты в отдельном классе, который в принципе понятия не имеет о том, чего ему могут "скормить" в качестве затравки. Хотите разбирать XML объявляйте новый "запускатель" тестового набора.


                                                                                                        [TestClass]
                                                                                                        public class DefaultXmlFormatterTest
                                                                                                        {        
                                                                                                            private Command formatterTestSuit;
                                                                                                        
                                                                                                            [TestInitialize]
                                                                                                            public void setUp()
                                                                                                            {
                                                                                                                string text = "<person><FirstName>"Jack"</FirstName><LastName>"Sparrow"</LastName></person>";
                                                                                                        
                                                                                                                Person model = 
                                                                                                                    new Person{ 
                                                                                                                        FirstName = "Jack", 
                                                                                                                        LastName = "Sparrow"
                                                                                                                    };
                                                                                                        
                                                                                                                FormatterSampleData<Person> sampleData = 
                                                                                                                    new FormatterSampleData<Person>{
                                                                                                                        Text = text,
                                                                                                                        Model = model
                                                                                                                    };
                                                                                                        
                                                                                                                Formatter<Person> formatter = new DefaultXmlFormatter<Person>();
                                                                                                        
                                                                                                                this.formatterTestSuit = 
                                                                                                                    new FormatterTestSuit<Person>(formatter, sampleData);
                                                                                                            }
                                                                                                        
                                                                                                            [TestMethod]
                                                                                                            public void Execute()
                                                                                                            {
                                                                                                                this.formatterTestSuit.Execute();
                                                                                                            }
                                                                                                        }
                                                                                                          0

                                                                                                          Сравните с кодом на D:


                                                                                                          import std.traits;
                                                                                                          import std.stdio;
                                                                                                          import std.outbuffer;
                                                                                                          import std.conv;
                                                                                                          import std.json;
                                                                                                          
                                                                                                          interface Packer( Target , Source )
                                                                                                          {
                                                                                                              Target pack( Source src );
                                                                                                              Source unpack( Target src );
                                                                                                          }
                                                                                                          
                                                                                                          mixin template PackerTest( alias Packer )
                                                                                                          {
                                                                                                              /// Packing booleans
                                                                                                              unittest
                                                                                                              {
                                                                                                                  auto packer = new Packer!bool;
                                                                                                                  assert( packer.unpack( packer.pack( true ) ) == true );
                                                                                                                  assert( packer.unpack( packer.pack( false ) ) == false );
                                                                                                              }
                                                                                                          
                                                                                                              /// Packing integers
                                                                                                              unittest
                                                                                                              {
                                                                                                                  auto packer = new Packer!long;
                                                                                                                  assert( packer.unpack( packer.pack( 0 ) ) == 0 );
                                                                                                                  assert( packer.unpack( packer.pack( 123 ) ) == 123 );
                                                                                                                  assert( packer.unpack( packer.pack( long.max ) ) == long.max );
                                                                                                              }
                                                                                                          }
                                                                                                          
                                                                                                          mixin PackerTest!StringPacker;
                                                                                                          class StringPacker( Source ) : Packer!( string , Source )
                                                                                                          {
                                                                                                              string pack( Source source )
                                                                                                              {
                                                                                                                  return source.to!string;
                                                                                                              }
                                                                                                              Source unpack( string target )
                                                                                                              {
                                                                                                                  return target.to!Source;
                                                                                                              }
                                                                                                          }
                                                                                                          
                                                                                                          mixin PackerTest!JsonPacker;
                                                                                                          class JsonPacker( Source ) : Packer!( JSONValue , Source )
                                                                                                          {
                                                                                                              JSONValue pack( Source source )
                                                                                                              {
                                                                                                                  return source.JSONValue;
                                                                                                              }
                                                                                                              Source unpack( JSONValue target )
                                                                                                              {
                                                                                                                  static if( is( Source : bool ) ) {
                                                                                                                      switch( target.type )
                                                                                                                      {
                                                                                                                          case JSON_TYPE.TRUE : return true;
                                                                                                                          case JSON_TYPE.FALSE : return false;
                                                                                                                          default: throw new Exception( "Non boolean: " ~ target.type );
                                                                                                                      }
                                                                                                                  } else if( is( Source : long ) ) {
                                                                                                                      return target.integer;
                                                                                                                  }
                                                                                                              }
                                                                                                          }
                                                                                                          
                                                                                                          void main()
                                                                                                          {
                                                                                                          }
                                                                                                            0

                                                                                                            И?

                                                                                                              0

                                                                                                              Всё куда проще.

                                                                                                                0

                                                                                                                :-) Не буду с вами спорить.

                                                                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                  0

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

                                                                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                                                                      0
                                                                                                      Я не очень понял, в чём состоит ваше решение. Более того, я не понял, в чём проблема.
                                                                                                    0
                                                                                                    Как-то всё это выдумано. Нет такой проблемы в реальной жизни.

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


                                                                                                      Почему не помогают, если мы сразу можем проверить соответствует ли новая реализация спецификации?
                                                                                              0

                                                                                              Как один из вариантов:


                                                                                              public interface Command
                                                                                              {
                                                                                                  void Execute();
                                                                                              }
                                                                                              
                                                                                              public class DataContextImplementationTestBase:Command
                                                                                              
                                                                                                  private DataContext context;
                                                                                              
                                                                                                  public DataContextImplementationTestBase(DataContext context)
                                                                                                  {
                                                                                                      if(context == null)
                                                                                                          throw new ArgumentNullException("context");
                                                                                                      this.context = context;
                                                                                                  }
                                                                                              
                                                                                                  public void DataContext_should_return_value()
                                                                                                  {
                                                                                                      IEnumerable<User> users = context.Get<IEnumerable<User>>();
                                                                                              
                                                                                                      Assert.IsNotNull(users);
                                                                                                      Assert.AreEqual(0, users.Count());
                                                                                                  }
                                                                                              
                                                                                                  public void DataContext_should_return_null()
                                                                                                  {
                                                                                                      Assert.IsNull(context.Get<IEnumerable<Agreements>>());
                                                                                                  }
                                                                                              
                                                                                                  public void Execute()
                                                                                                  {
                                                                                                      this.DataContext_should_return_value();
                                                                                                      this.DataContext_should_return_null();
                                                                                                  }
                                                                                              }
                                                                                              
                                                                                              [TestClass]
                                                                                              public class DefaultDataContextTest:DataContextImplementationTestBase
                                                                                              {
                                                                                                  public DefaultDataContextTest()
                                                                                                      :(new DefaultDataContext()){}
                                                                                              
                                                                                                  [TestMethod]
                                                                                                  public void Run_tests()
                                                                                                  {
                                                                                                      this.Execute();
                                                                                                  }
                                                                                              }
                                                                                              +3

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


                                                                                              Это как при обучении игре на гитаре сказать — "я уже сто(пусть даже тысячу) раз сыграл гамму, а все еще не могу сыграть Hotel California как Joe Walsh, не буду больше играть гаммы, это пустая трата времени".

                                                                                                0
                                                                                                Подскажите лучше какую-нибудь литературу по написанию тестов. Прочитал Кента Бэка, понял, что за методология такая. Но как научиться писать сами тесты? Есть сферический метод с параметрами и возвращаемым значением. Должен ли я проверять все граничные условия? Перебирать Декартово произведение аргументов? Как будет выглядеть идеальный тест?
                                                                                                Заголовок спойлера
                                                                                                А статья – какая-то переписка Энгельса с Каутским.
                                                                                                  0
                                                                                                  Идеальные тесты (как и все остальное идеальное), в реальном программировании, к счастью, не требуются, так что все эти теоретические изыски можно оставить. Проверили на каких-то входных значениях и ошибочных, если повезет, и уже и релизить пора. Все равно заранее все не предусмотреть, например, если на вход приходит какое-то сообщение из другой системы, то вообще непонятно что там за варианты могут быть, о чем-то и не догадаешься. Все самые интересные тесты будут написаны по результатам разбора логов ночного падения системы.

                                                                                                  Неплохая книжка есть https://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052
                                                                                                  Еще некоторые приемы описаны в https://www.amazon.com/xUnit-Test-Patterns-Refactoring-Code/dp/0131495054, но она довольно объемная и кажется, не без воды (читал давно).
                                                                                                    0
                                                                                                    Мне нравится книга «Clean Code» — она не совсем о тестах, но про тесты там хорошо расписано.
                                                                                                    Ещё люди хвалят «xUnit Test Patterns: Refactoring Test Code».

                                                                                                    Нет, Декартово произведение не нужно. Нужен разумный набор вариантов, покрывающий все «классы» параметров. Есть даже такое понятие как «классы эквивалентности».
                                                                                                      +1

                                                                                                      Мне нравятся как написаны Принципы, паттерны и методики гибкой разработки Роберта Мартина, там правда код на C#, но в данном случае особенности синтаксических конструкций не очень существенны.


                                                                                                      Еще можно читать перевод уже упомянутого здесь Clean code, того же Мартина, в русском варианте Чистый код. Там уже Java.


                                                                                                      Там на самом деле много читать не придется, там надо много практиковать прочитанное. Программирование — это навыки, а не знания.

                                                                                                        +1
                                                                                                        В случае TDD как раз специально учиться не нужно, нужно просто следовать правилу: «причиной любого изменения в коде должны быть либо упавшие тесты, либо решение о рефакторинге без добавления новой функциональности».

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

                                                                                                          Интересно, а улучшение производительности по вашему в какую категорию попадает?

                                                                                                            +1

                                                                                                            В "решение о рефакторинге без добавления новой функциональности"...

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

                                                                                                          Бесполезный спор на тему "кому, что кажется правильным". Это не теорема, доказательств нет ни у одной из сторон. Каждый участник подразумевает разные исходные и разные результаты.


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