Пишете ли вы unit тесты?

После общения с коллегами назрел вопрос, в идеале вообще бы провести опрос. Всегда ли вы пишете unit тесты или если сроки поджимают, то забиваете на них?

LinkedIn

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

Сталкивался с ситуацией на проектах, когда начальство требовало покрыть существующий г*внокод тестами, потому что (внимание), они сказали заказчику, что мы работаем по TDD. А то, что к нам пришло около миллиона строк кода без единого теста — умолчали. Мы осилили порядка 6-000 unit-тестов за 8 месяцев достигнув покрытия 8%....
Потом начались проблемы — малейшая правка в коде и 1/3 или половина тестов на класс тут же падает, поменял зависимость — ж*па почти всем тестам, код даже не компилится и не находит методы.

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

Тем не менее я для себя решил, что качественные тесты очень важны- и поэтому всегда пишу тесты на свой back-end перед самим кодом, если шаманю с базой, юзаю DB-unit, если бизнес-логика — то классический JUnit, пробовал кактус для контейнера — как-то не очень, лучше писать тестируемые loose-coupled управляемые Spring-ом контроллеры и мокать зависимости, никуда не деваются Mockito, PowerMock. На JS тестов не пишу.

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

Всегда ли вы пишите unit тесты или если сроки поджимают, то забиваете на них?
Сама постановка вопроса показывает непонимание правильного процесса разработки.
Она подразумевает что разрабатывать с юнит-тестами дольше, а если сроки поджимают — то можно пожертвовать тестами и написать быстрее. Ясный пень что при таком подходе юнит тесты будут считаться не помощью, а обузой и на них начнут забивать.
А ведь идея юнит-тестов как раз в том, что бы помогать, сокращать время разработки и повышать качество!
В первую очередь — за счет сокращения времени на дебаг. Даже если быстро написали код — его надо запустить хотя-бы раз перед чекином. Сколько займет времени запустить все приложение, дойти до нужного места, ввести нужные данные, нажать кнопочку? А если с первого раза не сработало — исправили одну букву и все по новой: запускаем, доходим, вводим, нажимаем ... Пускай заработало — но теперь по-хорошему надо еще проверить не поломали ли мы чего рядом. Это выливается в небольшую сессию ручного тестирования (не самая приятная работа для девелопера). Теперь если посчитать время на «ручную проверку» то окажется что написать и запустить несколько тестов было бы намного быстрее, а для чужого кода уже есть тесты и сразу видно что поломалось. И главное — писать код и быстро его запускать девелоперу намного приятнее, чем запускать все приложение и вручную клацать и набивать данные.
Остальные плюсы юнит-тестов не буду расписывать: все они много раз описаны и разжеваны и приводят только к увеличению скорости разработки (быстрее понимать чужой код, меньше багов фиксить, проще дебажить и т.д.).
НО — есть один подвох! Такими удобными бывают только правильные юнит-тесты. А именно: небольшие, понятные, атомарные и не требующие поддержки. Это тот самый Open-Closed принцип из SOLID: код написанный и протестированный однажды закрыт для изменений, но открыт для расширения и повторного использования. Это значит, что новые тесты пишутся только вместе с новым кодом (никакого «покрытия существующего кода тестами»!) и потом практически не требуют изменений.
И для того, что бы можно было писать такие правильные тесты изначально архитектура приложения должна быть правильной. Т.е. состоящей из небольших, слабо-связанных через интерфейсы классов, каждый из которых легко заменить моком.
Если кто-то занимался хотя-бы немного радиотехникой — то аналогия очень хорошая. Радио-детали (резиторы, емкости, кондеры, диоды, транзисторы) — это наши простые, надежные и протестированные классы (у каждого есть четкая спецификация). Юнит-тест: это прибор, которым можно проверить любую детальку. Схема — это наши UML диаграммы, печатная плата с дорожками — это композиция наших классов (например конфигурация инджершин-контейнера). Интеграционный тест — это подключили осциллограф к готовой схеме и проверяем кривую... Что-то я отвлекся.
Так почему же многие считают, что разрабатывать с юнит-тестами дольше и неудобнее? Да потому, что они пытаются написать эти тесты для уже написанного кода! Который изначально писался без всякой мысли о том, что его будут тестировать юнит-тестами. Вот это — однозначно бессмысленная трата времени. Тест для класса в 1000 строк будет занимать все 3000 строк (подготовка данных, проверки, попытки подсунуть стабы и т.д.). Мало того: этот тест придется постоянно менять при любом изменении (даже в «соседних» классах).
Я не фанат TDD, думаю тесты можно писать и после кода. Они нужны именно тогда — когда вы хотите свой код запустить. Но если вы уже написали и проверили код руками — то тесты писать поздно (и наверняка будет дольше). В крайнем случае — пишите тесты когда рефакторите старый код делаете его «чистым», но не «покрывайте тестами» спагетти-код, это «обезьянья работа» которую делают в основном для очковтирательства что у нас есть 100% покрытие (закопать свое Г в песочек, как котик).
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Хорошо в Go сделали, там юнит-тесты — это часть процесса разработки кода. В любом модуле можно положить файл, оканчивающийся на *_test.go с тестами, и при внесении каких-либо правок в коде быстро прогнать все тесты через команду go test.

Инноваторы однако. И как они до этого додумались.

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

пишу NG-тесты

Да, пишу и стремлюсь к BDD и стараюсь тестировать на уровне интерфейс. Вижу смысл не в том, чтобы покрыть каждую строку кода, а в том, чтобы протестировать наиболее вероятные кейсы и флоу, обычно это как минимум 1 happy path и 1 очевидный failure. Не раз спасали от неприятных ситуаций. Не спасали только тогда когда спешил и не покрыл или забыл написать или гнался за покрытием строк, а кто-то другой поменял код и перестало даже компилится. Вот так.

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

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

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

Я повторюсь: речь не о том что не додумались, речь о том что допустили ошибку, это две большие разницы !
Интеграционное тестирование куда более затратное. Ручное — совсем из другой оперы, я бы на него особо не разчитывал.

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

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

Такого еще не происходило за последние пять лет. Хорошо, есть ф-я, на входе объект данных юзера, на выходе — появляется запись в БД, происходит обращение по API и т.п. Какие тесты будете писать?

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

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

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

Расширение этого комплекта может идти в нескольких направлениях:

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

2. Эмуляция отказа потенциально ненадёжного участника (в указанном примере это БД — может порваться сеть по дороге, может быть переполнение соединениями, отказ диска, etc.) и проверка положенной реакции тестируемого кода на эти ситуации, даже если это банальное «мы такие исключения не ловим, нам только зачистить локально выделенные ресурсы».
Тесты такого рода необходимы для контроля надёжности системы.

3. Инверсия тестов методом искажения входных данных (выходные должны перестать соответствовать ожидаемому). Неположенное положительное прохождение теста сигнализирует или о проблемах тестовой среды, или о сложных нарушениях логики кода.
Тесты такого рода желательны в разумном (обычно небольшом) количестве и могут сходиться с пунктом 2. (У меня они срабатывали, кажется, 3 раза, и каждый раз это вскрывало серьёзную внутреннюю проблему.)

4. Рандомизированные тесты с хотя бы приблизительным предсказанием ожидаемого результата. Допустимы при условии, что тестовая среда способна при отказе теста зафиксировать точные входные данные для ручного повторения. Являются эффективными для поиска проблем, проявляющихся на редких входных шаблонах. Два примера:
* хрестоматийный — хэш-таблица с ограниченным количеством коллизий на один внутренний индекс (2 влезло, 3 — уже нет).
* комбинация данных привела к порождению NUL в транспорте, который формально двоичный, но конкретный кусок кода приёмника использовал неподходящее API (кажется, strlen).

Кроме того, в системах с внутренними временны́ми зависимостями ловят ситуации типа неправильной реакции на таймаут.

Другие, более специфичные для стресс-тестирований и тестирований на приёмку уровня военка/космос/etc., пропущу, потому что там ещё много можно говорить, но они вряд ли пригодятся хотя бы 1% читающих.

Хорошо, есть ф-я, на входе объект данных юзера, на выходе — появляется запись в БД, происходит обращение по API и т.п. Какие тесты будете писать?
Подставляются целевые заглушки-эмуляторы (в английском обычно используется термин mock) вместо доступа к БД и интерфейса соответствующего API.

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

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

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

Которые из?

Они будут падать, когда просто поменяется контракт вызов компонентов

Чем это отличается от любых других тестов, которые обращаются ко внутренним компонентам, или внешним, в которых зафиксирован контракт на API?

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

Зависит от того, как эмулируется база. Конечно, лучше использовать более высокоуровневый эмулятор. Например, в ряде тестов у нас используется sqlite, когда в продукции база на другом хосте. Это лучше, чем считалочка типа «раз — delete, два — select, три — insert». Но считалочка поможет проверить, например, порядок обращений в том случае, если он критичен для правильной изоляции текущей транзакции.

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

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

А про вызовы API вы забыли?

Которые из?

Тестирование вызова замоканных контрактов при сохранении юзера в базе.

Чем это отличается от любых других тестов, которые обращаются ко внутренним компонентам, или внешним, в которых зафиксирован контракт на API?

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

Зависит от того, как эмулируется база. Конечно, лучше использовать более высокоуровневый эмулятор. Например, в ряде тестов у нас используется sqlite, когда в продукции база на другом хосте. Это лучше, чем считалочка типа «раз — delete, два — select, три — insert». Но считалочка поможет проверить, например, порядок обращений в том случае, если он критичен для правильной изоляции текущей транзакции.

Тоже самое, что и выше — какая разница какой уровень изоляции? если у вас есть тест на concurrency или негативный тест на strong consistency данных — какая вам разница с каким уровнем изоляции она достигнута — read committed или serializeable? Вам важен результат — наличие консистентных данных.
К чему я это, надо следовать принципу, что изменение кода не должно влечь постоянную переработку тестов, если функциональность удовлетворяет требованиям.

А про вызовы API вы забыли?

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

Тестирование контрактов как вид тестирования в определенных случаях определенно имеет смысл, но не в этом.

