Check Levi9 best QA positions to Backbase team!
×Закрыть

Сначала тест, а потом реализация

1. Вы пишете тесты до или после реализации? Если до, то:
2. Почему это важно? Если одним предложением. Моя версия — вынуждает обдумывать реализацию.

👍НравитсяПонравилось0
В избранноеВ избранном0
LinkedIn

Похожие топики

Лучшие комментарии пропустить

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

И вот когда первая версия поднялась, когда интерфейсы связи с другим кодом и сервисами худо-бедно ответили и не наругались на несоответствие типизации — уже идёт проверка и первый рефакторинг. Удаляются 100500 закоменченых огрызков кода, десятки переменных которые в итоге не понадобились, тестовые переменные и точки останова. Код переходит в крупноблочную стадию, после которой автор может вообще забыть детали реализации и даже передать код другому человеку. Потому что каждый крупный блок понятно что делает и чего от него ждать.

Соответственно, до перехода к стадии понимания блоков и отсеву мелких деталей — никакие тесты накладывать нельзя. Это всё равно что пытаться определить умственные способности ребёнка по УЗИ на третьем месяце.

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

Подвергать что-либо тестированию внутри блока — это бюрократический идиотизм. Почему? Да потому что разобраться в блоке просто читая его компактно — намного проще чем с обвесом в виде извращённой пунктуации и якорями тестов. Если блок не работает целиком — его перечитать целиком, а непонятные части — переписать. Даже если случилось так, что после прочтения не очевидно где ошибка — да, пишутся тесты, запускаются, но после обнаружения ошибки — все эти тесты УДАЛЯЮТСЯ. Потому что когда ошибка исправлена, контролировать её не нужно.

А когда будут вноситься изменения — то они будут вноситься в логику всего блока, и соответственно отразятся на тестах снаружи. И снова блок выгоднее держать «чёрным ящиком» и не трогать внутренности аж до тех пор, пока всё работает и внешние тесты говорят ОК.

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

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

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

ИМХО тесты нужно в первую очередь программисту чтобы потом спать спокойно.

Я обычно проектирую upfront, определяю контракты, граничные условия и пишу код таким образом чтобы бизнес-логика была отделена от инфраструктурного кода. То есть, тесты нужны мне чтобы удостовериться что контракты не нарушены. Соотвественно я рассматриваю тесты как спецификацию поведения элемента кода( метода, сервиса, модуля и т.д ).
В общем, я не следую основной идее TDD ~ писать тесты до написания кода, но активно использую юнит-тестирование (а также integration и acceptance testing).
И это в life-critical system, в которой ценой ошибки может быть человеческая жизнь, соотвественно планка качества очень высока. Но TDD сам по себе не решает проблемы обеспечения качества, т.к. объем тестов в таком случае будет не меньше, а то и больше чем кодовая база проекта.

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

Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Пробували писати тести перед кодом. Конкертно у нас це виливалось зазвичай у перевитрати часу. Є одна позитивна річ в такому підході — краще продумуєш архітектуру аплікації, але в умовах, коли специфікації змінюються і оновляються мало що не кожні два тижні, наперед писати тести — не вигідно.

І загалом щодо тестів: додаємо зазвичай їх на етапі, коли проект вийшов у перше плавання і бізнес ідея клієнта спрацювала і проект продовжу життя, відповідно вимагає подальших релізів. Якщо ж проект завідомо одноразовий, тоді писати тести — це лише знову ж таки витрати часу.

Тести пишемо виключно в довготермінових проектах для підвищення якості коду і зменшення регрешин багів під час рефакторизації.

Пробували писати тести перед кодом. Конкертно у нас це выливалась зазвичай у перевитрати часу.
1) Сколько времени прошло? Обычно первые 3-4 месяца, даже если люди мотивированы, соотношение времени код/тесты == 1/2-3, потом оно падает до 1/0.5, это при том что “код и потом тесты” соотношение довольно стабильно код/тесты == 1/1-2.
2) Как меняли время? "Код без тестов"/"код + тесты" или “архитектура + код + тесты”?
коли специфікації змінюються і оновляються мало що не кожні два тижні, наперед писати тести — не вигідно.
Ок. Написали код, тестов нет. Прошло 2 недели, спека не изменилась. И только потом пишете тесты? Что-то я в такое не верю. А если правда, то вы посчитали время на переключение контекста + затраты на КуА?
Якщо ж проект заведомо одноразовий, тоді писати тести — це лише знову ж таки витрати часу.
Снова же КуА. Даже если проект одноразовый вы далеко не всегда можете забить на проверку, такое возможно только если проект одноразовы и при этом маленький (до 1-2 человеко-недель)

1. Я пишу сначала тест в своих проектах. В не своих большинство людей вообще не пишут тесты. Ни до ни после. Убедить в проекте писать юнит-тесты часто нереально. Вот такой у нас рынок труда. Причем будут утверждать в стиле «мы всю жизнь их не писали, поэтому считаем, что лучший способ — не писать».
2. Тест — это утверждение. Если писать сразу тест, то код за ним следует. А значит и код состоит из утверждений. По сути код в первую очередь должен быть жестким в отношении требований. Кто-то программистов обманул в самом начале и они твердят как мантру, что код должен быть гибким. Не правда. Код должен быть в первую очередь правильным и это называется жескостью. Гибким он должен быть только в том плане, насколько сложно его менять. Так вот «сначала тест» ведет к тому, что на каждое утверждение — один тест. А значит и в коде так. Тогда код меняться будет легко тоже — убираете утверждение и код можно менять по этой оси.
Много еще плюсов в таком подходе, перечислять не буду.

Вначале формализуем наши хотелки. Потом все остальное.

Интересно, много ли людей, называющих себя программистами, понимают разницу между test-first и test-driven development.

Работая над бизнес-логикой TDD применяется без проблем, т.к. есть понимание что есть на входе и что нужно получить на выходе до начала работы над таском. Но вот если мне кто-то расскажет, как прикрутить TDD когда работаешь над core функционалом апликухи? Над частью фреймворка? Над универсальным решением, которое будет расширяться и использоваться в нескольких частях приложения? Когда вообще смутно представляешь, как и что лучше написать, как разделить код на классы, что куда вынести и тп. Когда нет четкого понимания что есть и что нужно на выходе (по объективным причинам)? Часто бывает, что пишешь с десяток классов одновременно, некоторые сначала создаешь, потом удаляешь (когда понимаешь что лучше этот код перенести в другое место), постоянно перетасовывая то, что есть, пока не достигнешь приемлемого качества архитектуры и кода. Что, сразу архитектуру рисовать на бумажках, вплоть до полного понимания какой метод будет в каком классе, какие будут связи и тп и тд? Так за время рисования картиночек (которые все равно не будут полностью отражать будущую работу, т.к. предусмотреть все нереально), я успею набросать основной функционал по быстрому, перерефакторить это несколько раз, пока не добьюсь того, чтоб код мне нравился, и потом, только после этого, покрыть все дело тестами. Что я делаю не так?

Работая над бизнес-логикой TDD применяется без проблем, т.к. есть понимание что есть на входе и что нужно получить на выходе до начала работы над таском
ну, дык, и применяйте :)
где здесь сказано, что это серебряная пуля?
опровержения универсальности вижу, а заявления об универсальности — нет.
вон, внизу еще упоминают прототипирование и R&D как исключения, когда TDD скорее не подходит(ну, или я так это понял)

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

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

я уже подумал, что

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

И зачем открывать заново совсем недавно обсуждавшееся?

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

Буагага, какая красивая подмена понятий.

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

QA разницы вообще не увидит.

QA как раз увидит во-первых по увеличении времени билда на сервере CI, может даже баг завести. Во-вторых по количеству падений того самого билда, особенно если команда разработчиков объемная или за пару дней 40-50-60 не-однострочных коммитов.

Что-то делается не так, если юнит-тесты заметно увеличивают время компиляции и, тем более, роняют билды.

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

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

Тест-ран и билд для меня разные процессы.

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

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

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

Не, это ж не в тему. Я могу написать код, посмотреть на него, написать тесты, посмотреть на результаты и только после этого сделать push. Это нарушения главного принципа TDD, но ни QA, ни сервер CI не увидит последовательности действий.

TDD вообще не определяет, когда делать push или commit.

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

можно писать и «как получится», но опять же и продукт выйдет «как получится» :)

Программисту нужно работать так, чтобы QA чувствовал себя ненужным.

Если коротко:
— ДА, это отлично работает на коротких циклах разработки, повышает качество.
— НЕТ, это не работает на длинных циклах, поскольку вынуждает потом быстренько написать «временные» заглушки для теста, и таким образом не только вывести код из покрытия, но ещё и дать алиби. Первое что сделает разраб когда ему спустят баг — запустит тесты и удостоверится что всё ОК. Если баг поймать сложно — он рискует навсегда остаться в системе.

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

Сами решайте, с какой стороны яйцо разбивать.

PS. Подсветка синтаксиса кода — прекрасный пример теста до разработки. Как видите, работает. Но если реализована через ж̶o̶п̶у универсальный интерфейс (пользователи NetBeans поймут) — лучше выключить и включить когда код уже готов.

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

Типа пишем тест assert.equals(add(2,2), 4), а потом пишем код add(a, b) {return 4;}.

TDD подразумевает, что разработчик ответственно подходит к кодированию, что тест должен проходить не только формально на конкретном наборе аргументов, но и логически на разумном диапазоне этих аргументов и при этом использоваться разумные алгоритмы, типа add(a, b) { return a+b;}, а не add(a, b) { if (a==0 and b==0) return 0 elseif (a==0 and b == 1) return 1; elseif ...}

Типа пишем тест assert.equals(add(2,2), 4), а потом пишем код add(a, b) {return 4;}.
Угу, это аргумент. Если у вас нет мозга, то не используйте ТДД.
Кстати, отсутствие мозга эта так же довольно распространенный случай, наряду с по...уизмом.
P.S. вас — это просто обобщение, а не обращение к конкретному человеку.

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

когда разработчик (чаще уровня синьора, а не джуна) пытается доказать техлиду, что тесты вообще, и TDD в частности лишь зря потерянное время
Ок, не только отсутствие мозгов, но еще и мудак. Такое встречается реже, но тут как раз польза от ТДД еще очевиднее — позволяет очистить команду :)
.
А если серьезно, то ответ «потому что не хочу» непробиваемый и встречается при внедрении практически любой практики. И в таких случаях можно только изолировать подобных персонажей.
но тут как раз польза от ТДД еще очевиднее — позволяет очистить команду :)

От такого лида — да, правильно поможет.

Новий Рік на носі, а людина тестами переймається.

Всем огромное спасибо за комментарии! Блин, жаль, что в ближайшее время не будет доступа к Интернету.

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

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

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

Всем огромное спасибо за дискуссию, за одно я получил некий срез специалистов на DOU для себя. Желаю всем огромных успехов в Новом Году и ребята, самое главное, желаю вам стать в следующем году намного лучше! Мы должны развиваться и становится лучше с каждым годом!

Всех с Новым Годом, не воспринимайте все так серьезно!

Почитал ответы, и пришла мне в голову идея.

В соседней ветке лежит задача про самсунговскую пластинку: dou.ua/...ic/6342/#249637 . Господа адепты TDD, можете показать, как для этой задачи будет выглядеть

  • 1. тест, написанный до кода
  • 2. минимальная реализация, которая тест проходит
  • 3. рефакторинг п.2 в сторону решения

Все очень просто. По понятным причинам я не буду сейчас тратить свое время на решение этой задачи. Но какие-то считанные минуты у меня в этом году еще есть =)

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

1. Первый тест. Тестирую еще не существующий класс PlateSoundExtractor. У него будет метод Sound extractSound(imageFileName), который на вход получает путь к файлу, а на выходе выдает звук. Как я это буду тестировать? Все просто:

PlateSoundExtractor будет зависеть от ImageLoader и SoundExtractor. Я мокаю еще не существующий ImageLoader.load(imageFileName), что он будет возвращать изображение, а SoundExtractor.extractFromImage(Image) будет возвращать звук для картинки. Понятно, что там не статика, а конкретные инстансы.

Запускаю тесты, в PHP мои инструменты (phpspec) нагенерирует мне эти еще не существующие классы и методы.

На данном этапе у нас готова самая высокая абстракция PlateSoundExtractor.

2. Нужно описывать теперь ImageLoader. Здесь все понятно и очень просто. Нас интересует SoundExtractor.

Пишу для него тест с двумя зависимостями, первая ImagePixelsToSoundDigitsConverter с методом pixelsToSoundDigits() и вторая SoundFactory.fromDigits(). Мокаю их аналогично первому шагу. То есть я убеждаюсь, в корректном поведении методов.

На данном этапе у нас уже есть готовый SoundExtractor. Пора бы перейти к конкретным реализациям.

3. Осталось реализовать ImagePixelToSoundDigitsConverter и SoundFactory. Понятно, что тест для SoundFactory.create() будет очень прост. Получили на вход цифры и сгенерили на выходе звук. Нас интересует самое главное ImagePixelToSoundDigitsConverter.pixelsToSoundDigits().

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

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

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

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

Я могу написать свой план, но для вот этой задачи у меня написание тестов (acceptance tests, не прогон на одном-двух значениях) идёт вообще после получения результатов. То есть после выполнения задания.

Если идея правильная, это реально сделать за 4 часа. Но только первую идею. На вторую, даже если она есть, времени не хватит.

Согласен про ООП шелуху, даже можно просто алгоритм запилить сразу и писать тест только для него. В духе: загружаю такую-то картинку и получаю такой звук. А можно несколько иначе.

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

Я бы написал тест на извлечение дорожки из изображения пластинки, для реализации заюзал бы OpenCV или что там у вас для этих целей используется. Затем написал тест на разбиение дорожки, на блоки, типа передаю в метод дорожку Block[] split(PlateTrack track), а получаю блоки. Тоже не сложно написать. А далее, я бы писал тест на превращения такого блока в цифру звуку для метода SoundDigit mapToSound(Block block). Таких тестов было бы уже столько, сколько есть вариантов распознавания блока в цифру.

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

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

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

P.S. какие opencv, какие нейронные сети за 4 часа?..

Для написания теста нужно знать что должно получиться в ответ на те или иные параметры. Как оно получается знать не нужно.

Условие задачи выше. Покажите мне, что должно получиться (не написав ни строчки кода, потому что TDD).

В данном виде это исследовательская задача, для которой TDD в чистом виде не применим. Возникни подобная задача на практике, нужно было бы подготовить образцовые файлы источников и выхода, а потом писать решение по тестам типа assert.equals(Decoder.PngToString(’sample.png’), ’01234567890′);

В данном виде это исследовательская задача
Бггг. Тестовое задание джуну при приёме на работу.
для которой TDD в чистом виде не применим
И это основная мысль, которую я хочу подчеркнуть. TDD, т.е. тесты-до-кода, это всего лишь приём, который работает в одних случаях и не работает в других.
нужно было бы подготовить образцовые файлы источников и выхода, а потом писать решение по тестам типа assert.equals(Decoder.PngToString(’sample.png’), ’01234567890′)
  1. распознавание цифр (звук -> текст) в постановку задачи не входило. На выходе звук.
  2. black box test для этой задачи будет сложнее решения, и соответственно бесполезен. Там будет восстановление с потерями, и вместо equals будет очень неприятная операция сравнения двух звуковых дорожек. Это для более-менее произвольного sample.png
  3. sample.png, для которого выход будет заранее известным, зависит от алгоритма и деталей реализации. У меня есть две идеи как это сделать, так вот тестовые картинки для них будут принципиально разными.
  4. сказать, работает идея или нет, можно только имея заметную часть кода.
Начинать писать тесты до кода в этой задаче бессмысленно. Вообще в принципе. Нужна предварительная работа просто чтобы понять, как тесты должны выглядеть, и побочным результатом этой работы будет код решения.

>>Тестовое задание джуну при приёме на работу.

От этого она не перестаёт быть исследовательской.

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

Кто-то спорит с этим?

>>распознавание цифр (звук -> текст) в постановку задачи не входило. На выходе звук.

Вроде требуется текстовый файл.

>>Там будет восстановление с потерями

Откуда с потерями? Там же чистая математика.

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

Смотря с какой стороны идти.

Кто-то спорит с этим?
dou.ua/...c/12087/#612329
dou.ua/...c/12087/#612581
dou.ua/...c/12087/#612359
dou.ua/...c/12087/#613282
blog.8thlight.com/...tartUpTrap.html
Вроде требуется текстовый файл.
Не надо меня так пугать. «Требования к исполнению» п.2.
Распознавание там исключительно человеком (п.3 и п.4d)
Откуда с потерями?
Дорожки круглые, а пиксели квадратные :)

Потом, п.3 допускает потери, даже если без них можно обойтись. Скажем, если lossy сильно проще lossless. Но я думаю, что там lossless невозможно вообще.

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

>>Распознавание там исключительно человеком (п.3 и п.4d)

Слово «человек» как-то не встретил, значит неправильно понял условия.

>>Дорожки круглые, а пиксели квадратные :)

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

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

IMHO здесь попытка описать обычный дизайн сверху вниз. Тест — сравнение текущего результата с ожидаемым. Разработка теста состоит в определении, что с чем и как сравнивать. Фраза

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

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

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

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

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

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

Открываем ядро Linux и ищем там этот стандарт де-факто.

Там есть человечек, который заменяет тесты =)

Но не смотря даже на это, если в репе ядра нет тестов — это не значит, что их нет вообще, они просто существуют отдельно.

Из того, что знаю linux-test-project.github.io. Думаю если погуглить, то еще найдется много тестов.

Так речь же не о тестах, а о TDD.

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

Вот к пример участвовал в UaWebChallenge github.com/.../BattleBalancer и банально не было времени на тесты, но в голове они у меня были и направление движения мыслей, позволило быстро справиться с задачей. Другое дело, что я тратил время на постоянный перезапуск кода и проверку руками, что все работает.

Міра повинна бути у всьому в тому числі у тестуванні...
Багато лишнього часу ?
І в джаві 8 такий код це стандарт а не гамнокод .
Customers
.stream()
.filter(c -> c.getId() == id)
.findFirst()
.get()

Это хороший код, я не считаю его говнокодом. Вы приводили этот пример и я писал вам ответ dou.ua/...-comment#612395

Все хорошо, если это в одном месте. Плохо, если начнете таскать такой код и дублировать. Тут дело даже не в тестах.

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

в глобальный рынок, где тдд уже де факто стандарт.

Это где стандарт ?

Хех, как раз «де факто» и не позволяет привести ссылку или публикацию, которую вы ожидаете.

Везде где я работал — никто не применял ТДД.
На всех (аж 5) собесах которых я был в этом году, никто не применяет тдд.
Где стандарт ?

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

так значит всетаки не стандарт, не ?

Понимаете, а там где я ходил, я видел. И что теперь нам делать?! =))

Отучаться говорить за всех и ставить квантор всеобщности основываясь на частных случаях

Абсолютно с вами согласен.

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

Вот, например, вакансия с TDD/BDD jobs.dou.ua/...vacancies/4443 я бы пошел сюда пособеседоваться, но не из-за TDD. Или здесь jobs.dou.ua/...acancies/10540.

Вот, вроде интересная jobs.dou.ua/...acancies/12630, но нет TDD. Но я бы не пошел туда по-другим причинам.

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

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

ИМХО тесты нужно в первую очередь программисту чтобы потом спать спокойно.

Я обычно проектирую upfront, определяю контракты, граничные условия и пишу код таким образом чтобы бизнес-логика была отделена от инфраструктурного кода. То есть, тесты нужны мне чтобы удостовериться что контракты не нарушены. Соотвественно я рассматриваю тесты как спецификацию поведения элемента кода( метода, сервиса, модуля и т.д ).
В общем, я не следую основной идее TDD ~ писать тесты до написания кода, но активно использую юнит-тестирование (а также integration и acceptance testing).
И это в life-critical system, в которой ценой ошибки может быть человеческая жизнь, соотвественно планка качества очень высока. Но TDD сам по себе не решает проблемы обеспечения качества, т.к. объем тестов в таком случае будет не меньше, а то и больше чем кодовая база проекта.

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

Отличный комментарий, спасибо!

Хотел бы добавить, что для спецификаций есть xSpec фреймворки.

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

Больше всего интересно, где TDD может навредить.

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

Блин, я чертовски расстроен. Я думал, что у нас в Украине с отношением к качеству кода и с понимаем того к качеству прийти, всё гораздо лучше.

Эх, вы! Ленивые задницы! Признайтесь честно, лучше сразу, что вы не писали больше года с TDD и не пробовали покорить подход «Test First», но готовы с легкостью критиковать TDD и защищать свой подход. Потому как вам лень развиваться и двигаться дальше, лень напрягать мозг.

От этого комментария капли жира выступают прямо на мониторе.

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

Могут с уверенностью в 99%, что те, кто поддержали ваш комментарий (кроме 20% из них, в данном случае это один человек), не практикуют TDD.

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

))
Вот мышление адепта ТДД: множества, пересечения, триангуляция.

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

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

Я с таким же успехом могу сказать о вас: «Вот мышление антагониста TDD: стохастичность и метод Монте-Карло» (хуяк-хуяк и в продакшн). Лан. шучу, тут я конечно злонамеренно.

Но все равно спасибо, подумаю, может и вправду мыслю шаблонами.

Все очень просто. Это уже реализации. Давайте начнем с задачи, которая стояла перед разработчиком. Я верно понимаю, что это получение идентификаторов транзакций по определенным критериям?

Если так, то все просто.

Условно определяем для себя, что у нас будет метод TransactionRepository.findGroceryTransactionIds().

Условно на псевдо-языке пишем (не знаю Java):

void setup(TransationSourceInterface source) {
this.beConstructedWith(source) // в конструктор передаем мок source
}

void itShouldFindGroceryTransactionIdsOrderedByTransactionValue(TransactionSourceInterface source) {

source.getStream().willReturn(new List { new Transaction(2, Type.Grocery, 30) } ); // создаем список транзакций и говорим моку, что он вернет

// утверждаем, что вернет наш метод
this.findGroceryTransactionIds().shouldReturn(2, 3, 4);

}

Я извиняюсь за псевдоязык, могу привести пример на php работающего кода.

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

Эх, вы! Ленивые задницы!
Не способный покрыть 3 строки кода. Лол.

Не смотря на ваши провокации, я покрыл заведомо пахнущий код. Если бы я писал его изначально, то не допустил бы такого, что код покрыть нельзя dou.ua/...=comment#612272

Не привирайте и не переходите на личности.

Я уже 5-й раз пытаюсь добиться от вас внятного решения простейшей задачи для джуна и вот даже вас пришлось цитировать. Где вы увидели переход на личности в вашей же цитате? Не маскируйте свое незнание съездом с простейшего вопроса. Если не нравится моя реализация, напишите с нуля тестами до нее. Но переходите уже со слов на код, а то просто смешно читать таких адептов-теоретиков.