Я ничего не говорил о тестировании контрактов. Зачем вы его сюда привлекаете?

Если бы вы проверяли не как контракт вызван, а что сохранено в базе(какие поля в таблице, какие данные в них)

А с чего вы решили, что в моём начальном сообщении что-то этому противоречит?

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

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

Тоже самое, что и выше — какая разница какой уровень изоляции? если у вас есть тест на concurrency или негативный тест на strong consistency данных — какая вам разница с каким уровнем изоляции она достигнута — read committed или serializeable? Вам важен результат — наличие консистентных данных.

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

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

И опять домыслили, что от вызова API результат попадёт в базу. С чего вы это взяли и почему?

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

Если бы вы проверяли не как контракт вызван, а что сохранено в базе(какие поля в таблице, какие данные в них)

А с чего вы решили, что в моём начальном сообщении что-то этому противоречит?

Ну вот как бы, у вас написанно, что вы предлагаете замокать базу и проверять с какими параметрами вы вызываете методы ДБ клиента(значит проверять не состояние в базе, а способ вызова).

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

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

.

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

Я думаю вы просто не правильно поняли автора.

Вот как оригинально ставился вопрос:

В общем случае может и так, я говорил о своей специфике. Пока же не нашел достаточных оснований для использования юнит-тестов для своего случая (специфичные проекты в области серверного js)
Хорошо, есть ф-я, на входе объект данных юзера, на выходе — появляется запись в БД, происходит обращение по API и т.п. Какие тесты будете писать?

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

Ну вот как бы, у вас написанно, что вы предлагаете замокать базу и проверять с какими параметрами вы вызываете методы ДБ клиента(значит проверять не состояние в базе, а способ вызова).

Я не уточнял, что это про БД.
Но отвечаю сейчас — возможно, и так нужно. Зависит от того, нужен этот порядок вызова или нет. Часто — нет, но бывает, что нужен. Тестами его надо проверять, если он важен и не очевиден напрямую из кода.
Бывает, что важен на каком-то более высоком уровне. Например, стадия 1 требует, чтобы сущность была добавлена и нотификация об этом послана, а 2 — чтобы была модифицирована и тоже нотификация послана. Тогда важен порядок между стадиями, но не в каждой конкретно.

Повторюсь, что тут, как во всём, важны мера, норма и предел. Универсального прокрустова ложа не будет и не должно быть.

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

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

Это практически основная цель этих тестов. Если тебе или кому еще придется править этот код, то UT сообщат о том, что что-то не так. И или тест правишь или ошибку.

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

Зачем править код функции, если после этого она будет проходить старые тесты?

Оптимизация времени выполнения?

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

Тестирование требует, чтобы вы рассчитывали найти ошибки в своем кодe.

Спасибо за пополнение чёрного списка.

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

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

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

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

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

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

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

Если у Вас нет возможности самому выбирать себе подчинённых — сочувствую.
Если такая возможность есть и Вы их получили не вчера — то почему до сих пор не привели в порядок?

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

И, наконец, зачем проблемы с вашими подчинёнными возводить в общий принцип?

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

Да, это мощный аргумент против REPL и интерпретируемых языков. Уважил, запомню на будущее, хоть сам их использую в 99% своей практики:)

И ещё интересно, как Вы попытались вывернуть против меня мои примеры (это именно тут я приводил пример с умножением?) Только вот один момент есть. Умножение, деление — операции хоть и сложные в реализации, и имеющие хитрые подводные камни (типа INT_MIN/(-1)), но понятные всем, и воспроизвести тестовые примеры достаточно просто. (Кстати, не знаю, зачем Вам особое деление в JS.) А если что-то более хитрое вроде построения триангуляции для области? Или (ещё один мой любимый пример) паркинг и трансфер одновременно с двух сторон установленного звонка? Сама работа по придумыванию граничных случаев, которые требуют особых действий по реализации, может быть сравнимой по затратности с реализацией основного кода. Вы спокойно относитесь к тому, что выкидываете результат этой работы в мусор (а иначе нельзя это назвать, потому что уже через год всё забудется)? Если нет, то как документируете эти продуманные случаи, и почему это документирование не выглядит в виде тестов, которые можно в любой момент запустить и убедиться?

Считайте это юнит-тестами в ручном режиме.

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

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

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

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

Аналогично.

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

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

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

Но не у вас, да? И при этом подчинённые всё равно их пишут и в них нагло мухлюют?

Но никто не требует писать их всех сразу (тут я, как могли заметить, резко расхожусь с TDD).
Это трактовка TDD «писать их всех сразу» в стиле «заставь дурака богу молиться...»

Это всего лишь прямая трактовка его постулатов. В том, что они приводят к нерабочему методу, а работает только ересь, я не виноват:)

Ну, именно

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

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

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

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

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

upd.

И ещё интересно, как Вы попытались вывернуть против меня мои примеры
К сожалению не читал, не было времени листать весь тред) Я отвечал лишь на комментарий про чёрный список теми примерами, которые пришли на ум)

Холивар так холивар. Вброшу и свои пять копеек.

Я не пишу юнит-тесты. Я пишу код, который правильно работает.

Я не пишу юнит-тесты. Я пишу код, который правильно работает.
developerslife.ru/11715

скоріше так:
http*://*.ru/* =>furnace

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

“да ладно вам, полно хороших руских военных, тот же *** чего стоит”. після війни — розберемся, а наразі все в топку ;)

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

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

Не сдача? не предательство? вы издеваетесь?
1) а кто договорился про коридор в Иловайске, а потом устроили мясорубку, 700 в плену и 800-1000 убиты.
2) 200 камазов ездят туда-сюда, черти что ввозят и увозят
3) замалчивание инфы о вторжении, которое состоялось 27-го, по словам МВД 24-го, сказали про него народу лишь 29-го.
4) В ответ на явную агрессию не вводится военное положение (а то вдруг выборы не состоятся).
5) Солдатам не помогают тяжелой техникой, не отправляют регулярную армию
6) Главы фракций в парламенте делят портфели опасаясь отмены выборов.

Откройте глаза и посмотрите кого вы выбрали на золотой унитаз"престол"

Похоже, срач на тему юнит-тестов зашел куда-то не туда...

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

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

ещё и в сроки всегда попадаете?

А вот это — нет.
«могу сделать быстро, качественно, недорого — выбирайте любые два» ;)

«могу сделать быстро, качественно, недорого — выбирайте любые два» ;)
Что если заказчика устроит «качественно» + «недорого» ?

Певно, в такому випадку багрепорт до релізу піде замовникові на надгробну плиту замість епітафії.

Чего-то вспомнилось:


— Заказы заказывал, материалы и оборудование использовал, отдельное помещение занимал, два года работал — и вот, нате вам, пожалуйста! — распалялся от собственных слов Хилобок. — А как он на защите-то... ведь не только меня он осрамил — меня-то что, ладно, но ведь и вас, Аркадий Аркадьевич, вас!.. Вот будь на то моя воля, Аркадий Аркадьевич, я бы этому Кривошеину за то, что он такое сотворил ухитриться... то есть ухитрил сотвориться — тьфу, простите! — сотворить ухитрился... я бы ему за это!.. — доцент навис над столом, в его карих глазах сиял нестерпимый блеск озарения. — Вот жаль, что у нас принято лишь награждать посмертно, объявления да некрологи всякие," де мортуис аут бене, аут нихиль ", понимаете ли!.. А вот вынести бы Кривошеину выговор посмертно, чтоб другим неповадно было! Да строгий! Да с занесением...

— ...на надгробие. Это мысль! — добавил голос за его спиной. — Ох, и гнида же вы, Хилобок!

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

Почему сразу «что если» — у нас же так половина аутсорса работает. Заказчики не жалуются.

Ну естественно, с багами ведь в продакшен не пускают.

Каким образом Вы доказываете, что код правильно работает? (Я имею в виду не математические методы, а убеждение начальства и заказчиков)

Если мои changes прошли через все круги ада высокоуровневых тестов (functional, system, regression, stress) и проблем не найдено — можно считать, что их и нет.
И даже если находится какая-то исключительная ситуация, работающая неправильно (не так уж часто бывает) — быстрее взять и пофиксить, чем пытаться предугадать и покрыть все такие ситуации юнит-тестами.
В общем, я считаю что тестировать код — трата времени, которая не окупается. Полностью автоматизированного high level testing вполне достаточно.

У Вас в перечне навыков на LI присутствует и UT, и TDD. Как же это там оказалось, если вы считаете, что «тестировать код — трата времени»?
Можно, конечно, предположить, что Вас заставляли делать по TDD из-под палки, но история небогата случаями успеха при таком подходе, а значит, вы, скорее всего, имеете превратное понимание технологии.
Поправьте, если ошибаюсь.

Предполагать можно что угодно.
На самом деле я попробовал и разочаровался в этой методологии. Она несовместима с моей практикой упрощающего рефакторинга. Нет смысла писать юнит-тесты и проектировать дизайн 5 классов и 2 интерфейсов, если завтра придет идея как выкинуть эти классы нафиг и реализовать то же самое парочкой простых C-style функций по 20 строк, или вообще одним вызовом библиотеки типа Boost.

Нет смысла писать юнит-тесты и проектировать дизайн 5 классов и 2 интерфейсов, если завтра придет идея как выкинуть эти классы нафиг и реализовать то же самое парочкой простых C-style функций по 20 строк, или вообще одним вызовом библиотеки типа Boost.
А если такая Вам придет не завтра, а через полгода, когда половина системы будет с успехом использовать эти 5 классов и 2 интерфейса? Как тогда впилить вместо них вызов Boost?
Только не вздумайте сказать, что все должно быть в духе OOD спрятано за абстракцией, т.к. именно эту абстракцию и стоило разрабатывать по TDD, а всё перечисленное (C-style функции, Boost и т.д.) воспринимать как детали реализации, которые по определению не должны влиять на API.

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

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

«Неделание» чего-то всегда экономит время.
Мы — инженеры и наша святая обязанность отвечать за качество нашей работы, а как я могу отвечать за то, в чем не могу быть уверен ввиду отсутствия формального или эмпирического доказательства? Для этого и нужны тесты: модульные/компонентные — если речь идет о корректности кода, интеграционные/приемочные/пользовательские — если речь идет о корректности системы на глобальном уровне, соответствии требованиям бизнеса и соответствии правилам предметной области.

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

Она несовместима с моей практикой упрощающего рефакторинга.
А как вы знаете что вы ничего не сломали в процессе рефакторинга? А когда узнали что что-то сломалось, то как узнаете где оно сломалось?

CI подсказывает. Так как количество кода на каждый элемент функциональности стремится к минимуму, выяснить почему отпал функциональный тест очень легко.
И да, если есть юнит-тесты, то при удалении самого тестируемого API они тоже уничтожаются, а потому не могут подсказать место регрессии.

CI подсказывает.
А как он узнает? ;)
Так как количество кода на каждый элемент функциональности стремится к минимуму, выяснить почему отпал функциональный тест очень легко.
Так бы и сказали, что пишете «сайтики-визитки», для такого рода систем ТДД и тд таки оверинжениронг.
Так бы и сказали, что пишете «сайтики-визитки»
Угу, на С++ :)
«сайтики-визитки»
Угу, на С++ :)
Жыр!!! )))
А как он узнает? ;)

По падению функционального или интеграционного теста.

Так бы и сказали, что пишете «сайтики-визитки», для такого рода систем ТДД и тд таки оверинжениронг.

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

Всё именно так.

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

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

А пока одни следуют за трендами, другие эффективно работают по своим собственным правилам.

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

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

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

Считаю, что необходимо объяснять, а не доказывать. Предмет разговора не настолько формален, чтобы говорить о возможности доказательства.

иногда. иногда отлаживаю на потом

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

Когда падает тест:
вы первым делом
ищете баг
или
фиксите тест?

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

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

Это означает что догма «надо тесты» — иногда не работает так как нужно.

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

разработчики разработчикам рознь.

Ищу баг. Частые правки тестов — признак их «хрупкости», что есть антишаблон, сигнализирующий о наличии проблем в дизайне кода [тестов].

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

Сталкивался с ситуацией на проектах, когда начальство требовало покрыть существующий г*внокод тестами, потому что (внимание), они сказали заказчику, что мы работаем по TDD. А то, что к нам пришло около миллиона строк кода без единого теста — умолчали. Мы осилили порядка 6-000 unit-тестов за 8 месяцев достигнув покрытия 8%....
Потом начались проблемы — малейшая правка в коде и 1/3 или половина тестов на класс тут же падает, поменял зависимость — ж*па почти всем тестам, код даже не компилится и не находит методы.

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

Тем не менее я для себя решил, что качественные тесты очень важны- и поэтому всегда пишу тесты на свой back-end перед самим кодом, если шаманю с базой, юзаю DB-unit, если бизнес-логика — то классический JUnit, пробовал кактус для контейнера — как-то не очень, лучше писать тестируемые loose-coupled управляемые Spring-ом контроллеры и мокать зависимости, никуда не деваются Mockito, PowerMock. На JS тестов не пишу.

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

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

це по нашому!!!
назву говноконтори в студію

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

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

еще тесты помогают при «аджайле», то есть когда нет серьезного проектирования

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

Взял за правило писать unit testable код, а сами тесты в C# с его типизацией пока писать не довелось.

Бывает по разному.
Иногда проще написать юнит-тест, чем проверить руками, получается даже экономия времени.
Иногда написание теста может занять в 2-3 раза больше времени чем тестируемого метода, тогда в зависимости от наличия времени.
Иногда тест пишется только для 1-2 случаев чтобы «краснело» если потом что-то сломают.

Иногда написание теста может занять в 2-3 раза больше времени чем тестируемого метода, тогда в зависимости от наличия времени
Перевод:
Переиспользовать ваш код займет в 2-3 раза больше времени написать заново.
И в такой формулировке — это гигантская проблема для проекта не однодневки.

Как пример такого в .net можно привести тесты атрибутов:
stackoverflow.com/...t-to-login-page
jarrettmeyer.com/...orize-attribute

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

Вводная: Я не дотНет разработчик, с АСП.НЕТ не работал (или это АСП.НЕТ МВЦ), с шарпом лет 5-7 назад немного работал.
Взгляд со стороны:

jarrettmeyer.com/...orize-attribute
Проблема № 1 (Основная). Тесты по факту интеграционные, если я правильно понял, они проверяют как будет работать в среде фреймворка. Такой вывод сделан, например, потому что AuthorizeCore не вызывается на прямую. По причине описанной выше, могу ошибаться.
Проблема № 2: Код — гуано! Тесты вроде как так же гуано.
Если вынести получение httpSessionAdapter, то его можно спокойно замокать что сильно бы упростило тесты на «не авторизован». Снова же проблема в том что человек тестирует связку SessionBasedAuthorizeAttibute (его SUT) и AuthorizationContext (код предоставляемый фреймворком, на сколько я понял)
stackoverflow.com/...t-to-login-page
Тут как раз более-менее правильно описано (на сколько я понял): Просто проверьте что установлена аннотация (атрибут). 5 строк которые можно еще вынести в утилиты (если подобные проверки надо часто делать). Лично я бы такое не тестировал юнит-тестами, тут больше бы подошли честные УИ-тесты.
Еще случай — когда простая задача и код, но много разных крайних случаев которые нужно проверять, даже «решатор» для банального квадратного уравнения
Ну таки да, граничные условия надо проверять. На мой взгляд записать их займет не в 2-3 раза больше времени чем вспомнить/найти формулу которую надо реализовать :)

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

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

Компоненты pipeline Asp.net MVC покрывать юнит тестами практически всегда трудоемко и более того бессмысленно напрямую. Что вы хотите проверить? Порядок вызова сервисов? проверят это надо не Unit тестами.
Логику можно и нужно выносить в сервисы, как UserProvider в примере с атрибутом и создание IPrincipal по переданному UserName — ее уже покрывать юнит тестами.
Сами обращения к компонентам MVC pipeline обычно не должны прямо содержать этой логики в себе, если есть желание протестировать корректность обращения к этим сервисам для конкретного запроса и обработчика, обычно целесообразней использовать интеграционные тесты.

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

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

Это намек на то что тест — это не цель, а средство. Цель — чтобы был доволен клиент. Ему обычно пофиг сколько уровней абстракции в приложении или какое покрытие тестами, у него другие приоритеты.

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

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

Правильней будет сказать «фиксировало функциональность модуля».

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

Функціональні тести — інколи. Unit-тести — ні.

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

У мене тести це окремі програми, які перевіряють частку функціоналу головної програми на прикладах. За стилем це дуже нагадує make check у більшості GNU програм.

От тут добре видно різницю між специфікою різних робіт. Може бути, що більшість проблем це інтерфейсна, але код на зразок такого:


-spec(group_pc_repr(list({any(),list()})) -> list({any(),list()})).
group_pc_repr(List) ->
    rec_group_pc_repr([], List).

-spec(rec_group_pc_repr(list(), list({any(),list()})) -> list({any(),list()})).
rec_group_pc_repr(Out, []) ->
    Out;
rec_group_pc_repr(Out, [{Tag, List1}, {Tag, List2} | Rest]) ->
    rec_group_pc_repr(Out, [{Tag, List1 ++ List2} | Rest]);
rec_group_pc_repr(Out, [{Tag, List} | Rest]) ->
    rec_group_pc_repr(Out ++ [{Tag, List}], Rest).

майже неможливо відладити без юніт-тестів, які проходяться по всим потенційним кутовим випадкам. Навіть якщо його 2 таких функції на всю програму. ;)

пишу после написания рабочего кода)

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

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

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

По-перше, тести можуть використовуватись для налагодження у пісочниці. Це економія часу, особливо на великих проектах. У тесті ми можемо гарантовано викликати метод з довільними аргументами, а не шаманити з головною програмою, намагаючись створити необхідну ситуацію. Часто програмісту це банально ліньки робити, в результаті чого йому приходиться повертатися до цього методу через тиждень-другий, але вже на етапі багфіксу.

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

Отличие в том, что тесты ДО кода диктуют дизайн, а тесты ПОСЛЕ всего лишь фиксируют уже существующий.

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

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

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

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

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

Лучше написать после, чем не писать вообще: хоть какая-то гарантия выявления регрессий.

Правильнее сказать «хоть какая-то видимость», по написанному коду Unit тесты уже неэффективны, для регрессий используют интеграционные и функциональные тесты.

Это не так. Равно как и сами тесты, регрессии бывают различного рода, и выявляться должны по-разному.
Например: вы внесли изменения в алгоритм сортировки — существующие модульные тесты говорят, что всё ОК, но интеграционные тесты говорят об ухудшении производительности на реальной БД. Или же: вы переходите на новую версию 3rd-party компонента, в котором наравне со старым API (obsolete/deprecated) присутствует новый. Добавляя новые модульные тесты вы в какой-то момент обнаруживаете конфликтную ситуацию и необходимость внести правки в свой дизайн в то время как интеграционные тесты, по-прежнему использующие obsolete API, ничего о регрессии сказать не могут.

Важно лишь помнить, что писать после — гораздо сложней и менее полезно.

Не вижу причины ни одному из этих утверждений.

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

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

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

Спасибо за разъяснение.
Верно ли я понимаю, что Ваша работа всегда начинается со стратегического прототипировпния? Если так — Ваш подход целиком и полностью оправдан: на PoC тесты писать абсолютно необязательно.

Верно ли я понимаю, что Ваша работа всегда начинается со стратегического прототипировпния?

В подавляющем большинстве случаев это именно так.

Если так — Ваш подход целиком и полностью оправдан: на PoC тесты писать абсолютно необязательно.

Что такое PoC — я не понял. Но для кода под уже устоявшееся ТЗ тесты таки нужны (и даже критичны).

Под PoC я подразумевал Proof of Concept.
Согласен с Вашим заключением.

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

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

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

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

Я бы не сказал что в естественном. Ибо «дано» и «требуется» не относятся к решению самой задачи.