Возможно ошибся, но кажется, что у вас какая-то не приязнь, может из-за PHP в профиле. Могу сменить на Scala.

Не маскируйте свое незнание съездом с простейшего вопроса

Это считаю переходом. Так как вы не знаете, что я знаю, а что нет. Я привел тест для вашей задачи, вы его не осилили. Я объяснил почему ваш код плох — вы это не осилили. Я пытаюсь сейчас с нуля написать для реальной задачи, но что сделаете вы? Нет, вы не будете пытаться разбираться в вопросе и думать. Вы просто...

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

Вашу проблему я решал и прошел еще в самом начале, это типичный вопрос новичка в TDD, а как тестировать цепочки вызовов? Хотел вам мягко объяснить, что никак и нужно иначе.

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

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

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

Честно, мне пофиг. У меня ощущение, что я с вами не общаюсь, а пытаюсь оправдываться. Я отлично понимаю почему «типичный» аутсорсер не будет писать тесты и будет всячески им сопротивляться.

Не пишу тесты от слова совсем.

Эх, вы! Ленивая задница! =))))

Вопрос не в ленивости, вопрос в том, что данный подход требует немного вывернутого мышления.

Никогда не понимал, как сначала тест писать.
Окромя случаев, когда интерфес и поведение строго зарание определенно.
Но в овер 99% тасков такого нету. Определить интерфейсы это и есть часть реализации.
Написать тест на непонятно что — непонятно как.

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

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

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

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

Я с этим борюсь какраз написанием тестов (и рефакторингом, если надо) _после_ реализации интерфейсов.

То есть написали что-то, а потом разбираетесь что, фиксируете поведение и рефакторите?

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

Ясное дело, что такие есть и которым все это не нужно.

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

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

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

на такую строку something.getFoo().get(0).getBar().getTitle() сколько вам надо тестов? Надо подымать тестовый контекст, потому что бин something инжектится и внутри него могут быть сложные засечивания? юзать моки, замокивая все вызовы «паровозика»? проверять что getTitle реально вернет тайтл?
Я не вижу ни малейшего смысла в этом. Тем более что таких строк могут быть десятки и PROFIT стремится к нулю.

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

something.getFoo().get(0).getBar().getTitle()
Вот за подобное мне всегда хочется отрубать руки делателей сего. Вы в цепочку еще вызовов надцать добавьте и можете просто выкинуть весь написанный бред.

А как вы предлагаете писать?

Так, чтобы человек мог прочитать, а не крутой прогер Епама.

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

Ну перепешите накшталт.
Thing thing = something.getFoo().get(0)
String title = thing.getBar().getTitle()

Что это меняет ?

прям перепись 23-летних сеньйоров.
Ня Закон_Деметры

Огромное вам спасибо! На убой не мог вспомнить и прогуглить этот закон.

Смешались в кучу кони, люди, законы деметры..

Although the LoD increases the adaptiveness of a software system, it may also result in having to write many wrapper methods to propagate calls to components; in some cases, this can add noticeable time and space overhead.

В том-то и дело =) Что ничего не меняет =)

Меняет. Если где-то посредине этой каши вылезет null, будет потом легче отыскать где все срезается.

Если посреди каши вылезает null, то должно генерироваться исключение. Хотя JS уже исправит только могила.

Да ладно — все нормально с этой конструкцией.
Не явист и не шарповец, но нормально ее читаю.

А я глубину вызовов больше 3 считаю убожеством.

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

Я ответил по конкретному примеру, а сферический код в вакууме сложно обсуждать.

Всё нормально с цепочками. В чем разница по сложности:

getFoo1().getFoo2().getFoo3();

и:
var f1 = getFoo1();
var f2 = f1.getFoo2();
f2.getFoo3();

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

Если же вопрос в том, что много сложности — так это уже каждый решает сам. Или оставляет или выносит в отдельные методы. Вещь субъективная, потому как вынос в отдельный метод в общем не избавляет от сложности, а просто скрывает за именем. Если имя удачное, значит получилось уменьшить «локальную» сложность. Но глобальная только увеличилась: плюс одно имя.

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

>>Но глобальная только увеличилась: плюс одно имя.

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

Ну как не факт. Сложность == объем информации (хотя этот объем — вещь субъективная). Но в любом случае добавление нового имени — это плюс еще одна хрень. Не было имени, добавилось имя. Значит объем информации увеличился. Сложность выросла.
Это в общем. Я соглашусь, что важнее в коде локальная читаемость. Т.е. если у вас не возникает проблем с пониманием при чтении локально кода, не зная о других частях системы, не подозревая неявных связей, то код неплохой. Но это будет при условии умелого выбора имен, а также принятых соглашений по именованию. Если имя выбрали нечитаемое, которое не говорит, что делает метод, то какое же здесь будет упрощение? Вы читаете код метода, встречаете непонятное имя, начинаете материться и переходить на указанный метод, чтобы почитать и его и понять что он делает. Т.е. в таком случае возможно лучше, чтобы тот код был заинлайнен.

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

>>Не было имени, добавилось имя. Значит объем информации увеличился. Сложность выросла.

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

Главный недостаток же цепочек вызовов «слева направо» (как и простыней «сверху вниз») — зависимости, в случае цепочек или нетипизированных простыней ещё и не явные. Инкапсуляция наше всё, разделяй и властвуй :) Кстати, этот девиз имеет прямое отношение к проблеме управления сложностью систем. Блокируя горизонтальные связи мы уменьшаем сложность, упрощаем контроль и управление, будь то код или мировая политика.

Инкапсулируя же часть этих строк в новую функцию/метод мы часть этих потенциальных связей принудительно рвём
Мы ничего не рвем. Как была зависимость от работы вынесенного метода, так и остается от заинлайненного кода. Уменьшаем сложность, если этот вынесенный метод используется несколько раз в разных местах. Скажем так: чтобы в первый раз разобраться с таким кодом, нужно больше усилий. Если имя ясное и связанное с реальным миром, например GetPersonName(), то разбираться будет легче с конкретным методом, потому что меньше сущностей нужно удерживать в внимании. А внимание узкое, всего лишь 3-7 объектов. Т.е. введение имен, которые уже нам известны, не повышает особо количество информации. Но упрощает восприятие.

Если говорить о цепочках, то они как раз используются там, где код не повторяется и смысла выносить в отдельный метод нету. Например:
Select(p => p.Name == Name).OrderBy(x => x.Name);
Цепочки могут быть такие длинными, при этом в каждом месте некая уникальная комбинация. И поэтому нет смысла их выносить. нет смысла писать метод:
SelectByNameAndOrderBy()
Горизонтальные связи могут быть по задаче, исходя из задачи. Понимаете, инкапсуляция, полиморфизм, наследование — это всё круто в вакууме, но говно в реальности. Сравните с реляционными субд. Там таблицы связаны горизонтально. По сути это n-мерная структура, где оси ортогональные и вы можете делать любые вычисления поворачивая этот многомерный куб как хотите. ООП же — это дерево, в котором одна иерархическая ось. Другие оси можно цеплять, но через задницу с гемороем. В ООП важен изначальный правильный выбор оси. И если вы хотите по уровням иерархии распихивать зависимости и инкапсулировать — это хорошее желание, но оно не работает в ряде задач. Вот ниже где-то здесь прямо дали пример, когда вызывают по цепочке сущности, подобно работе с таблицами. Они могут быть сколь угодно длинными и даже циклическими. Ну просто могут быть такие задачи, когда нельзя определить, что выше, что ниже в иерархии. Бывают агрегации, композиции и всё такое.

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

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

Бывает и такое. Особенно если применяют паттерны по причине «это тренд».

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

Дело не в языке. Просто вдумайтесь, что происходит в строке, сколько в ней сразу зависимостей уровней. И как банально пользователь something должен был узнать о существовании getTitle() у getBar() у первого getFoo().

Вдумайтесь сколько сложности вложено в этот код.

По-моему сейчас так практически все библиотеки устроены. Особенно реализующие UI чуть сложнее примитивной формы на вьюшке.

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

Там не совсем о тех цепочках идет речь о которым мы говорим.

В jQuery объект возвращает сам себя и там все ОК, так как мы модифицируем один и тот же объект. Это называется Fluent Interface и это нормальный подход, тут я ничего не имею против.

А, всё, понятно. Все о разном говорят.

В jQuery объект возвращает сам себя и там все ОК, так как мы модифицируем один и тот же объект. Это называется Fluent Interface и это нормальный подход, тут я ничего не имею против.
В JQuery цепочка вызовов не является примером Fluent Interface

сфигали return this не fluent interface? o_0

Посмотрел, да, в одном месте реализуется Fluent Interface

Может ошибаюсь, плохо знаю jQuery. Но там вроде оно возвращает само себя.

где-то встречал определение как раз на примере вьюшек. как только глубина вложенности больше двух — нафиг.
т.е. mainView.optionsFormView.timeWidget.getDate() -> это пипец, инкапсуляция разорвана на кровавые ошметья.
что еще кажется мне допустимым — прокидывать событие из глубин на верх. хотя, по-хорошему, какое-то time:changed во глубине глубин должно двумя уровня выше превращаться в более абстрактное settings:changed. иначе будет больше.

Да ну бросьте — конструкции типа view.titleLabel.bounds.size.height сплошь и рядом и как вы собрались последние три разрывать? Все время size декларировать по дороге?

и как вы собрались последние три разрывать
не использовать.
давайте, реальный пример.
с этим даже не интересно — зачем-то у вьюхи берем и вытягиваем размеры заголовка. что? нахрена оно нам в отрыве от контекста — самой вьюхи? подгонять что-то там? вычислять что-то там? а почему нас не интересует другое что-то кроме заголовка? а если он мультистрочный? нафига это всё?
не, давайте, реальный пример.
из-под моих пальцев тоже выходит someView.someWidget.someControl.set(...)
а потом я бью себя по пальцам и переписываю.

account.getDepartment().getHead().geAssistant().getName() - типа показать имя асистента главы департамента где работает акаунт.

Давайте еще так, скажите где вам это понадобилось, не хватает контекста.

Это абстрактный пример, я формошлеперством на ОРМах на работе не занимаюсь.

Я в Eclipse нажимаю Ctrl+Enter и узнаю. В чем проблема?

В промежуточных звеньях.

Вы подрываете инкапсуляцию, когда A -> B -> C -> D.

Это значит вы не можете менять теперь B и C, так как сломаете кучу таких цепочек в своем коде.

А как надо что бы не сломать?

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

Могу ошибаться.

Т. е. выпредлагаете передать акцессоры к D из C в A?
Может тогда вообще одним классом A обойтись на несколько тысяч строк?

Это не обязательно могут быть просто геттеры.
Хороший пример с именем главы департамента:

account.getDepartment().getHead().geAssistant().getName()

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

В данном случае просто нельзя будет менять:

Deparment::getHead()
Head::getAssistant()
Assistant::getName()

Согласны?

Но здесь это не страшно, хотя можно и так сделать:

account.getDepartment().getHeadAssistantName()

Дело в том, что эта цепочка выражает какое-то явное понятие, в данном случае, имя ассистента главы департамента и зачем-то оно где-то нужно.

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

То скажите честно, вы бы копировали эту цепочку?

Но я хотел сказать не о простых геттерах, а о каких-то более сложных примерах.

Например, есть заказ, у него есть список товаров, и у них есть цены, Нужно посчитать сумму заказа.

Можно где-то в контроллере или еще где, сделать так:

order.products.sum(p => p.price)

В данном случае неявно выражено понятие суммы заказа. А что если нужно добавить скидку для пользователя? Вы скажете не проблема:

order.products.sum(p => p.price) * 1.1

Окей, а что если для разных категорий, разные скидки, вы поняли? Да?

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

order.totalSum

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

Но вам ведь привели другой пример. Зачем вытаскивать из главы его имя? А может еще пол вытащим? А возраст? А если нам потребуется зам, который может быть опциональным? Будем писать в соседних строчках getHeadAssistantName(), но getDeputyHead().getAssistant().getName()?

account.getDepartment().getHeadAssistantName()

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

Что значит «менять»? Переименовать я их в любой момент могу по хоткею.

И что значит «сломаю»? Как сломаю, так и починю, поправлю ручками пока Eclipse не перестанет подсвечивать красным. Могу даже find | xargs sed запустить, если такого реально много.

Только не надо мне про большой проект и бла-бла-бла — в большом проекте есть модули и A-B-C-D кишки нормальные люди наружу за фасады не вытаскивают.

Да, понятно, что отрефакторить можно, что угодно. Тогда вообще можно писать как угодно и сказать, что я потом просто заюзаю find | xargs sed.

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

Давайте поконкретней. Вам там сверху привели несколько примеров цепочек. Как бы вы их отрефакторили и что бы вы там тестировали?

А как вы будете потом менять именно цепочки через find | xargs sed? И как вы будете, что нашли именно все цепочки, если вы не контроллируете свой код.

Ну например так:

find . -name «*.c» | xargs sed -i -e ’s/somegetter1()\.somegetter2()/shortgetter()/g’

Что вы называете контролем кода? Если я поменяю класс и не найду все цепочки, мне об этом сообщит сначала индексер, а потом и компилятор/

find . -name “*.c” | xargs sed -i -e ’s/somegetter1()\.somegetter2()/shortgetter()/g’
хорошая попытка.
а оно найдет:
Thing thing = something.somegetter(1);
String title = thing.somegetter2()
?

Конечно, нет. Вот почему лучше написать цепочку чем так извращаться, о чем вам реально хакер и писал.

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

Никакой сложности. Объект хранит своё состояние в приватных переменных, каждый его метод возвращает ссылку на сам объект. Всё. Это нужно не всегда, но часто удобно. Например, вот так в Kohana можно сделать запрос к БД. Ни одной лишней переменной.
$result = DB::select() ->from(array($table_trades, 'tt')) ->where('tt.idu', '=', $idu) ->and_where('tt.uac_id', '=', $uac_id) ->execute();
Хотя можно и по-другому. Можно например завести сначала переменную $select и вызывать в каждой строке отдельный метод.

Здесь тоже вы правы, но опять — это Fluent Interface, здесь QueryBuilder возвращает сам себя и здесь все очевидно и ничего плохого в этом нет. Плохо, когда там будет иерарахия объектов. Например,

this.getSecurity().getContext().getToken().getUser()

Забейте на архаичные Java 7 и ниже. В 8 сплошь коллекции, stream, map, collect и такой «ручейковый» код очень даже поощряется. Наоборот если начинать выделять любой вызов в переменную начинается уродство и много buzz-кода.
Чем пострадает читабельность если, скажем, разбить вызовы по строкам например так

something
        .getFoo()
        .get(0)
        .getBar()
        .getTitle();
Но это скорее зависит от codestyle принятого в компании. Руки стоит отрывать ТОЛЬКО если в этой цепочке возможно схватить NullPointerException

А кто гарантирует, что нельзя его схватить?

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

Если могут, то:
1. Можно так и оставить. Если программа должна упасть и null там не ожидается, то делать проверку можно разве что для того, чтобы выбросить более понятный эксепшин. Но это не обязательно и код может захламлять. Не плюсы, страшного ничего не будет при обращении к null.
2. Если при наличии null надо просто остановиться и ничего не делать, можно или на разных строчках с проверками писать (что захламляет код) или обратить внимание на паттерн MayBe

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

Поддерживаю такие цепочки только в случае Fluent Interface, все остальные случаи — это код с запашком (ru.wikipedia.org/....B2.D0.BE.D0.B2)

Цепочки очень удобны

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

something.getFoo().get(0).getBar().getTitle()

Вы привели заведомо пример плохого кода, но даже с ним можно работать — сделать something.getFooFirstBarTitle(). То, что приведенный вами пример сложно тестировать, не значит, что смысла писать тесты нет. Этот пример сам по себе плох. И здесь тесты как раз хорошо показали бы, что раз плохо тестируется, то нужно анализировать, что с кодом.

Хорошо, как вы будете покрывать TDD этот код? Все мои вопросы выше остаются прежними.
.......
Foo foo = something.getFoo();
Bar bar = foo.get(0);
String title = bar.getTitle()
.......

Смотрите, я согласен провести эксперимент =)

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

Этот код протестировал бы так, условно:

something.setFoo([new Bar() { title = «Alias»]);

something.getFirstFooBarTitle().shouldReturn("Some title");

Гмм, а с чего вы решили, что у Bar есть публичный сеттер для title?
И еще — а на NULL, строку больше 100500 символов, SQL Injection, XSS, различные локали тестировать не будете? Если нет — грош цена таким тестам.
А если будете — то getter или setter?

Хорошо.

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

Что касается SQL Injection, то его берёт на себя ORM в одном месте и это покрыто тестами, XSS берёт на себя Templating и это покрыто тестами. Мне не нужно это тестировать еще раз.

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

В-третьих, я не тестирую 100500 случаев работы кода. Мне достаточно одного.

Проблема данного кода в цепочке, в том, что мы не можем модифицировать промежуточные звенья
Гмм — а как вы можете вообще модифицировать или тестировать, скажем, библиотечные вызовы? В смысле везде, где они — доверяем слепо, а себе — не верим и проверяем все? В конце концов внутри метод или интерфейс тоже из кучи разного кода состоит — нужно ли тестировать его? Или там try-catch достаточно?
Никак не пойму логику.
Мне не нужно это тестировать еще раз.
Вот! Я и спрашивал — почему не использовать для тестирования кода SCA и иже с ними? Зачем каждый раз ваять велосипед, который будет наверняка неполным, при этом утроив время разработки?
В-третьих, я не тестирую 100500 случаев работы кода. Мне достаточно одного.
То есть вы тестируете только на штатную отработку?
Но ведь это и так при написании тестируется и подразумевается? А проблема как-раз в нештатных и непредусмотренных ситуациях?

Понял вас. Смотрите, как я это понимаю и как делаю.

1. Про цепочку. Когда есть такой вызов методов A->B->C->D, то в этой цепочке мы не можем изменить уже B и C, так как от этого пострадает цепочка где-то в нашем коде. Более того это нам намекает, что если мы так глубоко лезем за D, а у нас есть только A, то зачем нам вообще нужны эти B и C. Нам нужно либо сразу получать D, либо скрыть за A получение D.

2. SCA — для меня классная вещь, но есть семантика кода и то как он будет выполняться. Тесты позволяют мне описывать бизнес-правила в явном виде и проверять, что код работает корректно в соответствии с моими ожиданиями, а SCA для меня вещь, которая в принципе говорит, что код работает и у него нет проблем мест. Ведь, SCA не знает же, что «на сумму заказа должна начисляться скидка» или что «при получении элемента из стека, оный должен оттуда удаляться». SCA классный инструмент, который может дополнить тесты и сказать, что с технической точки зрения все реализовано ОК.

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

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

Я уже тоже понял, спасибо.
Скажем так — ТДД это годный инструмент применительно к распределенной строго структурированной разработке на воспроизводимых данных.
В этих случаях он действительно помогает сэкономить общее время за счет некоторого увеличения времени каждого разработчика.
Однако есть порядочно областей, где такой подход не имеет особого смысла — монолитные приложения и утилиты, отдельные замкнутые части. которые пишет один разработчик, визуальные части (как UI так и OpenGL etc), обработчики датчиков и пр.

Да, Тим, есть места где я бы этот подход не применял или бы задумался. Я больше пишу о тех 80% случаев, где он применим.

Например, я не пишу тесты для CRUD, мне банально лень + знаю, что все что ниже — протестировано.

Я больше пишу о тех 80% случаев, где он применим
Простите (не хочу разводить холивар, но ваша ошибка присуща большинству разработчиков на этом форуме), но вы зашорены в довольно узкой задаче в индустрии ИТ — веб-разработке и поэтому вам кажется, что весь ИТ мир — веб разработка.
А это далеко не так. Так же как и девелоперы в бодишопах — это не «большинство», а максимум 10% от всех девелоперов в стране.
Навскидку приведу вам несколько примеров областей, которые заметно больше веб-разработки и где о применении ТДД речь пока не идет:
1. ERP (от 1С до SAP/Axapta/OEBS)
2. SQL (весь, включая BI)
3. Embedded
4. Практически весь Mobile (он меньше WEB, но имеет право находиться в списке)
5. OS/drivers/firmware
6. Gamedev
...
Т.е. в сущности — ваши 80% выходят от доли индустрии примерно в 10% и по факту — 8%.
Это не умаляет достоинств подхода, но стоит помнить, что он узкоспециализирован.
.
Возможно поэтому вы и получили столь разные отзывы на вопрос топика. Если бы уточнили, что речь идет и ВЕБ-разработке, то результат мог бы быть намного позитивнее.

Тим, перестаньте =) Я не настолько наивен.

Те темы, которые вы описали. в них спокойно можно применять TDD, пусть даже не TDD, а хотя бы тесты:

1. Mobile: на оф. сайте Anroid developer.android.com/...it-testing.html
2. Gamedev, как один из самых популярных примеров unity3d.com/...hive/test-tools
3. OS/drivers здесь вообще в первую очередь, у меня знакомая пишет софт для Automotive, для железа и прекрасно там у них работает тестирование и это из самых важных моментов.
4. ...

Может вы просто не представляете себе, как там можно это применить? =))

Я же не в вакууме живу и общаюсь с людьми, знаю, кто применяет, кто нет. Чаще всего знаю почему. Я эту тему открывал для тех, кто практикует TDD. Я же как раз сужал аудиторию отвечающих. Перечитайте. Я спросил: «Вы пишите тесты до/после и почему?» Не более.

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

Те темы, которые вы описали. в них спокойно можно применять TDD
Можно, но не применяют.
1. Mobile: на оф. сайте Anroid developer.android.com/...it-testing.html
Возможность (и даже туториалы с тренерами-коучерами) есть и в XCode, но нюанс в том, что библиотек и компонентов на том же github, включая самые «крутые» типа фейсбук-гугл апи, afnetworking и пр., использующих этот подход просто нет. Только туториалы от тех тренеров выше.
Юнити не занимался, но снова-таки возможность и повсеместная реализация — совсем разные вещи. Ну и юнити — далеко не весь геймдев. Если тут есть кто-то из Warthunder (Перша студия) — может пролить свет.
OS/drivers здесь вообще в первую очередь
Исходники linux, xBSD, macosx открыты. Не наблюдаю там юнит тестов ни разу, может не туда смотрю? Дрова тоже разрабатывал и патчил — там другой подход к тестированию, ТДД его назвать нельзя.
Я эту тему открывал для тех, кто практикует TDD
Но ненамеренно зацепили просто всю аудиторию. :)
Впрочем — на ДОУ всегда ответов будет на порядок больше, чем было в вопросе, даже если спросить «который час»...

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

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

Говорю не абстрактно, а как человек имеющий опыт покрытия тестами в целях перехода (в 50% успешного) на TDD с ERP, SQL и Gamedev.