У тестов до кода есть существенное преимущество в том, что они помогают заставить самого себя написать таки этот код:)

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

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

Юнит тесты они хороши, когда сферические в вакууме. Когда логика-математика — да, прекрасно. А когда UI или персистенс какой, то оно бесполезно. А когда есть политика «всегда пишем тесты», то притягивают за уши и пишут тесты, которые никогда не зафейлятся. Все хорошо к месту и в меру.

А когда UI или персистенс какой, то оно бесполезно. А когда есть политика «всегда пишем тесты», то притягивают за уши и пишут тесты, которые никогда не зафейлятся.
Да бывает такой код который никогда не меняется (и не меняется его окружение). В моем мире такого не так уж и много.
Простой пример с с «персистенсом»:
Если пришли значения, то сохранить их поле объекта. Если нет, то сохранить дефолтное значение.
Функционал нужен 1% клиентов. Написали и забыли.
Прошел год. За этот год поменялось дефолтное значение на нулл, кто-то прописал какие-то дефолты для полей в БД, и мы переехали на другую СУБД.
Результат от нас уходит 1 клиент и его куча бабала, потому что он «увидел эксепшн». Причина простая никто не проверял эту функциональность (Написали и забыли). При наличии тестов на каждом этапе у нас бы валились тесты __при сборке__ и мы бы помнили о такой функциональности.
Менее драматичный пример:
Вводные те же.
Задача отрефакторить всю ручную работу с транзакциями на аннотации-или-типа-того. Поменяли в 100500 мест.
Где гарантия того что вы везде все правильно отконфигурировали/прописали? Вы планируете разобраться во всем функционале приложения и руками проверить 100500 мест? Или понадеяться что то что сломалось не нужно ни одному из ключевых клиентов?
.
К слову, времени на написание тестов на пример выше ушло бы даже меньше чем зайти в УИ и проверить что все работает.
.
P.S. Важно понимать что такое «юнит» в каждом случае, если у вас вся «бизнес-логика» уходит в БД, то БД — это часть вашего юнита.

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

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

Когда логика-математика — да, прекрасно. А когда UI или персистенс какой, то оно бесполезно.
Это еще одно распространенное заблуждение.
Думаю причина его в том, что в книжках первые примеры юнит-тестов это как раз тестирование отдельного «математического» метода (как раз тот «сферический код в вакууме»). До глав со сложными примерами (моки, стабы, шимы) добирается не каждый.
Возникают мифы о юнит-тестах:
1. Юнит-тесты придумали для проверки сложных методов с вычислениями. Если вычислений нет — то и проверять нечего.
2. Маленькие, простые методы и проверять незачем. Ну вызывает метод 3 других по очереди — что тут может поломаться?
3. Методы, которые принимают не простые параметры (числа для вычислений), а сложные объекты, протестировать очень сложно. Лучше даже не пытаться.
4. Протестировать можно только публичные методы. Ведь до остальных не добраться. А публичные вызывают внутри приватные поэтому один тест затестит их всех сразу.
5. Все что работает с базой, файлами, сетью не подлежит юнит-тестированию вообще. Невозможно написать юнит-тесты на базу.
6. UI автоматически протестировать невозможно — его должен проверять только человек глазами.
7. Код, который вызывает системные функции или внешние библиотеки тестировать бесполезно. Ведь это чужой код и он уже протестирован — поэтому всегда работает правильно.
8. Очевидные тесты, которые «никогда не зафейлятся» писать бесполезно. Например есть пустой метод-заглушка. Какой смысл писать для него тест?
Каждый из этих мифов можно разрушить.
Даже если код написан на строго-типизированном языке + используется статический анализ кода + код ревью, все равно самый простейший код может поломаться от банальной невнимательности. Например метод принимает 2 числовых параметра и его вызывают «method(a, b)» — через полгода придет джун и напишет «method(b,a)» в одном месте — и попробуй потом найти.
Другой пример: был метод «add (a, b) return a+b» все вызвали как хотели «add (a, b)» или «add (b, a)» — ведь один хрен «от перемены мест не меняется». А через полгода его поменяли на «add (a, b) return 2*a+b». И «глазами» такие мелочи поймать очень сложно.
Для тестирования базы, файлов и т.д. существуют транзакции. Открыли транзакцию, все проверили, откатили.
Презентайшин то же тестируется: если это HTML или XAML то проверить несложно. Для сайтов есть QUnit — можно проверять прямо ДОМ. Даже UI контролы — это классы, которые можно создать и вызвать без запуска всего приложения.
Да — многое зависит от языка и платформы. Могу сказать за .Net — можно написать юнит-тест почти на все если найти нужные библиотеки.

>Для тестирования базы, файлов и т.д. существуют транзакции.

Не в транзакциях дело (они могут базой, ФС или используемой библиотекой не поддерживаться), просто нужно разделять бизнес-логику в самом приложении и логику хранения (и, возможно, бизнес-логику в системе хранения типа хранимок в СУБД или других внешних подконтрольных сервисах) на уровне архитектуры, не создавая сильную связанность моделей (если придерживаться MVC и ко) с системой хранения.

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

Всегда ли вы пишите unit тесты или если сроки поджимают, то забиваете на них?
Сама постановка вопроса показывает непонимание правильного процесса разработки.
Она подразумевает что разрабатывать с юнит-тестами дольше, а если сроки поджимают — то можно пожертвовать тестами и написать быстрее. Ясный пень что при таком подходе юнит тесты будут считаться не помощью, а обузой и на них начнут забивать.
А ведь идея юнит-тестов как раз в том, что бы помогать, сокращать время разработки и повышать качество!
В первую очередь — за счет сокращения времени на дебаг. Даже если быстро написали код — его надо запустить хотя-бы раз перед чекином. Сколько займет времени запустить все приложение, дойти до нужного места, ввести нужные данные, нажать кнопочку? А если с первого раза не сработало — исправили одну букву и все по новой: запускаем, доходим, вводим, нажимаем ... Пускай заработало — но теперь по-хорошему надо еще проверить не поломали ли мы чего рядом. Это выливается в небольшую сессию ручного тестирования (не самая приятная работа для девелопера). Теперь если посчитать время на «ручную проверку» то окажется что написать и запустить несколько тестов было бы намного быстрее, а для чужого кода уже есть тесты и сразу видно что поломалось. И главное — писать код и быстро его запускать девелоперу намного приятнее, чем запускать все приложение и вручную клацать и набивать данные.
Остальные плюсы юнит-тестов не буду расписывать: все они много раз описаны и разжеваны и приводят только к увеличению скорости разработки (быстрее понимать чужой код, меньше багов фиксить, проще дебажить и т.д.).
НО — есть один подвох! Такими удобными бывают только правильные юнит-тесты. А именно: небольшие, понятные, атомарные и не требующие поддержки. Это тот самый Open-Closed принцип из SOLID: код написанный и протестированный однажды закрыт для изменений, но открыт для расширения и повторного использования. Это значит, что новые тесты пишутся только вместе с новым кодом (никакого «покрытия существующего кода тестами»!) и потом практически не требуют изменений.
И для того, что бы можно было писать такие правильные тесты изначально архитектура приложения должна быть правильной. Т.е. состоящей из небольших, слабо-связанных через интерфейсы классов, каждый из которых легко заменить моком.
Если кто-то занимался хотя-бы немного радиотехникой — то аналогия очень хорошая. Радио-детали (резиторы, емкости, кондеры, диоды, транзисторы) — это наши простые, надежные и протестированные классы (у каждого есть четкая спецификация). Юнит-тест: это прибор, которым можно проверить любую детальку. Схема — это наши UML диаграммы, печатная плата с дорожками — это композиция наших классов (например конфигурация инджершин-контейнера). Интеграционный тест — это подключили осциллограф к готовой схеме и проверяем кривую... Что-то я отвлекся.
Так почему же многие считают, что разрабатывать с юнит-тестами дольше и неудобнее? Да потому, что они пытаются написать эти тесты для уже написанного кода! Который изначально писался без всякой мысли о том, что его будут тестировать юнит-тестами. Вот это — однозначно бессмысленная трата времени. Тест для класса в 1000 строк будет занимать все 3000 строк (подготовка данных, проверки, попытки подсунуть стабы и т.д.). Мало того: этот тест придется постоянно менять при любом изменении (даже в «соседних» классах).
Я не фанат TDD, думаю тесты можно писать и после кода. Они нужны именно тогда — когда вы хотите свой код запустить. Но если вы уже написали и проверили код руками — то тесты писать поздно (и наверняка будет дольше). В крайнем случае — пишите тесты когда рефакторите старый код делаете его «чистым», но не «покрывайте тестами» спагетти-код, это «обезьянья работа» которую делают в основном для очковтирательства что у нас есть 100% покрытие (закопать свое Г в песочек, как котик).

Все правильно ты написал, но вот здесь

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

Логично: вначале проекта руками все проверять быстрее. Ну или если все приложение — это одна формочка с кнопкой, то можно ее вообще без всякой архитектуры и тестов налабать.
А поскольку тесты требуют изначально правильную архитектуру — то на старте проекта будет дольше вдвойне: нельзя будет лепить по-быстрому с аргументацией «потом зарефакторим и покроем тестами».
Тут зависит от объема проекта: проект на 1-2 месяца можно слепить, сдать и забыть по-быстрому без архитектуры и тестов. А вот дольше уже беда: сделать из спагетти-кода пригодный для тестирования и покрыть тестами будет не быстрее, чем выбросить и переписать по-правильному.
Особенно плохо когда новый клиент и что бы ему угодить хотят первую версию выкатить побыстрее. А потом клиенту нравится и такой подход «лепить халтуру» растягивается на годы.

Именно, но именно на старте проекта начальство чаще всего хочет увидит, что все закрутилось и что-то уже работает и пофиг на архитектуру и код. И вот тут-то на кого-то и сваливается сложность внедрить подход TDD в нужный момент — не раньше и не позже. А оно надо?
Первый раз попробовать — интересно, а второй и третий уже нет.
Начальство выше по итогам увидело, понравилось и силовым методом требуют внедрить на всех проектах и дальше идет внедрение в стиле: «всем поктрыть бессмысленными тестами 100% кода».

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

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

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

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

Сама постановка вопроса показывает непонимание правильного процесса разработки.

_Единственно_ правильного? ;)

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

Я честно не понимаю, какой смысл писать комментарий в стиле «Мышки, станьте ёжиками».

Основная мысль, которую я хотел донести: применять юнит-тесты нужно только если они помогают в процессе разработки. А что бы они помогали надо уметь их правильно применять. Иначе получится как в басне «Мартышка и Очко»:
rvb.ru/...1fables/053.htm
Менеджер где-то услышал про юнит-тесты и заставил девелопера их писать. Девелопер по-диагонали прочитал статейку, посмотрел пример, не захотел вникать — и погнал писать «кучерявые» тесты на сотни строк каждый.
В результате девелопер уверен что с тестами код писать в 2-3 раза дольше и это тупая работа. Менеждер уверен что если отказаться от тестов то можно написать в 2 раза быстрее. А клиент не хочет и слышать про тесты — ведь за них нужно дополнительно платить.
И эти «комплексы» потом очень сложно изжить! Я, как тех лид, сразу подразумеваю что на новом проекте тесты будут неотъемлемой частью написания кода (т.е. код чекинится сразу с тестами). Потому что пробовал так разрабатывать и знаю что это удобно, точно не в 2 раза дольше и гарантированно окупается уже на 2 месяце проекта. Но в команде джуны (а иногда и мидлы!) сразу говорят: «ну с тестами это я буду вдвое дольше делать», а менеджер первым делом спрашивает: «а если без архитектуры и тестов то насколько быстрее будет?» Потом у клиента спросят «какой процент покрытия тестами вы готовы оплатить» (как будто он понимает что это такое) и он, ясный пень, отвечает что будет платить только за фичеры. Все — проект сразу начинается как халтура («тяп-ляп и в продакшин»).
Поэтому не умеете применять юнит-тесты — или научитесь, или не применяйте! Но не поддерживайте заблуждение что написать хороший код с тестами это намного дольше, чем слепить халтуру. Этим вы плодите унылые, гнилые проекты, хотя они могли бы быть нормальными если бы вначале знающий человек их правильно «стартанул».

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

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

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

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

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

Ну вот это как раз один из примеров, когда TDD в его каноническом виде реально нужен: защита от таких тупых читеров на менеджерских позициях. «Больше бумаги — чище задница» в IT варианте этого принципа. Устанавливаешь TDD на уровне высшего руководства, и хитрозадому менеджеру уже никуда не деться. В этом я его поддерживаю на 149%. Но само по себе наличие таких менеджеров уже говорит о проблемах конторы...

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

Или перекинет работу в другой бодишоп. Хотя тут ничего не хочу твёрдо говорить, я никогда в таких местах не работал (к счастью или сожалению — ХЗ).

По-мне это все равно что спросить: «можно я не буду проверять что код компилится перед чекином раз сроки поджимают?» (тупо написал код — и сразу чекин, пускай другие проверяют и фиксят).

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

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

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

«_Единственно_ правильного? ;)»

Предлагаемого TDD/BDD — он не везде применим, на ряде задач абсолютно не применим, на ряде себя не оправдывает.

«Я честно не понимаю, какой смысл писать комментарий в стиле „Мышки, станьте ёжиками“.»

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

1. Прошу для квотинга использовать или «>», или blockquote. Ваш комментарий крайне тяжело парсить.

2. Вы делаете крайне странные (для меня) обобщения на тему архитектуры. У меня есть случаи, когда центральный «god object» не может быть разделён в силу крайне плотно связанной специфики; любое разделение будет искусственным и начнёт тормозить результат. Но это не мешает делать юнит-тесты на отдельные аспекты и его работы, и прочих компонент, не имеющих такой проблемы.

Коммент чтобы пособирать плюсики:

Нет
Ну хоть один честный человек :)

Все зависит от проектов.

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

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

дохожу я до 8 уровня в игре и шлеп
Для себя с сделал такой вывод, что в моих проектах прогон руками лучший тест :
Логично. Если «делали игрулю с ребятами», то для того чтобы проверить работу на 8 уровне можно пройти предыдущие 7, и чтобы проверить текст на 9-м, можно пройти предыдущие 8. Но вот когда вам захочетсо чтобы эта игра вам приносила что-то на покушать, то вы начнете считать сколько всего вы могли бы делать полезного вместо очередного прохождения уровней.
.
дохожу я до 8 уровня в игре и шлеп — нестандартное разрешение экрана
Не совсем понятно почему оно было «стандартное» на предыдущих 7-ми уровнях, а на 8-м стало «нестандартным».

На 8 уровне это впервые проявилось.

Нет впервые это появилось на втором, и юнит тесты работали :)

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

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

Вы не поняли, юнит тест как раз и пказал что все ок :)

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

В любом случае — юнит тестам быть. И нет ничего сложного в подходе TDD — только вот лично Я за максимальную чистоту функционального кода (тот, который испытуемый) от кода юнит тестов (тот, который испытывает). Вплоть до раздельного хранения модулей исходного кода и различных сборок (build targets).

Конечно пусть будут юнит тесты, но они не везде и не всегда применимы.

Иногда клиент сам забывает что он хотел... :)

Вы не поняли, юнит тест как раз и пказал что все ок :)
Боюсь, что это вы не поняли :)
Юнит тест который проверял/должен был проверять:
нестандартное разрешение экрана и главный герой появился в внутри стены
Не мог показать что все ок. Возможна ситуация что подобного теста не было, или он был написан без ТДД (то есть он был не корректным, а вы об этом не знали)

Богдан, в описываемом случае было что-то нечто спора: юнит тест или ручное тестирование.

Автор юнит теста проиграл, причина мне особо не важна: его (автора юнит теста) кривые руки или отсутствие необходимой проверки в самом тесте.

В моем конкретном случае прогнали тест и пошли пить пиво, а я решил прогнать игру руками и нашел баг :)

Юніт-тести — не панацея, а 99% гарантія, що все ОК. Якби не вони, у вас би бага вилізла набагато раніше, а при зміні коду в майбутньому — вони б вилазили постійно.

Дуже мало людей думає як Ви.

Юніт-тести — не панацея, а 99% гарантія, що все ОК. Якби не вони, у вас би бага вилізла набагато раніше, а при зміні коду в майбутньому — вони б вилазили постійно.

Розімієте в IT немає гарантій взагалі. Все продається «як є».

Є такий жарт про нову версію ПЗ: «Відомі помилки виправили, нові додали» :)

Юніт тестом Ви можете частково зменшити витрати часу на перевірку вже відомих помилок в тих модулях, які не змінювались.

Моя Ваша останього взагалі не зрозуміла.

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

Як вже писав Вам — гугліть BDD і не кажіть про відсутність гарантій. Можливо не все ІТ таке, а у Вас на роботі тільки?

Про відсутність гарантій:
Чи читали Ви колись щось подібне у ліцензійній угоді
«THE SOFTWARE PRODUCT AND ANY RELATED DOCUMENTATION IS PROVIDED „AS IS“ WITHOUT WARRANTY OF ANY KIND...» ?

Якщо так, то питаня в тому «як можна давати гарантії, на те що працює без гарантій?»

Я розумію, що прицепився до слів, але вважаю нерозумним давати гарантії там, де вони відсутні апріорі...

BDD — змусьте вашого замовника працювати з вами через такі специфікації (їх має готувати Ваш БА), і педальте юніт і енд ту енд тести, базуючись на них. Реально працює.

“Відомі помилки виправили, нові додали”

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

Что и требовалось доказать!

Именно за эту фразу я Вас поддержал
"

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

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

Обнаружение ошибок тестерами и юзерами при использовании юнит-тестирования — штатная ситуация. Видимо ни вы, ни, как минимум, ваш ПМ не понимаете сути автоматизированного тестирования, особенно юнит-тестирования.

Я же сразу написал, что

я загорелся вот наконец-то увижу как работают юнит тесты и смогу научиться
.
:)

В моих проектах юнит тесты не подходят ибо:
1) Проекты маленькие — быстрее прогнать руками чем писать тесты.
2) Клиент все равно проверяет руками, а не тестами. Дай Бог чтобы проверил вообще.
3) Клиент так часто меняет спецификацию, что переписывание каждый раз тестов значительно удорожит проект. Проще тестировать все руками.

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

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

Владимир, а как проверить, что юнит тест тестирует правильно?
Писать юнит тест на юнит тест?
А если ошибся разработчик юнит теста?
Что тогда?

Владимир, а как проверить, что юнит тест тестирует правильно?
ТДД. Вы написали код который соответствует поведению описанному в тесте.
Писать юнит тест на юнит тест?
Только опыт, свой опыт, или наставника. Тут надо понимать что первое время будет херня, реально результат будет после 3-6 месяцев.
А если ошибся разработчик юнит теста?
Что тогда?
Перевод: а если разработчик кода не понял задачу?
Это уже совсем другая проблема.
.
Для тестирования тестов есть так же и технические инструменты, но они как правило просто определяют что все плохо. Самый простой — это покрытие, если оно мало, то проблема есть. Более сложный и не всегда оправданный мутационное тестирование ( en.wikipedia.org/...utation_testing )
Перевод: а если разработчик кода не понял задачу?

Разрабочик кода приложения или разработчик юнит теста?

Это уже совсем другая проблема.

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

«Разрабочик кода приложения или разработчик юнит теста?»

Обычно это один человек. Юнит-тесты (и, как правило, интеграционные) пишет разработчик, а не QA.

«Почему другая проблема?»

Это проблема прежде всего коммуникационной компетентности между постановщиком и разработчиком, а не девелоперской разработчика.

Это проблема многих людей.

Как говорится: «Я написал только то, что я написал, а то что Вы подумали, это подумали Вы» :)

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

А если ошибся разработчик транслятора, библиотеки или фреймворка? :)