Да, ладно. Работал на проектах с статистикой, юнит-тесты на ура писались.

Юнит-тесты вообще прикрутить можно куда угодно, если методы не имеют сайд-эффектов. К статистике? Запросто. Если они считают статистические данные, так дайте на вход какие-то точные данные, результат работы метода на которых точно известен. Т.е. если в методе нет рандома (и, желательно, других сайд-эффектов), то тестируйте на здоровье.

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

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

На статистике, повторюсь, не обязательно ж генерить случайные данные на каждое прохождение теста? Берете данные, небольшие, на которые точно известен результат. И пишете с ними тест. Т.е
вариант 1: считаете результат в уме, карандашом, потому как данные небольшие. Зато тест показывает, что статистику считает верно.
вариант 2: берете данные и считаете результат на другом ПО. В экселе, например.
вариант 3: придумываете простой, но другой алгоритм, высчитывающий то же самое. Он может быть неоптимальным, но главное — не должен повторять логику исходного кода. И прогоняете и там и там, сравнивая в конце результаты.

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

можно, конечно, и с некоторой точностью считать.
то проверяйте статистики этого распределения, моменты.
Мда и чей то я. Забыл что программистам математика не нужна. Исправлюсь.

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

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

Результативные статистические характеристики каких-то сложных случайных процессов зачастую неизвестны на этапе кодирования — есть формула, часть параметров которой случайны, но итоговое распределение нужно трудоемко рассчитывать со всем аппаратом теорвера. Но даже если, например, известно матожидание в 95% интервале, но канонический юнит-тест, «достойный» того чтобы быть включенным в коммит-хук, к нему неприменим, поскольку с вероятностью 5% он будет падать. Можно зациклить и делать 100500 повторов, но все равно падающий тест не говорит об ошибке, а пройти может случайно: любой исход может быть на первом испытании, как и любой исход может не появиться на любом конечном числе испытаний.

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

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

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

Вы же очевидно говорите о функциях с сайд-эффектом — с вызовом рандома. Т.е. внутри функции происходят случайные процессы и вы не можете гарантировать точное значение функции при тех же параметрах. Иногда нужны сайд-эффекты, согласен. Но сделать тесты на это тоже можно. Просто вместо вызова rand() передайте интерфейс как параметр в функцию, в котором будет этот метод. И тогда вы можете в тесте вызвать свой мок, который будет не случайным. Я так уже тесты писал. Рандом же вам не нужно тестировать, верно?
И вот тогда вы сможете проверить, не ошиблись ли вы, когда писали вычисление сложной формулы. И тест будет строго однозначный, падать не будет с вероятностью 5 процентов. Потому что там вероятностей уже нет.

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

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

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

Тогда так: на основной поток — до, на созданные в ходе реализации узкие и стремные места — после, на баг-репорты и фич-реквесты — снова до.

«грош цена таким тестам»

Каково, по-вашему, назначение тестов?

При TDD вопрос «как вы будете покрывать этот код?» лишен смысла. Скорее я спрошу «какой извращенный тест нужно было написать чтобы появился такой код» :)

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

А если в этой цепочке задействована инфраструктура, а вы её отюниттестить пытаетесь, то значит что-то пошло не так.

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

Такое протестировать невозможно DoSomething().DoSomething().DoSomething().DoSomething().DoSomething().DoSomething().DoSomething().DoSomething().DoSomething().DoSomething().DoSomething().DoSomething()...

Какой пример, такой и ответ. Мне кажется вы не готовы слушать и вы не открыты к дискуссии. Давайте попробуем по-другому.

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

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

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

Даже с вашим абстрактным примером, я показал, что можно сделать лучше something.getFirstFooBarTitle().

Давайте конкретный пример и будем разбираться конструктивно.

Могу повторить в 3-й раз, мне не трудно, мой пример не поменялся, я даже переписал специально для впечатлительных в три строки

Foo foo = something.getFoo();
Bar bar = foo.get(0);
String title = bar.getTitle()
при условии, что something, сущность засечиваемая инфраструктурой, например Spring-ом.

Мои вопросы тоже не поменялись:

сколько вам надо тестов? Надо подымать тестовый контекст, потому что бин something инжектится и внутри него могут быть сложные засечивания? юзать моки, замокивая все вызовы «паровозика»? проверять что getTitle реально вернет тайтл?

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

Вы же даете код, который просите протестировать. Я используя тесты не приду к такому коду, потому как вы правы здесь:

сколько вам надо тестов? Надо подымать тестовый контекст, потому что бин something инжектится и внутри него могут быть сложные засечивания? юзать моки, замокивая все вызовы «паровозика»? проверять что getTitle реально вернет тайтл?

Используя тесты в начале. Я поставлю перед собой вопрос: «Почему я имея только foo, должен лезть в какие дебри и получить из него title baz-а?» Тут я почувствую запашок.

И попробую, для начала, написать тест, в котором something.getFirstFooBarTitle() должен будет возвращать то, что вы описали в примере. И реализую этот методом, например, вашим путем.

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

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

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

Это говорит лишь о том, что ваш код не написан в соответствии с принципами TDD :)

нафига тестировать эту строку?
я уж молчу о том, нафига так глубоко лезть за данными?
юнит тестирование предполагает независимое(с минимумом зависимостей) тестирование блоков.
то есть, отдельно будет тестироваться something.getFoo() на предмет каноничности результата. оно ж возвращает объект класса, так? который будет тестироваться отдельно — fooInstance.getFirstSomeItem(вместо магического get(0) — нафига вообще?) и так далее.
как только по отдельности блоки протестированы, можно и интеграционные тесты писать. но вместо странного и единственного приведенного кейса, лучше тогда уже небольшой модуль тестировать. то есть, опять же, класс, который эту самую строку, возможно, будет выполнять.

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

something.getFoo().get(0).getBar().getTitle()
Я просто оставлю это здесь:
www.ccs.neu.edu/...lieber/LoD.html

Отличный пример, кстати. Говнокод сложнее покрывать гранулярными юнит-тестами. Вот вам и профит от TDD.

Это тестировать скорее всего не надо. Во-первых, something — это непонятно что. Юнит-тест — декларация. Т.е. должно быть что-то осмысленное.
Да, я понимаю, что для примера упущено название. Но даже с названиями, что можно тестировать? Тесты скорее уже есть на каждый метод. Если методы могут возвращать null, то и одну эту строчку нет смысла тестировать, она с багами. Если не возвращают, значит есть уже тесты на методы.

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

Так что реальная такая строка если и тестится, то просто на ее наличие.

Надо еще понять, что 100 процентного покрытия тестами не бывает. Точнее так:

«Факт 33
Даже если бы 100%%тестовое покрытие было возможно, оно не годилось бы на роль критерия достаточности тестирования. Примерно 35% дефектов ПО вызвано пропущенными логическими путями и еще 40% связаны с выполнением уникальной комбинации логических путей. Их не выявить при 100% покрытии тестами.» Роберт Гласс «Факты и заблуждения профессионального программирования.»

Так что можно строку на возврат null каждым методом и зафиксировать, можно это не далать, т.к. есть тесты на каждый метод. Можно много чего, но без фанатизма.

Давайте с другой стороны. Какой смысл писать тест после того как код написан? Ведь вы же уже знаете, что код работает. Что вам это даёт?

Убедиться работает ли? Так написали же как-то. Хотя бы раз же запускали его? Верно? Значит уже уверены, что работает.

Код написан, значит оказалось возможным его написать. Всего лишь. Идея была правильная, от реализации не выворачивает и так далее. Работает ли? А вот напишем тесты и посмотрим.
Запускали, если вообще запускали, на одном-двух вариантах. Уверенности хочется для всех вариантов.

Потом это быстрая проверка на «не поломали ли».

Я к чему это спрашивал.

То есть мы пишем тесты после, что убедиться, что работает и отрефакторить код?

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

не могут они повторять код.
мы как раз пишем тесты, работая с кодом как с черным ящиком — знаем о его интерфейсах и всё.

Зачем тогда придумали моки, если мы тестируем черный ящик? =))

скидка на реальный мир.
во-1, некоторый код слишком дорого переписывать в угоду тестируемости.
во-2, от некоторых зависимостей слишком дорого избавляться. и проще подменять их незаметно для клиентского кода(например, завязка на AJAX).
точно так же можно спросить «а зачем нам багтрекер, если у нас не должно быть ошибок?»

Дело не в том, что они будут повторять код. А в том, что разработчик знает, как его код работает, даже если будет тестировать только интерфейс. И он теперь знает как его тестировать. И сможет даже схитрить.

А в случае, если тест написан «до». То есть четкие критерии, хитри, не хитри, придется написать код, который будет им соответствовать.

А в том, что разработчик знает, как его код работает, даже если будет тестировать только интерфейс.
скорее, что он должен делать.
вот, писал чувак бинарную сортировку. и он представляет, что должно получаться. но это не значит, что он намеренно написал >= вместо >.
потому вместо
схитрить
он берет граничные значения — null, пустой массив, отсортированный в обратном порядке массив, массив из одного элемента, уже отсортированный и массив с дубликатами. и пишет тест.
если уж совсем хитрожопый, то «схитрить» может, просто написав тест без тела. без assert’ов.
То есть четкие критерии, хитри, не хитри, придется написать код, который будет им соответствовать.
как-то за уши притянуто.
а что помешает схитрить с самого начала и написать тест, который будет просто проверять ненулевость результата? не, ну, а чо — тест сначала падал, а потом перестал, всё ок.
И сможет даже схитрить.
Это только в том случае, если тесты навязаны сверху и требуется «чтобы тесты запускались». Если делать тестирование для себя, на результат — то зачем хитрить? Можно забыть что-то протестировать, но намеренно — не вижу смысла.
И тест, который написан до реализации, так же может быть неверным.
А в том, что разработчик знает, как его код работает, даже если будет тестировать только интерфейс. И он теперь знает как его тестировать. И сможет даже схитрить.

Тут две совершенно разных проблемы.
1) Дев не особо заинтересованный в тестах, и пишет их «на отеб*** ». Возможно потому что написание тестов спускаеться сверху методологией, а сам дев немотивирован. Довольно частый случай. Но я не верю, что такой дев будет гораздо эффективней в случае тдд.
2) Знание кода «мешает» деву покрыть гораздо больше возможных вариантов. На самом деле это не «знание кода», а собственная недалекость. Потому я большой сторонник, чтобы окромя юнит тестов еще и присутствовали различные интеграционные, нагрузочные, автомейтед, и ручные куа тесты, написанные и выполненые другими людьми.
юнит тестов
Это пишет разработчик (роль)
еще и присутствовали различные интеграционные, нагрузочные, автомейтед, и ручные куа тесты, написанные и выполненые другими людьми.
Это пишет тестеривщик (роль).

Поддержу. Правда, имхо, интеграционные тесты обязанность разработчика (слишком уж они низкоуровневые, что там тестировщик натестирует по взаимодействию контроллера, модели и ОРМ). А функциональные — наверное обоих: основной поток пишет разработчик (а-ля BDD), а тестировщик покрывает всякие «имя на три символа длиннее фамилии».

>>работая с кодом как с черным ящиком — знаем о его интерфейсах и всё.

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

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

Это для любителей имитации бурной деятельности.

Вот это толсто, расшифровал бы, что имеешь в виду?

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

Раньше я вылавливал ошибки или рефакторил по несколько недель, искренне веря, что я работал, а не имитировал.

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

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

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

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