Делать его настолько малым и/или простым, чтобы для получения ошибки нужно было постараться. Плюс обычный теорвер — если вероятность ошибиться у разработчика 1%, то без тестов мы будем иметь этот 1%, а с тестом 0,01% (и даже меньше), так как чтобы ошибка в тесте привела к ошибке в коде, ошибиться нужно одинаково в двух разных местах, например написать что-то вроде assertEqual(5, mul(2,2)) в тесте и return a*b + 1 в коде. Мы же не говорим о разработчике, который добивается работоспособности кода «методом научного тыка» типа «что-то не работает цикл как ожидается, вычту-ка я единицу из параметра цикла при обращении к массиву»?

Уже писал тут: тесты не средства поиска багов, а средство фиксации функциональности. Не фэйлящий тест не означает, что нет ошибок, а означает, что в данном случае программа всё ещё ведёт себя как это задокументировано (sic!) в тесте. Тесты — это спецификации, с возможностью простой и быстрой (по сравнению с ручным уж точно) проверки кода на их соответствие (методология BDD вообще предполагает что тесты пишет заказчик на «естественном» (типа SQL) языке). Если спецификация не соответствует требованиям «в голове», то это не проблема уровня кодирования.

Именно это

А если ошибся разработчик транслятора, библиотеки или фреймворка? :)

я и хочу показать
dou.ua/...-comment#526533

Долго ждал, когда кто-то напишет это:

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

Вы не поверите, наверное, но на сложные модульные тесты тоже пишут тесты.
Именно поэтому первым требованием выдвигается простота и лёгкость для восприятия, плоская структура и «самодокументируемость».
Ат от ошибок никто не страхован, для чего есть парное программирование и test-code review.

Мне это понятно.

Многие уперлись в юнит тесты как в единственное средство что-то там достичь и слепо этому следуют без понимания экономики проекта:
1). Рентабельность
2). Время
3). Конкурентспособность

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

По возможности всегда. Но иногда руководтсво против TDD.

Но иногда руководтсво против TDD.
А мотивация какая? Нам нас...ть на то за что мы платим вам деньги? :)

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

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

зависит от.

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

а есть проекты по старше, где несмотря на single page application на последнем уровне луковицы абстракций дверь в ад (ibm iSeries привет ) ну вот там с тестами все хреново, и добавлять новые часто очень напряжно. Плюс controllers в тыщи строк, ага... ну собственно там есть тыщи интеграционных тестов завязанных на базу но как все это сделано и майнтениться это жесть.
Я там тесты только когда они мне помогают в девелопменте, на tdd бюджетов не дают да оно там пожалуй и маловозможно

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

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

рано или поздно «иногда» превращается в «постоянно».

Не превращается. Кроме TDD есть и другие методы обеспечить административно покрытие тестами.

Польза от тестов очень заметна

Против этого, надеюсь, никто и не спорит.

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

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

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

А вот когда писать тесты — до или после — больше зависит от баланса между waterfall и agile в конкретной разработке; грубо говоря, надо пофиксить всё важное до того, как оно начало приносить убытки, но после того, как стабилизировалась концепция и больше не будет «всё переделать, ибо забыли важную деталь». 100% достижения этого, конечно, не будет, но надо стремиться. При этом, после написания основного кода или до — уже неважно.

Разработчик считает что свою задачу он выполнил, а тут на него ещё что-то вешают.

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

Хуже только когда документацию писать заставляют :)

Документация она разная бывает. Если кодер в коллективе с выделенными документаторами — его дело написать внутреннюю документацию, лучше в чём-то вроде javadoc или doxygen, дальше не его дело. Если кто-то посерьёзнее — и требования выше. Но без doxygen/аналога не считать работу завершённой, это принципиально.

Похоже, что ваша позиция «тесты нужны для выявления багов», что несколько не соответствует философии TDD. По ней тесты нужны для определения того, что должен делать код, всё остальное лишь приятные следствия. Например, ситуация с крайними случаями может быть трех видов:
1. Ни постановщик задачи, ни разработчик не заметили (или первый считал очевидным, а второй четко следовал формальной задаче) необходимости их специальной обработки или контроля их невыхода к ним.
2. В коде они обрабатываются, но тестами не покрыты
3. И обрабатываются, и покрыты.

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

Похоже, что ваша позиция «тесты нужны для выявления багов», что несколько не соответствует философии TDD.

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

Вопрос соответствия философии TDD меня интересует в предпоследнюю очередь.

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

«Определение», простите, в каком смысле? Если «задание», «специфицирование», то, во-первых, это уже не TDD, а BDD. Да, их часто путают. Во-вторых, тесты для BDD и тесты для проверки корректности — в общем случае разные вещи. Вот, например, мы проверяем функцию умножения чисел (представим себе процессор, где нет такой команды). Тесты для специфицирования функциональности будут начинаться с банальностей типа 0*0==0, 0*10==0, 2*2==4, (-2)*(-3)==6, а затем, если их продолжать, могут хоть покрыть весь набор входных значений, но не дойдут до крайних. Тесты для проверки корректности будут проверять в первую очередь крайние случаи. Если тип входного значения — 16 бит, дополнительный код, то без проверок типа (-32768)*(-2) этот набор тестов откровенно недостаточен. Понимаете разницу? Вы в принципе не можете покрыть тестами все варианты таких параметров. И вот выбор тех, что остаются актуальны — зависит от того, для чего рисуются эти тесты.
(Я тут не рассматриваю тестирование по Монте-Карло, которое пригодно для такого умножения, но не пригодно для чего-то хуже формализуемого.)

Проблема TDD в его классическом определении и понимании в том, что нет критерия, что тесты хоть как-то должны отражать проблемы кода, и даже нет требования, что код должен делать что-то кроме соответствия тестам. Ему формально соответствует даже такое:


// Автор: Кумарабдул Абдулкумар
long mul(int x, int y) {
  if (x == 0 && y == 0) return 0;
  if (x == 0 && y == 10) return 0;
  if (x == 2 && y == 2) return 4;
  if (x == -2 && y == -3) return 6;
  // Харе Кришна! Мы удовлетворили все тесты!
}

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

Это вы юнит-тесты просто неверно понимаете.
Код пишет тот же человек, что и тесты. Код и тесты не разрывны. И юнит-тесты не являются тестами черного ящика. Сразу же все ваши противоречия снимаются.

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

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

Это вы юнит-тесты просто неверно понимаете.

Простите, а почему я должен понимать их именно так, как понимаете Вы?

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

Отлично, ключевое утверждение сказано и выявлено. А теперь встречные вопросы:
1. Почему написанное Вами осознаётся крайне малым количеством сторонников TDD, в то время как остальные в принципе отвергают наличие проблемы?
2. Как быть с другими проблемами TDD в Вашем «обдуманном» подходе? Например, ситуация: добавлено новое требование в ТЗ, на которое нужно нарисовать тест, а «обдуманный» программист уже давно это требование удовлетворил просто походя (без кода только под это требование). Требуется, чтобы тест вначале свалился, а потом был удовлетворён. Ломать явно работающий код — бессмысленно. Как быть?

1. Почему написанное Вами осознаётся крайне малым количеством сторонников TDD, в то время как остальные в принципе отвергают наличие проблемы?
Книгу по ТДД Кента Бека наверное не читают ))
Требуется, чтобы тест вначале свалился, а потом был удовлетворён. Ломать явно работающий код — бессмысленно. Как быть?
И снова книгу не читают. ТДД — это циклическая работа: тест, код, рефакторинг. Рефакторинг выбрасывать нельзя. Явно работающий код ломать смысл есть.

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

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

И снова книгу не читают. ТДД — это циклическая работа: тест, код, рефакторинг.

Вы не отвечаете на мой ответ вопрос ;(
Что вы будете делать, если считаете, что тест удовлетворён изначально? Как докажете, что тест что-то проверяет?

[skip банальщину для сферического кода в вакууме]

Как не отвечаю? Без рефакторинга — это не ТДД. Рефакторинг подразумевает изменение кода без изменения функционала. Это и есть: ломать код имеет смысл. Противоположное утверждению:

Ломать явно работающий код — бессмысленно.
Принцип YAGNI совершенно не касается рефакторинга. Код в ТДД меняют и это почти обязательное правило.

Проблемы на самом деле нет, есть только ваши представления о ТДД, по которым вам кажется там проблема.

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

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

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

То, что вы написали в вопросе, не относится к ТДД. Поэтому видимо я вас и не понял. Такие «обдуманные» программисты и ТДД — вещи не совместимые.

Ну и пишите понятнее.

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

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

Если код разрабатывался по ТДД, то такого в принципе не должно быть.

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

кто-то пишет наперед

В том-то и дело, что в такой ситуации никто ничего наперёд у меня не писал.

Походя — это всмысле ходил и код не писал, а будущее требование удовлетворил?

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

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

Так как всё-таки с требованием «сначала тест должен упасть»? Забьёте на него или будете вводить поломку в код?

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

Шутка такая. Но доля правды есть.

Как вы планируете в уме? Вы думаете по частям. Разбиваете задачу на части и анализируете каждый кусок. Так вот ТДД — это дисциплина — сразу записывать. Тоже по частям. Тест перед кодом дисциплинирует, учит не уходить в фантазии.

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

Вот, можно ответить на ваши вопросы.

Новое требование вытекает из нового ТЗ верхнего уровня и только в новой версии этого ТЗ становится важным.
Не придирайтесь к тому, что не может быть никогда или может быть. Есть разные подходы к пониманию, что такое 100 процентное покрытие. Я придерживаюсь, что это покрытие всех веток программы, все ифы, все циклы. Поэтому любое поведение уже покрыто тестами.
Бывают просто глупые требования. Например, пересечение требований, которые уже были. Но я практически не встречал такого, чтобы ничего делать не надо было. Если заказчик раннее вам давал требование, чтобы вы реализовали сложение, а потом вдруг начал настаивать, когда уже требование реализовано, что именно 2+5 = 7, то тут надо с ним обсуждать. Или он забыл, что было уже такое более общее требование. Или все таки ему это важно. Ну тогда без проблем. Пишем тест, проверяем, код не падает, гуд.
Потом, не стоит прямо так буквально придираться к шагам в ТДД. Для реализации требования можно или написать код (+20 символов, например), а иногда бывает, что наоборот удалять надо код (-30). То и 0 символов формально тоже может быть. Точно так же, как обязателен рефакторинг, но вы можете обнаружить, что код и так хорош. В этих случаях надо только задумываться, что здесь не так. Может быть рефакторинг не нужен, потому что вы начали мудрить, а не самое простое решение писать. Так же с требованием вопрос, почему вдруг оно возникло, когда код его уже выполняет?
Вы, видимо, противопоставляете ТДД и «обычному» подходу с планированием и написанием без тестов или с тестами после кода.

_Я_ противопоставляю? Это его авторы делают такое противопоставление. Потому что это у них «test first», «тест должен упасть» и тому подобное.

ТДД — это использование кода как черновика.

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

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

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

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

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

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

Этот подход позволяет рождаться архитектуре почти самой.

Это просто миф, авторы которого не понимают, каким образом рождается эта самая архитектура.

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

Человек всегда должен представлять, куда он идет, это не рандомная система.

Это и существенно неверно, и не имеет отношения к обсуждаемым вопросам. Человек не должен представлять, куда он идёт. И человек — в колоссальной мере «рандомная» система. Например, были исследования, что выполнение более 70% работы по чётким однозначным алгоритмам приводит к психологическим проблемам исполнителя, вплоть до депрессии, и требование к психологически комфортной обстановке — дать на оставшиеся минимум 30% делать как хочет, просто для разнообразия. Но не буду дальше на эту тему, просто погуглите.

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

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

Я придерживаюсь, что это покрытие всех веток программы, все ифы, все циклы.

Это очень интересный тезис, разберём подробнее.

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

Во-вторых: если «покрытием» называется факт выполнения кода из ветки под каким-то вариантом теста, то это тупой механистичный критерий, пригодный только на «отвяжись» без понимания сути происходящего. Простой пример. Пишем подсчёт длины utf-8 строки в кодовых пунктах. (Предполагается, что строка не длиннее 1000 символов.) В соответствии с принципом TDD он разбивается на две раздельно тестируемые функции:
1) функция skip, которая сдвигает указатель вперёд на один кодовый пункт; для этого она анализирует байт под входным указателем.
2) функция count, которая в цикле считает количество раз, сколько вызвана функция skip, до конца строки. Для теста skip может заменяться на мок.