Т.е. «сначала тест, потом код» — это прямой способ кодирования. Сначала постановка задачи, потом решение. А работа наоборот — это «сначала что-нибудь напишу, в башке нафантазирую, как эти механизмы будут работать. И никому не буду говорить и фиксировать что я хочу, а потом оно точно будет делать то, что я придумал (но что я придумал, это тайна, которую даже выразить в тесте не с руки. Лучше сразу напишу как)». Где-то так.

Хм, как бы сделать вашим комментарий лучшим? =)) Самый лучший ответ на вопрос который я и задал в этом топике.

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

Выходит так, что садясь за клаву, Вы не знаете, что будете писать?

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

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

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

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

З.Ы. Чтобы не оказаться в заложниках этого опыта мне приходилось специально баги в код добавлять, чтобы давали время все сделать нормально. Когда баг, тогда грят, что править нужно.

Ничего не нашёл про «прототипное кодирование». Прошу ссылку на первоисточник.
С Ваших же слов получается, что производится попытка решить непоставленную задачу. Вот поэтому и выходит «непонятно что — непонятно как».

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

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

Зачем?

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

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

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

Короткий вопрос — а чем SCA не угодили?

Разное назначение. SCA ищет подозрительные места, которые могут вызывать ошибки в каких-то случаях, а тесты показывают, что в каких-то других случаях ошибок нет. SCA нам скажет «указатель не проверяется на null», а тесты скажут «когда указатель не null всё работает как задумывалось». SCA не знает и не может знать, что мы пытаемся сделать, он только выявляет подозрительные (не факт, что ошибочные) места. Тесты наоборот, знают, что мы пытаемся сделать в конкретных случаях, но не знают что мы хотим получить в остальных.

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

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

Потому, что SCA не анализирует динамику и семантику.

Даешь тесты без кода!

А что? Все остальное уже было, хочется чего-то нового_днего.

Где-то было исследование, что очередность теста и реализации не влияет на качество конечной кодовой базы. Ссылку не найду — сижу с телефона.

Сам часто пишу вначале высокоуровневый тест, а потом реализацию попеременно с мелкими тестами.

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

Kent Beck пишет, что TDD умерло еще в апреле этого года www.facebook.com/...750840194948847 Причины подробно описаны у него в посте. Я стараюсь практиковать BDD. Не всегда получается написать тесты заранее, особенно на не вполне очевидную фичу, которая вроде бы понятна, но еще будет кристаллизоваться в процессе.

Пост был написан с сарказмом ведь) Неужели не догадались?

Нет, не догадался, я склонен доверять китам индустрии, особенно создателю TDD.

У него куча пунктов «I need new techniques, I need to find a new way, I need, I need...»
Т.е. намекает, что TDD помогает ему в этом сейчас, а раз люди (david.heinemeierhansson) говорят, что TDD умерло, он пишет, что придется искать новые пути решения этих проблем.

Я увидел: «лошадь сдохла, надо слезть и найти шото новое», многим нашим такого подхода не хватает.

Он увидел то, что хотел увидеть. Любой адепт TDD поймет, что это просто стеб, не иначе.

Под BDD подразумеваете Gherkin с описанием фич и сценариев или спеки xSpec? Или и то и другое?

так вы не путайте бдд и инструменты с помощью которых бдд практикуют.

Это был сарказм на «вброс» DHH. Вброс стал катализатором многосерийной онлайн-дискуссии с участием того же Кента Бека. Стоило бы посмотреть, прежде чем утверждать, что «создатель TDD объявил о том, что лошадь сдохла». (facepalm)
www.youtube.com/...CiwKuDuzERXSU8m

Где-то так bit.ly/1tgvLRP
Для меня «сначала тесты» — это в первую очередь про «дисциплину», это нормальный инженерный подход (конечно немного теряется ощущение творца-художника, но, как по мне, это не повод делать свою работу непрофессионально).

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

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

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

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

Пишу одновременно с кодом.

Пишите пару ассертов, потом пол реализации, пару ассертов и еще пол реализации, так?

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

И вот когда первая версия поднялась, когда интерфейсы связи с другим кодом и сервисами худо-бедно ответили и не наругались на несоответствие типизации — уже идёт проверка и первый рефакторинг. Удаляются 100500 закоменченых огрызков кода, десятки переменных которые в итоге не понадобились, тестовые переменные и точки останова. Код переходит в крупноблочную стадию, после которой автор может вообще забыть детали реализации и даже передать код другому человеку. Потому что каждый крупный блок понятно что делает и чего от него ждать.

Соответственно, до перехода к стадии понимания блоков и отсеву мелких деталей — никакие тесты накладывать нельзя. Это всё равно что пытаться определить умственные способности ребёнка по УЗИ на третьем месяце.

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

Подвергать что-либо тестированию внутри блока — это бюрократический идиотизм. Почему? Да потому что разобраться в блоке просто читая его компактно — намного проще чем с обвесом в виде извращённой пунктуации и якорями тестов. Если блок не работает целиком — его перечитать целиком, а непонятные части — переписать. Даже если случилось так, что после прочтения не очевидно где ошибка — да, пишутся тесты, запускаются, но после обнаружения ошибки — все эти тесты УДАЛЯЮТСЯ. Потому что когда ошибка исправлена, контролировать её не нужно.

А когда будут вноситься изменения — то они будут вноситься в логику всего блока, и соответственно отразятся на тестах снаружи. И снова блок выгоднее держать «чёрным ящиком» и не трогать внутренности аж до тех пор, пока всё работает и внешние тесты говорят ОК.

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

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

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

+100
Я не уявляю якою примітивною є..ою треба займатися, щоб підхід «спочатку тести» працював.

Не представляю, какую поеб..нь нужно писать, что подход «сначала тесты» не сработал.

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

Давайте ваш аргумент, почему сложно писать тесты вначале?

Твои «сначала тесты» на неготовом коде будут просто СРАТЬ тебе мегатонны бесполезной инфы, и ты как последний клоун будешь просирать часы времени чтобы найти там иголку в стоге сена. А потом ты просто ПЕРЕПИШЕШЬ заново все тесты, потому что изменилась внутренняя логика. Притом лучше раньше чем позже. Позже тебе будет впадло и код не пройдёт ни одного рефакторинга.

Жестокая истина: не только «сначалатесты», а вообще все бюрократические подходы — не работают. Бюрократия — не работает. Она имитирует работу.

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

Твои «сначала тесты» на неготовом коде будут просто СРАТЬ тебе мегатонны бесполезной инфы, и ты как последний клоун будешь просирать часы времени чтобы найти там иголку в стоге сена.
А может стоит таки научится разбивать работу правильно на небольшие куски (юниты по пиндосски)? Та не это бред, вы же непогрешимый идеал :)

Так давай научись сразу код писать правильно и без багов — тогда вообще тесты не надо. PROFIT!

Так давай научись сразу код писать правильно и без багов
developerslife.ru/11715

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

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

Подозреваю, что про код без багов был сарказм))

Подозреваю, что про код без багов был сарказм))
Если это был сарказм, то в чем его смысл? Зачем оно было сказано?
Написать научится разбивать задачу на подзадачи — это так же реалистично как и сразу код писать правильно и без багов? Скорее это был не сарказм, а просто выпад в ответ (абы ляпнуть).

Был сарказм, абы ляпнуть.

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

Я junior coding monkey и мне TDD нужен для моей же дисциплины и для вправки мозгов.

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

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

И еще раз, я не буду переписывать все тесты. Я буду переписывать тест, который соответствует изменению логики. А так как есть Single Responsibility Principle, то я буду переписывать только один тест и только одно место в коде.

Если у вас тысячи мест однотипного кодирования — то да, имеет смысл. Я даже не удивлюсь если все эти тесты являются практически копипастой.

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

Общее правило таково: тест должен покрывать только узловые части, там где состояние известно. Если есть необходимость в промежуточном покрытии — есть смысл тестить прямо в коде, предсказывать появление ошибок и формировать поведение программы по факту их возникновения. Короче, делать всё чтобы в ДЕТАЛЬНОЕ кодирование лезть не было необходимости, и чтобы ПЕСЕЦ если есть — находился как можно раньше.

Позвольте с Вами не согласиться. Не в курсе что там с тестированием на Java т.к. норм. опыт у меня только на pure C (не думаю что есть критические отличия), но мое мнение — сначала unit тесты, потом код.

Соответственно, до перехода к стадии понимания блоков и отсеву мелких деталей — никакие тесты накладывать нельзя.
То что Вы описали в начале своего поста — это написание прототипа, затем переделывание его под релиз. На прототип тесты можно и не писать, но стоит оставить в покое и начать заного опираясь на полученный опыт.
но после обнаружения ошибки — все эти тесты УДАЛЯЮТСЯ
Смысл удалять актуальные тесты? Именно это является причиной страха перед рефакторингом:
не трогать внутренности аж до тех пор, пока всё работает и внешние тесты говорят ОК.
Если ваш код плотно покрыт тестами, вы можете рефакторить код без опаски, так как тесты покажут вам, сломали вы что-то или нет :)
PS. А если поступить наоборот и сначала наложить тесты — на выходе будет говнокод первой версии, который никто не станет рефакторить. Потому что придётся переписать все тесты, а кто это станет делать когда код уже работает?
Как раз тесты переписывать и нельзя если они верно отражают требования, в то время как рефакторить стоит.
В результате — имеем код с десятками лишних переменных, с непонятными комментариями к непонятным именам, с кусками кода которые непонятно что и зачем, но трогать их не моги потому что покрыты тестам.
Тесты никак не влияют на именование переменных, а непонятные куски кода должны фикситься автором, ровно как и неактуальные тесты.
И самое главное — когда у тебя тест хотя бы на 30-50 методов, то пускать его на слабом или недоделанном коде — каждый раз имеешь срач в логах, из которых извчлечь инфу — что и голку в стоге сена
Сталкивался с такими проблемами лишь тогда, когда что-то кардинально меняли в проекте, в таком случае могут и 50 тестов упасть, но как правило по одной причине, все зависит от контекста, но если в проекте порядок, все нормально продумали и разные части приложения не въедаются друг в друга то вернуть все в рабочее состояние не отнимет много времени и усилий.
Лично я не стараюсь сразу написать все тесты для функции. Написал тест -> он упал -> поднял тест, только после этого пишу следующий.
Если же писать код и лишь потом тесты — есть риск подгонять тесты под код. Кода Вы написали какой-то кусок, порефакторили, и вот он такой красивый, Вы довольны своей работой, пишите несколько тестов, один из них падает — скорее всего тест неправильный, я код трижды проверил, все ок, поправлю тест :)

а в джаве с тестовыми фреймворками все хорошо, Алексей просто некомпетентен

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

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

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

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

Если задекларирована именно разработка прототипа, то от давления менеджмента позволяет уйти его разработка на язііке/платформе, которіе не возьмут в продакшен. Например, если согласована разработка на php, то пишем прототип на рельсах и после апрува заказчиком с чистой совестью пишем на пхп.

с чистой совестью пишем на пхп.

Это оксиморон.

>

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

Я лично в таком варианте на стороне “давления” менеджмента.

>>Это оксиморон.

В смысле?

>>Я лично в таком варианте на стороне «давления» менеджмента.

Менеджмент просил прототип — он его получил. Прототип подразумевает что его код написан на коленке и продакшен пойти не должен. Менеджмент говорит «а давай ты его допилишь и запустим» вместо того чтобы нормально реализовать. Вы на стороне менеджмента?

В смысле?

В самом прямом. Хотя люди ухитряются и не такое реализовывать... :crash:

Прототип подразумевает что его код написан на коленке и продакшен пойти не должен.

Безусловно.

Менеджмент говорит «а давай ты его допилишь и запустим» вместо того чтобы нормально реализовать.

А вот тут у Вас прямая логическая подтасовка — в слове «вместо». «Нормально реализовать» и «допилить и запустить» это не антонимы. При нормальной реализации второе должно происходить через первое. Подходы же типа «всё выкинуть и написать заново» ведут к успеху только в том случае, если исходная подложка (язык, рантайм) были окончательно непригодны для продолжения проекта, и то — переписка в этом случае должна проходить методом последовательной трансформации операторов одного языка в операторы другого.

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

Подходы же типа «всё выкинуть и написать заново»
позволяет изменить кардинально архитектуру, а не просто перенести блок кода в другой класс.
maintainability, слышали?
не, ну, если приложение одноразовое и даже расширяться не должно — не то, что масштабироваться — тогда конечно, зачем переписывать?

Тогда просто пишется приложение, а не прототип.

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

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

Вы понимаете, что «изменить кардинально архитектуру» означает написание и проверку прототипа под новую архитектуру? Вдруг не пойдёт по причинам, которые до того не были видны (чаще всего — находится неустранимое узкое место)? А уже этот прототип должен превратиться в рабочий код.

Разумеется, всё это в том случае, если вас интересует результат, а не побольше потратить времени.

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

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

Ваше верхоглядское
выньте бревно из глаза.
да, переписывать систему, что уже «как будто работает» расточительно.
да, порою good enough просто продолжать работать над латаным-перелатаным.
но, если речь идет о прототипе — никогда-никогда оно не будет изначально и до конца продуманное. иначе это не прототип.
означает написание и проверку прототипа под новую архитектуру?
что такое прототип?
либо не уверены в функционале и пишем MVP. функционал будет заведомо не полный и, возможно, не гибкий вообще. зато можно обкатать идею.
либо не уверены в реализации и в итоге у нас сотня строк которые никогда-никогда не вызываются, куча дубликатов и полный хаос в структуре кода. зато можно выбрать наиболее подходящий под нефункциональные требования вариант.
либо не знаем нефункциональные требования и пытаемся определиться с подходящей архитектурой. тогда у нас костыль на костыле, дюжина зависимостей, от которых можно было бы избавиться и пара все еще узких мест.
какие еще варианты? а то я истощил фантазию.
А если расширяться — то тем более надо использовать старое, а не выкидывать бездумно.
больше драмы!
переписывать — это не выкидывать. откуда это заключение вообще?
это значит, мы берем алгоритмически сложные участки(20% кода, что несут 80% пользы, ха ха) и причесываем только их. а остальное выкидываем, а не пытаемся реанимировать труп.

PS

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

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

Во-вторых, пусть найден локальный оптимум (как во времени, так и в пространстве требований), назовём его даже тем самым «продуманным». И почему Вы тогда считаете, что прототип не может содержать основную часть необходимого?

да, порою good enough просто продолжать работать над латаным-перелатаным.

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

выньте бревно из глаза.

Извините, через терминал до Вашего глаза мне не дотянуться. Поэтому разбирайтесь с этим сами.

какие еще варианты? а то я истощил фантазию.

А хотя бы эти три. Какая по сути разница, какой из них, если они все осознаются в своих свойствах, могут быть проанализированы и в результате — спланированы дальнейшие действия? Бояться надо другого: [*] в принципе непонятно, как что-то делать, и даже куда начинать рисовать любой прототип; [*] отсутствия ресурсов на реализацию; [*] принципиальной технологической невозможности (хотя бы на нынешнем этапе). А всё описанное Вами это так, временные производственные заковыки...

значит, это будет уже второй прототип. ваш КО.

А, то есть, если не пошло, значит, это прототип. А если пошло, назовём разработкой. Понятно-понятно...

Но любое написание с нуля по определению дороже адаптации уже существующего, пусть даже требующего доработки.
Сплошь и рядом это «определение» нарушается. Существует даже понятие не то что write only code, но даже write only language.
Существует даже понятие не то что write only code, но даже write only language.

Даже на самых жестоких «write-only» языках, как Perl в режиме маркиза де Сада, не нужно переписывать эти специально завёрнутые куски, пока они не мешают.

>>люди ухитряются и не такое реализовывать

Какое «такое»? Или вы считаете, что на php ничего невозможно нормально реализовать в принципе?

>>При нормальной реализации второе должно происходить через первое.

Почему «должно»? Откуда «должно»?

>>если исходная подложка (язык, рантайм) были окончательно непригодны для продолжения проекта

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

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

Мотивация «не хочу говнокода в продакшене» не существенная?

Мотивация «не хочу говнокода в продакшене» не существенная?

Для грамотного рефакторинга и очистки от кривых решений локальными изменениями — более чем существенная. Для выкидывания всего написанного и написания с нуля — нет.

Почему «должно»? Откуда «должно»?

Из предыдущего моего абзаца в данном сообщении.

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

Значит, изначально было заложено выбросить деньги на ветер. Бывает.

Какое «такое»? Или вы считаете, что на php ничего невозможно нормально реализовать в принципе?

Не считаю. Ухитряются. Но цена за это чрезмерна.

>>Для грамотного рефакторинга и очистки от кривых решений локальными изменениями — более чем существенная. Для выкидывания всего написанного и написания с нуля — нет.

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

>>Из предыдущего моего абзаца в данном сообщении.

Не вижу логической необходимости.

>>Значит, изначально было заложено выбросить деньги на ветер.

Был заложен бюджет и время на сбор/уточнение требований.

>>Но цена за это чрезмерна.

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

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

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

Был заложен бюджет и время на сбор/уточнение требований.

И что, значит, нужно выбросить что-то сделанное только потому, что не гарантировался его результат? :)

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

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

>>Предварительное покрытие тестами

Речь не о разработке прототипа, а его рефакторинге в продакшен.

>>И что, значит, нужно выбросить что-то сделанное только потому, что не гарантировался его результат? :)

Мавр сделал своё дело...

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

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

Мавр сделал своё дело...

Что-то не похоже на аргумент.

это уже просто какой-то аджайл получается.

А в чём проблема? Я не вижу, чтобы где-нибудь в постановлениях XXIX съезда КПСС был запрет на разумные элементы аджайла в разработке.

>>Что-то не похоже на аргумент.

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

>>А в чём проблема? Я не вижу, чтобы где-нибудь в постановлениях XXIX съезда КПСС был запрет на разумные элементы аджайла в разработке.

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

Как говорится или трусы наденьте, или крестик снимите.

Мне пофиг на ваши религиозные заморочки.

Прототип — быстрая, черновая реализация будущей системы. © (вики)
Почитайте о прототипах, где применяются, как и зачем создаются.

Прототип — быстрая, черновая реализация будущей системы. © (вики)

1. Википедия не абсолютная истина и никогда ею не была. Практически всегда это лишь обзор канонической точки зрения о предмете статьи и ссылки для дальнейшего гугления. Как аргумент она гарантированно слабее профильной статьи.
2. И что собственно Вы хотели сказать этой цитатой? Что прототип не может быть доведён до боевого состояния без революционных действий типа «всё выбросить»? Что именно склоняет Вас к этой позиции и почему?

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

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

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

Что значит «прототип показал отличные результаты»? Для меня прототип — это часть процесса сбора требований. Ну, отлично собрали.

уже идёт проверка и первый рефакторинг. Удаляются 100500 закоменченых огрызков кода

Каким образом идет проверка? Как вы можете быть уверены, что все ОК, если удаляете 100500 строк кода и добавляете новые? Как вы проверяете, что все ОК?

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

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

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

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

Вы автоматизируете рутинные проверки потому, что в этом и видите суть тестов.

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

Как знать что является поводом выбросить нормальное исключение, а что — бага, до тех пор пока код не написан?

Тест до написания кода — это спецификация. Вот вам пример для Java jetbrains.github.io/spek

И это самый популярный камент? Мне стыдно за украинских программистов.

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

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

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

Не путай либы тестирования и реальное написание тестов которые останутся в продакшене. Правильные тесты приходится переписывать едва ли не чаще чем свежий код. По одной простой причине — дело теста не просто проверить, а проверять только то что нужно проверять. А то что проверять не нужно — проверять не нужно.

Я не путаю, я хотел сказать, что в языках на уровне библиотек предусмотрено. Вон в python есть uniitest либа. Только об этом.

Тесты переписываем только когда меняются требования.

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

Что дает вам право решать какие практики хорошие, а какие — нет?
Давайте говорить не об идеальном коде и не о хороших практиках а о результате.
Я уверен, найдется over 100500 отличных продуктов написанных без применения TDD, и единицы (почему-то о них тут вообще не упомянуто) с ним.

Что дает вам право решать какие практики хорошие, а какие — нет?

Да, я решил, что TDD — хорошая практика. Вот просто так, от фонаря. Что по вашему мне может запретить или дать такое право?

Я уверен, найдется over 100500 отличных продуктов написанных без применения TDD, и единицы (почему-то о них тут вообще не упомянуто) с ним.

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

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

Он к тому, что ваш комментарий против хороших практик и набрал большинство голосов. В этом печаль.

define «Бюрократия не есть хорошая практика» TRUE

Алексей, как тесты связана с бюрократией? Бюрократия — говно, согласен! НО причем здесь тесты?

Вы используете дебаггер? Статический анализ? Юниттесты такой же инструмент!

Не тесты. А ПРАВИЛА писать тесты, документацию, интерфейсы etc ДО реализации.

Обьясню в чём ключевая проблема: написав тесты к несуществующему коду — появляется спрос написать заглушки к некоторым из этих тестов. В результате — тесты и заглушки дружат друг с другом хорошо. А на самом деле эта «дружная компания» создаёт алиби коду.

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

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

Это самая обычная БЮРОКРАТИЧЕСКАЯ ловушка — пытаться предсказать результат и процесс со 100% вероятностью, не задумываясь о последствии нарушений.

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

Смотри: разработчик привык что есть 8 тестов которые не проходят, а будут проходить в конце месяца. Примерно к концу месяца таких «непроходящих» тестов стало 5. Если бы тесты длинных участков писались ПОСЛЕ — он бы понимал что все 5 давно кричат о багах. А теперь грабли: код пишет лид, а тесты отдал вчерашнему джуну. Станет ли джун беспокоить кого-либо из-за тестов, которые не проходили никогда? Нет! Кстати, об этих граблях рассказали даже не программисты, а... авиастроители. Как раз там тесты всегда утверждаются ДО.

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

А теперь грабли: код пишет лид, а тесты отдал вчерашнему джуну.
без код ревью?
какая бессмысленная смерть ©

Алексей, уважаю Ваши коментарии на доу, но в этом треде Вы как-то не правы. Зачем кричать о том, что это все бюрократия и тупые правила? Необходимо акцентировать внимаение на том, зачем вообще кому-то может понадобиться делать что-то до реализации. Если разработка происходить по принцыпу «huyak-huyak in production», то да — тесты вообще не нужны. Но если время есть, то начав писать тесты, документацию, интерфейсы etc ДО реализации, мы получаем первого КЛИЕНТА разрабатываемого программного компонента. Это ли не чудо? Смысл в том, что ранние тесты могут показать несостоятельность идей, корявость, неудобство использования, направить на путь истинный, помочь с реализацией. Не знаю, как в мире OOP, в мире функционального программирования, тесты не пишут, да-да их генерируют на основе рандомизации входящих данных. Таким образом, пропадает ситуация, когда

тесты и заглушки дружат друг с другом
а 8 тестов, покрывают 8 вариантов вызова и есть вариант с «посылом». Также, если тесты написаны в виде спецификаций, то это помогает быстрее вникнуть в работу модуля и понять что к чему:
The 'Hello world' string should
      contain 11 characters
      start with 'Hello'
      end with 'world'
ЗЫ. Имхо, если разработчик судит о работоспособности кода только по успешному прогону тестов без попыток разобраться — нужно либо переучивать, либо расставаться.
ЗЫЗЫ.
разработчик привык что есть 8 тестов которые не проходят, а будут проходить в конце месяца.
Мысль о том, что тесты, а значит и сборка всего проекта, не проходять целый месяц, заставляет меня думать что я сплю и это мне снится. Неужели такое возможно на реaльных проектах?

Хорошие тесты ПИШУТ, а те что генерируют — как правило не несут полезной нагрузки. Метод, реализующий «hello world» вообще не нуждается в тестировании — это хотя бы понятно? Даже если этот метод длиной в 600 строк — он нуждается в тестировании ровно один раз.

Куски кода, покрываемые тестированием, должны содержать внешнюю логику. То бишь не быть самодостаточными, а реализовывать человеческие решения. Если метод самодостаточен, что он делает понятно из его названия вроде getЧегоНибудь — его нужно сознательно выпиливать из тестов. Всего лишь строчка более круноблочного теста где это ЧегоНибудь используется.

Тот код, который пишется один раз, в который изменения не планируются — автоматом тестить смысла ноль. Потому что первый раз ты его сам проверишь, в последующие разы если кто-то вдруг решить менять [и ему это разрешат] — ты не знаешь куда он полезет и в чём суть изменений, другими словами тест и тогда не окажется полезен.

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

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

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

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

Хорошие тесты ПИШУТ, а те что генерируют — как правило не несут полезной нагрузки.
Господи, это же просто пример.
а те что генерируют — как правило не несут полезной нагрузки.
Генерируется же не тело теста, а входные данные для теста. Видно, Вы с этим никогда дела не имели. Таким образом тест может находить граничные условия, о которых девелопер не подумал и при которых код не работает. Профит же.
Тот код, который пишется один раз, в который изменения не планируются — автоматом тестить смысла ноль.
Интересно знать, это кто так умеет планировать изменения в коде? Ванга?
ты не знаешь куда он полезет и в чём суть изменений, другими словами тест и тогда не окажется полезен.
Вот это человек таки полез и поменял код. Случай, когда человек тест не трогал и тот валится сразу и сразу является полезным, направляя на путь истинный — тривиален. Допустим тест тоже подогнали под код. Есть же код ревью. При код ревью, адекватный ревьювер задаст себе вопрос, почему тест изменился(или был удален) и выяснит это у автора. Таким образом тест тоже будет полезен.
В остальные места где тестирование казалось бы и помогло бы — крайне сложно добраться автоматикой не наделав брешей в безопасности кода.
Имхо, такие места в коде называются код смелл, нетестируемы, имеют проблемы с зависимостями и должны, по мере наличия времени, переписываться. А если Вы не считаете нужным это делать и не обучаете этому других, то да, у вас появляются тесты-пустышки, лишь бы было и ситуации когда всем насрать и бюрократия.
но сколь-либо вменяемой копии данных и поднятых серверов на полигоне — нет. В результате некоторые тесты не проходят.

Если мы говорим об end-to-end тестах, то тут возможны варианты. При наличии QA — такие тесты может быть и не нужны. А если живых QA нет и тесты запускаются нерегулярно, или регулярно валятся — это бардак.

Генерируется же не тело теста, а входные данные для теста. Видно, Вы с этим никогда дела не имели.

Я имел. И не вижу никакой связи именно с «миром функционального программирования» (5 лет с Erlang, надеюсь, дают право сказать об этом). Задача или позволяет варьировать параметры, предсказывая выход (на основании каких-то известных данных, например, менее быстрого алгоритма), или нет. Если не позволяет — делать тут нечего. Если позволяет — ещё вопрос, как обеспечить это знание для генератора теста. Знаменитый PropEr, например, тут требует чуть ли не ИИ, чтобы внятно это описать.

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

Интересно знать, это кто так умеет планировать изменения в коде? Ванга?

Обычно опытный разумный программист способен делать краткосрочный прогноз такого рода с хорошей точностью (вероятность — до 20%). О долгосрочных (год и более) никто не говорит.

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

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

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

Сами себе выдумали какую-то бюрократию.

То есть подтверждается общеизвестное — эти принципы работают только если их нарушать.

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

Существует понятие Shu-Ha-Ri о трех шагах мастерства- ru.wikipedia.org/wiki/Сюхари
Уровень адаптации ТДД зависис от уровня Вашей просветленности, так сказать.

Существует понятие Shu-Ha-Ri о трех шагах мастерства-

В обсуждаемом случае первый шаг избыточен.

Уровень адаптации ТДД зависис от уровня Вашей просветленности, так сказать.

Мы достаточно просветлены: TDD не нужен.

И джун, который приходит к вам на проект, тоже просветлен? Или вы адепт WTF driven development’a ? :) Есть еще Exception Driven Development. Или Bug Driven Continuous Delivery :D

Или вы адепт WTF driven development’a ? :) Есть еще Exception Driven Development. Или Bug Driven Continuous Delivery :D

Не хочу отвечать на необоснованные домыслы.

И джун, который приходит к вам на проект, тоже просветлен?

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

Я имел ввиду, что, при отсутствии тестов, новый человек в команде будет чаще ломать существующий функционал, вносить регрессии и таким образом добавлять хаоса в проект. Оно надо? Более всего это проявляется с джунами, которые мнят из себя сениоров (что в наших широтах не редкость!) и меняют код с шашкой наголо (тестов-то нет). Отсюда имеем баги и ошибки времени исполнения, и WTF? со стороны остальных членов команды, сталкивающимися с кодом, который еще вчера работал. А еще имеем коммиты по 100500 правок в каждом, которые хрен отревьювишь!

Ну а разве непонятно,что таких можно выпускать только через что-то вроде gerrit для ревью плюс jenkins для автозапуска тестов? О чём собственно речь, если отсутствие этого — как отсутствие элементарной гигиены, и в приличном обществе не рассматривается?

Лично мне — понятно (ревью через Jira, билд в teamcity).
Речь о

Мы достаточно просветлены: TDD не нужен.

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

+100.

А тесты есть тогда, когда они пишутся до или параллельно с кодом.

-200. Или не проверяли на практике, или же у вас качество организации процесса ниже плинтуса.

Если код уже написан — велик соблазн тест не писать вовсе, так как проще один раз потестировать вручную.

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

код, в который изменения не планируются — автоматом тестить смысла ноль.

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

-200. Или не проверяли на практике, или же у вас качество организации процесса ниже плинтуса.

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

Похоже, что вы не понимаете зачем нужен TDD/BDD и не понимаете как это работает.

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

Не апеллируйте на основе своего N-летнего опыта, он вам тут больше мешает.

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

Не правы. А почему неправы — поймёте, когда перестанете вещать из позы очередных Свидетелей Пришествия Скрама (или как там оно у вас сегодня называется) и осмыслите, что я писал в соседних комментариях и темах. Это ж надо — «ретроспективу не провели»...

Не апеллируйте на основе своего N-летнего опыта, он вам тут больше мешает.

С «Credo quia absurdum» — пожалуйста, в церковь.

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

Уже задолбали, перечитайте вопрос в теме. Этот топик только для последователей секты TDD/BDD, если вы не практикуете, то зачем ломитесь навязывать свое единственно-правильное мнение.

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

Очнитесь! Применять нужно только то, что работает и позволяет решать задачу эффективно. Нужно быть рациональным, а не темным средневековым человечком. Не работает у вас TDD? Так и не применяйте, работайте так как получается. Главное, что вы решаете поставленные перед вами задачи.

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

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

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

Я несколько прочитал ваши комментарии и понял, что вы не знаете ответа на вопрос: «Почему нужно писать тесты сначала?»

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

Почему я решил, что вы не можете допустить, что вы глупы и наивны?

Мы достаточно просветлены: TDD не нужен.

У вас не работает TDD/BDD, так и не используйте. Все очень просто. Еще раз повторюсь и вы сами это знаете, используйте только то, что подходит именно вам. Нах..р приходить сюда и навязывать мнение, что TDD/BDD плох. Люди же увидят у вас в профиле должность и могут поверить вам. Это очень опасно.

У вас есть шанс исправиться и признаться, что вы не понимаете и не умеете TDD/BDD. В этом нет ничего страшного.

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

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

Это не делает его неверным.

Этот топик только для последователей секты TDD/BDD

Докажите. Я не вижу никаких аргументов для подобного вывода.

Очнитесь! Применять нужно только то, что работает и позволяет решать задачу эффективно.

Ура! Вот поэтому всякие TDD — в сад.

вы не знаете ответа на вопрос: «Почему нужно писать тесты сначала?»

Да, точно так же как я не смогу ответить на вопрос «Вы перестали пить коньяк по утрам?»

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

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

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

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

Нах..р приходить сюда и навязывать мнение, что TDD/BDD плох.

Во-первых, дорогой оппонент: Вы нагло врёте. Сейчас я это говорю открытым текстом. По пунктам:

1. Я не говорил, что TDD плох. Я говорил, что это методика, которая крайне ограниченна по возможностям в своём чистом виде и работает за пределами этой области, только если нарушать какие-то её каноны, и приводил чёткий список проблем, которые она не решает, и ситуаций, когда она не годится. Разницу чувствуете? «Плохо» — это Ваши эмоции. У меня — аргументация и факты.

2. Я ни словом подобное не высказывал в отношении BDD, которую, замечу, считаю (без обязаловки на test first и прочие TDD-шные задвиги) достойной и адекватной методикой на широкий набор случаев. То, что Вы приписали мне какое-то «плохо» про неё, показывает, что Вы крайне невнимательно читали мои сообщения.

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

Люди же увидят у вас в профиле должность и могут поверить вам. Это очень опасно.

Для Вас и Вашей секты? Да, возможно, опасно. Готов полностью поверить. Но это замечательно — вокруг и так слишком мало думающих людей, а население растёт.

Для развития IT в целом — это будет полезно. Поменьше предвзятости и евангелизма, побольше разумных решений, а нам ещё на сотни лет есть куда расти.

У вас есть шанс исправиться и признаться, что вы не понимаете и не умеете TDD/BDD. В этом нет ничего страшного.

Нет, это у Вас сейчас крайне актуален шанс
1) понять, что же пишет собеседник,
2) если уж хочется дискутировать, то для этого хотя бы прийти в сознание,
3) наконец, всё осмыслить и понять, где же что работает и почему. я не предполагаю, что Вы придумаете что-то новое, но хотя бы начать применять известное сознательно, а не с иконой наперевес — обычно помогает.

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

Самокритика — это хорошо. Но публичное самобичевание — уже зависит от ситуации, а когда оно используется как провокация на откровенность — извините, как говорят американцы, I don’t buy this.

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

Спасибо за ваш комментарий. Заставили меня задуматься. Я действительно где-то перегнул палку, осознанно или нет. Извините, если может и оскорбил где.

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

вокруг и так слишком мало думающих людей

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

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

WTF driven development’a ? :) Есть еще Exception Driven Development. Или Bug Driven Continuous Delivery :D
Кстати сказать, в некоторых конторах данные методологии работают несколько продуктивней чем скраможайл :-)

Подписаться на комментарии