Вы честно пишете тест на обе функции: на skip — по варианту на каждый диапазон значений первого байта; на count — на вход и выход из цикла.
Но тип результата count, назовём его MYSIZE, оказывается в результате кривого подключения хедеров равным uint8_t, и строки длиннее 255 кодовых пунктов вызывают муть вместо ответа.

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

У меня как-то был «noname» ethernet коммутатор, который не пропускал пакеты, которые были не-первым фрагментом IP пакета и имели длину приблизительно от 200 до 300 байт. Что у него там было в логике — не знаю, обычно у таких зверей таблица паттернов и коды действий по ним; видимо, где-то был конфликт в этой таблице. Но пользоваться им в итоге было нельзя — непропуск таких пакетов был абсолютным. Очевидно, тестирование данного коммутатора было сделано без понимания его внутреннего устройства.

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

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

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

Заказчик: — Эта функция должна гарантированно отработать за 30 мс.
Архитектор: — Тут для поиска данных потребуется их отсортировать.
Кодер: — Особые требования к сортировке были? Нет? Ну, introsort — наше всё, у меня ещё с лабораторки код есть:)

Заказчик: — Статистика показывает, что в 99% случаев функция работает меньше 5 мс. Но мне сейчас требуется гарантия в 10 мс.
Архитектор: — Тут точно O(n log n) при C<1000? Я вписываю это в ТЗ. Иначе не уложимся.
Кодер: — Да чтоб меня покрасили в гламурные сердечки, ничего менять уже не надо.

Что неестественного, по-Вашему, в таком сценарии? И это только один из возможных.

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

Я утверждаю, что у вас неверное представление о ТДД, выше я написал, где и какое верное. То, что мне можно верить больше чем вам, простая логика: по вашим представления ТДД не работает, по моим работает. Вероятнее, что все же не дураки его придумывали.
(каким это образом написание теста перед кодом, а не наоборот создает такую кардинальную невозможность разработки по ТДД и почему перестановка действий резко невероятно увеличивает трудозартаты? Хотя не спрашиваю)
Просто разберитесь с вопросом. Я удаляюсь )

Просто разберитесь с вопросом.

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

Я удаляюсь )

Не смею задерживать :)

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

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

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

Этот подход имеет другую колоссальную проблему: он в принципе не допускает грамотную алгоритмизацию.

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

Вы вначале решите, что должен пройти тест для (1,2) и (2,1). OK, сравнили два элемента, прошли. Теперь добавляем третий... четвёртый... двадцатый... во что превратилась функция? В лучшем случае вы получите сортировку вставками (если кодер очень умён), а скорее всего это будет «пузырёк». При совсем тупом кодере это вообще не будет читаемо даже после ста грамм. И Вы никак не получите ни метод Хоара, ни тем более метод Бэтчера. Потому что для них надо сначала реализовать алгоритм, хотя бы в уме, со всеми циклами, ветвлениями и тому подобным, а уже затем тестировать.
Вы можете сказать тут, конечно, что алгоритм в уме и алгоритм в коде — разные. Но когда вы заранее знаете, какое именно ветвление вы напишете и почему — вы уже действуете не под тест, а под алгоритм. Идея предварительного разделения на два подмассива, как у Хоара — тестом не решится.

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

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

но тогда не приходится говорить о TDD.

Именно. Есть масса областей, где применение TDD есть прямой обман, ибо сокрытие настоящего подхода.

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

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

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

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

Вы не владеете вопросом ))

Стандартное начало от того, кто просто не захотел подумать, о чём говорит собеседник. Пока что проигнорирую.

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

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

Алгоритм, в отличие от простого onButtonDrop() { target->drop(); }, должен быть «нарисован» в уме хотя бы в базовой сложности.

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

«Потом» не будет. Потому что сортировка сложнее условного пузырька — уже сложнее, чем можно уложить на юнит-тесты. Например, метод Хоара принципиально распадается на три-четыре функции. Вот и возвращаемся к тому, что алгоритм первичен. А что вы предложите с методами «рисуем тесты и удовлетворяем»?

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

Сначала написали алгоритм, потом подгоняем под него тест так, чтобы код вдруг выгнулся в нужную форму. Простите, я думал о работе программиста, а не циркача.:)

Придется подробнее о философии. Вы сейчас спорите о крайности. Это неверие, что задачи можно описывать декларативно.
Так вот юнит-тесты — это не совсем та мельница, с которой стоит воевать.

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

Например, считается, что функциональный подход, вроде Scheme, Haskel — это близкий к декларативному (если без присвоений). Но при этом в коде происходит удивительная фигня. Например, в книге SICP, где описывается этот подход, внезапно, при поиске корня используется метод Ньютона. Ведь нигде в задаче не сказано, что нужно этот метод использовать. Значит, метод Ньютона — это не требование, это реализация.

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

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

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

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

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

У вас, возможно, была задача с точки зрения бизнеса — найти 5 наиболее дешевых товаров. Там нигде не сказано, что вы должны делать сортировку. И теперь можно упереться, что никаким образом нельзя продвинуться вниз, не выбрав способ решения — сортировка. Ладно, выбрали сортировку. О небеса! Теперь же надо выбрать какую именно! А никак это не решить, просто задавая себе вопрос: я хочу просто посортировать и не должен ничего выбирать. Если хотите выиграть в лотерею, хоть билет покупайте. Если хотите выбрать себе автомобиль, то надо выбрать, а не думать, что вы хотите все цвета одновременно и все марки одновременно, и нельзя никакую обидеть.

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

Так что никакой проблемы в ТДД нет, вы можете по нему писать и алгоритмы. Другое дело, с какой стороны стремиться к декларативности кода. Если вы разрабатываете по ТДД, то тогда действительно бывают случаи, когда благодаря практически механическому рефакторингу архитектура самоорганизовывается. Т.е. работа с кодом таким образом хоть и не избавляет вас от проблемы выбора насовсем, но всё равно заставляет смотреть на задачу по другому и отбрасывать всё лишнее. Вот, можете посмотреть на примере этой статьи:
habrahabr.ru/post/153845

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

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

Так вот юнит-тесты — это не совсем та мельница, с которой стоит воевать.

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

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

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

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

Да, это именно так. Особенно с пост-классической группой компиляторов, типа GCC >=4.0. И даже процессоры сейчас такие, вредные, стараются всё переупорядочить;) Но какой нам вывод для данной темы? Что-то не вижу связи.

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

Могу. В тривиальных случаях. Но в истинно интересных случаях — таки не могу.

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

Верно. И опять-таки никакой связи с тем, что я говорил.

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

Могу. Но меня не устраивает цена за переделку по сравнению с вариантом тестирования по уже продуманной реализации. Если для какого-нибудь M-V адаптера она ничтожна, для серьёзного алгоритма она может вылиться в увеличение затрат в десятки раз.

Вот, можете посмотреть на примере этой статьи:

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

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

Я ни слова не говорил про модульное тестирование вообще. Посмотрите, пожалуйста, внимательнее.

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

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

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

Есть пригодность вообще и есть осмысленность при наличии альтернатив. Я говорю про второе.

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

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

Если Вы хотели тут вместо «нельзя» сказать «можно», то я согласен, и спорить не буду. Речь совсем не о том.
Да, спасибо за то, что поправили: именно возможность я и имел в виду.
Пока что на мои конкретные примеры были только возражения в предельном общем, на уровне лозунгов съезда КПСС.
Прошу указать, где я был недостаточно конкретен.
Прошу указать, где я был недостаточно конкретен.

Не Вы. Кое-кто из других оппонентов, кому я задавал прямые вопросы типа «а как Вы поступите в этом случае?»

«Так как всё-таки с требованием „сначала тест должен упасть“?»

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

«Так как всё-таки с требованием „сначала тест должен упасть“?»
Никак. Иногда можно отходить от формальностей, но нужно анализировать, почему так случилось. Или заказчик идиот (если это от него требование), или кто-то из программистов опередил заказчика и не по YAGNI разрабатывает, написал наперед. Что плохо. Или заказчик всё понимает, но очень хочет, чтобы это требование было выделено. Тогда пишете этот тест и всё. Это тогда и не шаг по ТДД, а просто регрессионный тест, закрепляющий поведение. Главное, чтобы вы при этом поняли причины и что происходит в коде.
Иногда можно отходить от формальностей, но нужно анализировать, почему так случилось.

Вот тут и разница. Вы считаете, что эта формальность важна (хотя в порядке исключения можно от неё уходить). Я рассказываю, почему она вообще не имеет смысла, как только мы ставим вопрос о качестве сопровождения продукта. «fail first» оказывается костылём, возникшим из-за попытки решить проблему частными методами без осознания проблемы полностью.

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

Главное, чтобы вы при этом поняли причины и что происходит в коде.

Это безусловно выполняется.

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

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

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

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

1. Ничего из сказанного не поможет против проблем тестового фреймворка и наведённых проблем.
2. Избавление от условной логики во многих случаях невозможно.

Также существует уже упоминавшееся здесь мутационное тестирование.

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

По первому пункту: приходилось ли Вам сталкиваться с проблемами такогоирода в современных framework’ах? Я работал с рядом этих инструментов. С ограничениями сталкивался, с проблемами — никогда и ни один пример подобного инцидента мне неизвестен. Поделитесь, если известен Вам.
По второму пункту: избавление от условной логики в модульных тестах возможно во всех случаях, заявляю ответственно. В противном случае налицо проблема тестового кода и антишаблон «Гибкий тест».
Насчёт мутационного тестирования: думаю, Вы окажете огромную услугу сообществу, если поделитесь своим опытом и напишите статью.

По первому пункту: приходилось ли Вам сталкиваться с проблемами такогоирода в современных framework’ах?

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

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

«Этого не может быть, потому что не может быть никогда» (tm)

Извините, я на такое не ведусь. Несмотря на то, что сам стараюсь не делать сложной логики в юнит-тестах, доказательства у Вас нет. Есть только «горячее сердце».

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

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

Пишем сначала тест, который не должен проходить (а-ля asserNotEqual(4, mul(2, 2)) если у заказчика возникло требование «в реализованном ранее умножении дважды два должно быть обязательно равно 4»), видим что он падает, а потом его исправляем (asserEqual(4, mul(2, 2))), фиксируя заказанное поведение.

Пишем сначала тест, который не должен проходить (а-ля asserNotEqual(4, mul(2, 2))
а потом его исправляем

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

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

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

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

ТДД заставляет приземляться и не витать в облаках.

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

Если его понимать в вольном стиле и адаптировать под практические реалии, он не даёт ничего нового к существующим проверенным методикам.

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

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

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

Я только скажу, что ТДД идет в связке с YAGNI, а также с навыками — рефакторить, правильно юнит-тесты писать. Если этого нет, то пробовать его болезненно и может казаться, что он вредит.

А код, созданный по ТДД работает годами и выкатывался без единого бага.

И это заслуга не TDD, а наличия тестов и умного (не механического) покрытия ими.

Я только скажу, что ТДД идет в связке с YAGNI, а также с навыками — рефакторить, правильно юнит-тесты писать. Если этого нет, то пробовать его болезненно и может казаться, что он вредит.

«Болезненность» вызывается неуместным следованием основным нормативам TDD. Но в книге вам этого не скажут.

И это заслуга не TDD, а наличия тестов и умного (не механического) покрытия ими.

В общем да. Тесты можно и после писать. Но ТДД так устроено, что не забудешь ничего и покрытие полнее обычно.

«Болезненность» вызывается неуместным следованием основным нормативам TDD.

Не согласен. Неумение рефакторить и не понимание, зачем он нужен, вот в чем причина. Это даже связано с умением видеть в коде говнокод. Просто по коду видеть недоработки. Обычно это прямо связано с тем, рефакторит человек вообще код или нет. Если он рефакторит, то и рука набита на рефакторинг. Он видит, где код можно улучшить. А тот, кто не рефакторит, у него вечный вопрос: ПОЧЕМУ ЭТО НЕЛЬЗЯ НАПИСАТЬ БЫЛО СРАЗУ?
))
Этот вопрос не дает человеку покоя и он думает, что написание кода по ТДД, которое еще нужно рефакторить — лишняя трата времени. Или неуместное ТДД, другими словами.

В общем да. Тесты можно и после писать. Но ТДД так устроено, что не забудешь ничего и покрытие полнее обычно.

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

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

То, о чём я говорил перед этим, не имеет никакого отношения к теме рефакторинга. Я не понимаю, зачем Вы его примешиваете тут.

Этот вопрос не дает человеку покоя и он думает, что написание кода по ТДД, которое еще нужно рефакторить — лишняя трата времени. Или неуместное ТДД, другими словами.

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

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

он совершенно прав в том, что преждевременное тестирование это просто выброс времени на ветер.

При TDD нет преждевременного тестирования, есть создание частных спецификаций и периодическая проверка кода на их соответствие.

и периодическая проверка кода на их соответствие.

И как именно вы проверяете «периодически» код на их соответствие?

Запуском, как минимум, перед коммитом.

Ну то есть вопрос «а проверяет ли тест вообще что-то?» решается приёмом результата на веру.

У меня инверсии неожиданно сработали 2 или 3 раза за всю историю, но каждый раз это был очень серьёзный сигнал, что что-то было концептуально испорчено так, что обычные методы не ловили в принципе, несмотря на все представления про «100% покрытие».

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

Проверяется тем, что сначала тест должен упасть.

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

Когда пишешь тесты перед кодом, то делаешь это несколько (а то и десятки) раз в день.

«Когда вовремя пишешь тесты». Давайте не будем ставить такие коварные подтасовки в мелочах.

(но архитектура при этом заведомо легкотестируемая)

А это само собой должно быть категорической целью независимо от остальных аспектов. :)

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

Вы космический софт разрабатываете? :)

>"Когда вовремя пишешь тесты«. Давайте не будем ставить такие коварные подтасовки в мелочах.

Спалился :) А если серьезно, то практика показывает, что написание тестов разработчиком после написания кода очень часто превращается в профанацию или они вообще перестаются писаться — «потом, сейчас некогда».

>А это само собой должно быть категорической целью независимо от остальных аспектов. :)

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

Вы космический софт разрабатываете? :)

Ещё нет:) Но временами отношение соответствующее. Например, система мониторинга в реалтайме с секундными требованиями на сложную реакцию и гарантиями самовосстановления любого компонента в те же времена.

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

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

Но: как Вы думаете, какая часть из тех, кто собственно пишет код, строго соблюдает эти принципы, а какая — только делает вид, а на самом деле перемешивает код с тестами в любом порядке, лишь бы после этого были соблюдены приличия в репозитории, тикетнице и аналогичных местах? ;)

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

И опять подтасовываете:) Обеспечить ясность и лёгкую тестируемость — да, необходимо. Но не тот комплект средств, который называется TDD — он достаточен, но не единственно необходим:)

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

>И это заслуга не TDD, а наличия тестов и умного (не механического) покрытия ими.

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

Заслуга TDD в том, что архитектура создаётся заведомо легкотестируемая.

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

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

Верно. У Michael Feathers есть даже книга про проблемы перевода такого кода в тестируемый. Но зачем для этого циклиться на TDD?

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

Не стоить так его ругать лишь за то, что он не применим в Вашем случае.

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

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

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

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

Уж лучше тогда функциональными тестами покрыть (ИМХО, конечно)

Всегда ли вы пишите unit тесты или если сроки поджимают, то забиваете на них?
Перефразируем вопрос:
Всегда ли вы проверяете то что сделали или если сроки поджимают, то забиваете на проверку?
.
Строго говоря добиваться 100% покрытия часто может быть не эффективно в плане времени. Задача правильно оценить риск поломки. Если вы можете дать гарантию что в каком-то «модуле» поломка крайне маловероятна, то это может быть причиной не писать тесты. Но это в теории.
На практике, дать подобные гарантии практически не возможно (в любом случае не адекватные случаи отбрасываем). Другой печальный случай: код есть гуано (и при этом скорее всего нет/мало тестов) и написать тесты на него проблематично, в таких случаях можно подзабить на тесты, но это стрельнет очень громко.
.
Если вы используете ТДД, то проблема со скоростью пропадает: Вы в любом случае вы обдумываете код перед написанием. ТДД/БДД просто говорит что вы должны записать то что придумали, в виде тестов. Поэтому оверхед получается не большой.
Но судя по формулировке вопроса, будет проблема с тем что люди __не хотят__ писать тесты и тем более примут в штыки идею ТДД, в это случае оверхед на тесты будет в разы (2-5 раз). Но это уже проблема в головах.

У нас если программист push-ит код без тестов, то на стадиях Code Review и тестирования её могут переоткрыть по причине отсутствия тестов. Но это на усмотрение code-review-еров и QA.
Иногда, если задача очень срочная — делаем без тестов, но отдельно заводим задачу «покрыть тестами такой-то код»

но отдельно заводим задачу «покрыть тестами такой-то код»
Можете соврать что это работает :)

Я не знаю, как у них, но у нас при выделении какой-то доли времени на тесты (например, до 30%) административно — эти задачи таки решаются.

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

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

Всё что я писал — это было про server-side (php) и в нашей компании у server-side-а своеобразное понимание термина «релиз». У нас релиз дважды в день (в пятницу один раз). В таких условиях тесты _достаточно_ эффективны для нахождения регрессий

Непозволительная роскошь юнит тесты

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

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

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

)))

Это особенность вашей «малоизвестной но интересной » конторы, разработка в которой к TDD отношения не имеет.

если сроки поджимают, то забиваете на них?

Вы не одни ;)

Зависит от менеджеров и лидов.

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