Pull to refresh

Comments 69

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


Вы рефакторите код или добавляете новую функциональность, модифицируя существующий код.

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

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

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

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

Но, к сожалению, в жизни имеются некоторые детали, которые сводят благостную картинку на нет. Что примечательно, в обоих случаях (тесты и социализм) — все портит человеческий фактор.
Собственно, мой пост направлен на то, чтобы объяснить, как оно в реальности у нас происходило. И может произойти у других.
UFO landed and left these words here
>Не воспринимайте это как персональное оскорбление, но в своём анализе вы многократно пали жертвой Availability bias. Отсюда и пост. Увы, у вас по какой-то причине всё пошло плохо, и ни вы, ни ваши коллеги не видели примеров того, как оно работает и делает жизнь лучше.

Поясните пожалуйста. Вы имеете в виду, что у меня выборка малая? Что я сужу исключительно по опыту, с которым сталкивался, хотя немало и обратного опыта, так?
Вынужден с вами согласиться. Пожалуй я слишком резко обобщаю.
Тогда, воспринимайте мой пост как отчет о специфическом кейсе в фирме, где я работал.
UFO landed and left these words here
А это какой-то замкнутый круг. «TDD помогает всегда. Если не помогает — вы что-то делаете неправильно. А если делаете неправильно и всё равно не помогает, то это не серебряная пуля бла-бла. Но всё равно смотри п.1». С таким подходом любую практику оправдать можно, т.е. абсолютно любую, вплоть до спагети.

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

Вообще, если, по вашему собственному признанию, «дискуссии повторялись по многу раз», то, видимо, проблема таки существует.
UFO landed and left these words here
Я на всякий случай замечу, что yorick_kiev_ua под «спагетти» имел в виду вовсе не блюдо, а спагетти-код, который объективно плох.
Просто из вашего комментария не совсем очевидно, что вы это уловили.
ТDD, которое вы, как я понимаю, защищаете, говорит что именно всегда(за исключением ряда вырожденых случаев, но мы сейчас всё же не об этом).

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

P.S. Правильно приготовленый спагетти-код — это сильно!
Начинать нужно с применяемой методики разработки. Если разработка ведется водопадом, когда собственно на разработку и отладку (не тестирование!) отводится порядка 15-25% трудозатрат по проекту и наиболее важный шаг это проектирование архитектуры и специфицирование, то применение tdd в таких проектах необосновано.

Нужно просто разобрать плюсы и минусы водопада.

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

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

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

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

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

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

Tdd стоит отнести к вершине методик экстремального программирования.

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

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

Ваше видение проблемы вызывает уважение (без шуток). И я сожалею, что наш менеджмент этого не понимал.
Я и мои коллеги обучены и привычны к использованию Waterfall с подробным планированием и согласованием интерфейсов. Собственно, я описывал взгляд с этой стороны. Попытка заставить нас использовать TDD отзывалась болью и мучениями, окончилась крахом и стоило громадного количества человеко-часов.
Другое дело, что поменять команду (>50 человек) было нереально.
Заставить разработчика использовать TDD невозможно. Нужно, чтобы сам разработчик понимал плюсы данного подхода. Потому что если требование использовать TDD спускается сверху, вы получите боль, мучения, и совершенно бесполезный набор тестов, пишущийся для галочки. Юнит-тестирование как бронежилет — можно продолжать утверждать, что от выстрела из базуки бронежилет не спасет, а от пуль проще увернуться, но на практике бронежилет спасет вам жизнь =)
UFO landed and left these words here
Поскольку писать совсем без багов практически невозможно, это будет означать — «не занимайтесь разработкой ПО».
Вы знаете, но ведь мы с вами сошлись во мнениямих.

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

Я в посте написал, что есть 3 ситуации, когда юнит тесты оправданы.
>1. ПО повышенной надежности (для атомной электростанции)
>2. Легко изолируемый код – алгоритмы и все такое
>3. Утилиты – код который будет ОЧЕНЬ широко использоваться в системе, так что стоит их досконально прошерстить заранее

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

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

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

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

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

>И второй вопрос: к системам какого типа вы применяли TDD в частности и автоматизированное тестирование в целом?

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

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

Система, состоящая из примерно 10 процессов, связанных друг с другом через СОМ, плюс несколько десятков динамически подгружаемых DLL. Писалась больше десяти лет до нас.

Занятно. Просто мой опыт говорит, что к разным системам TDD применим в разной мере. Вот например к сервисам — легко и в путь. А к GUI-приложениям — практически никак. Было интересно, какого рода система у вас, что такое отторжение.
Был там и GUI, были и сервисо-подобные процессы (хотя они не были оформлены как сервисы, но занимались backend обработкой задач).
В общем, система была с большим количеством взаимосвязей. И изолировать код можно было только массированным написанием и программированием моков. Собственно, вся моя боль выражена в ячейке таблицы:
>Дополнительные усилия в программировании x2… x5

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

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

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

Пожалуйста, обратите внимание на то, что я уже говорил: в предлагаемом мной тестировании изолируются только внешние зависимости. Если у вас их в одном сценарии так много, что затраты вырастают в пять раз — вы что-то не так делаете.
Да, там где затраты вырастали в 5 раз — это был особенный АД.
Но даже и в более-менее простых местах, затраты вырастали раза в два.
И это было тоже болезненно.
Угу. К этой арифметике неплохо бы еще добавить уменьшение затрат на тестирование, буде оно (уменьшение) есть.
По поводу уменьшения затрат на тестирование. Я оного не заметил. Правда, я и не особенно вглядывался — все таки я программист, а не QA. Но я полагаю, что в любом случае, экономия гораздо меньше: в наших палестинах зарплата QA в два-три раза меньше зарплаты программистов.
А я вот начальник отдела, под которым и разработка, и qa, поэтому мне как раз видно. И когда день работы программиста экономит неделю (рабочую) работы тестировщика (и это только при первом проходе, при последующих изменениях все еще веселее) — математика начинает работать в сторону автоматизированного тестирования на стороне разработки.

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

Я добавлю, что честно старался. И мои коллеги тоже (кто-то больше, кто-то меньше). И мы все не осилили.
А компании это стило много, очень много денег.

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


Кстати. Вот вам неплохой (частный) пример места, где с TDD хорошо и приятно.

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

А теперь приведите гораздо более дешевый способ борьбы с этими багами, учитывая, что юнит-тест под это сводится к трем строчкам (конечно, если есть готовый xsd-валидатор).
Говоря прямо, юнит тесты — еще не tdd.

Tdd это подход к проектированию через написание тестов. Как минимум — проектированию программных интерфейсов.

А юнит тесты можно и вслед коду написать.
Я в курсе, спасибо. Собственно, здесь нормальное такое TDD: берем «требование» в виде XSD, пишем тест на работу сериализатора, потом начинаем писать сериализатор.
Извините, но я обращу ваше внимание на кусок моего поста.

>Тем не менее, есть 3 вида кода, когда юнит тесты очень даже к месту (два из них я уже упоминал):
>1. ПО повышенной надежности (для атомной электростанции)
>2. Легко изолируемый код – алгоритмы и все такое
>3. Утилиты – код который будет ОЧЕНЬ широко использоваться в системе, так что стоит их досконально прошерстить заранее

Ваш пример подходит под пункт 2. Видите?
Судя по тому, что этот абзац ускользает от внимания читателей, я довольно неудачно оформил свой пост.
В вашем «резюме», которое я цитирую, про это нет ни слова. И, как я уже говорил, «легкость» изоляции кода — понятие субъективное и наживное. Собственно, про это все сказано у Физерса.
Я прошу прощения за неопытность.
Это вообще нормально, если я сейчас подредактирую пост и в свое «резюме» вставлю вышеприведенный дисклаймер?
Здесь так делают?
После этого ваше резюме потеряет свой смысл, вам не кажется?
Нет, не кажется.
Для всего, что под них не подпадает, под 3 упомянутых случая, мое резюме сохраняет смысл.

Я лично в 90% своей трудовой деятельности имел дело именно с кодом, который для юнит тестов не удобен. Может кому-то везет больше. Рад за них.
Просто ваше резюме после этого будет выглядеть приблизительно так: «в тех случаях, когда юнит-тесты сложно написать, или не нужна повышенная надежность системы, соотношение затраты/выигрыш у юнит-тестов невелико». Спасибо, кэп.
Поверьте, это далеко не для всех очевидно.
Наш менеджмент свято верил, что юнит-тесты и TDD — панацея от всех багов. А это совершенно не так! Баги бывают разные. И тех, которые юнит тесты не ловят принципиально (в моей практике) — большинство.
… и вот тут наша с вами практика расходится.
Не завидуйте. Поверьте, стремление к максимальной автоматизации тестирования — это не от хорошей жизни.
В посте я пишу, что юнит тесты не предназначены (или плохо это делают) для отлавливания ошибок мультисрединга, интерфейсов и интеграции систем, написанных большим количеством людей.

Если у вас таких ошибок меньшинство, то либо у вас неопытные программисты, которые делают много «ошибок в коде», либо у вас не слишком большие проекты, и мултисрединг за вас решает Фреймворк. Я предполагаю, вы работаете с вебом, так?
«Ошибки в коде» делают любые программисты, не только неопытные. Чем сложнее бизнес, тем больше вероятность таких ошибок.

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

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

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

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


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

То есть тесты в первую очередь — это средство не отлова собственных ошибок, а метод непустить на ревью то, что вообще не будет работать (в неожиданных местах).
Скажите, а кто там пишет тесты? И какая у него зарплата?
Собственно, весь вопрос о том, насколько это экономически оправдано.
Разумеется, если писать юнит тесты легко и просто (не мой случай) — то вопрос отпадает.
Tdd призвана научить писать простые тесты. Большинство новичков в tdd здорово мудрят и парятся.
Пишут тесты там те, кто хочет, чтобы их код появился в проекте. Это куча компаний, которая платит своим программистам. Код без тестов просто не принимают в апстрим.

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

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

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

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

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

Ну так басфактор же. Собственно, не надо даже «черезвычайно большого» проекта, достаточно shared code ownership.

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

Вот е еще одна причина, почему вы не внедрили TDD. Неподдержание актуальных тестов — это, как бы, сильное нарушение методологии.
Извините, а что такое «басфактор»? Термин незнакомый.
От английского bus, автобус. Сколько людей из вашей команды должен сбить автобус, чтобы вы не смогли поддерживать код.
Понял, спасибо.
Нда, если прицельно бить, то человек 5 было достаточно. Все, конечно, заглядывли друг к другу в код, но были и особо трудные модули, где шарили единицы.
Да, и это отвечает почему в нашем случае TDD имел мало смысла. У нас был code ownership в полный рост. Я, например, три месяца изучал один сильно запутанный модуль, пока не разобрался какие вещи на входе, что дают на выходе и как оно внутри вращается. Ну, а потом я этот модуль сопровождал, пока не написал всторонке заново в новой архитектуре.
TDD от этого никак не зависит. Можно работать в TDD в одиночку.

А что касается вашего примера: я, оказавшись ровно в такой же ситуации, начал покрывать модуль (в моем случае это был слой) тестами, начав с создания контрольной копии. Мне это потом сохранило кучу нервов — я практически всегда знал, что остальная система не заметит ни моих изменений в модуле, ни его подмены на новую версию.
Как раз таки в этом случае вы бы сказали TDD огромное «Спасибо!». Не знаете, как работает модуль? Открыли тесты — посмотрели. При этом, вы будете точно знать, где нужно сделать правку, чтобы заработало так, как вам надо, и в течение нескольких секунд (прогон тестов) убедитесь, что оно работает правильно.
UFO landed and left these words here
Во-первых, спасибо за очень содержательный комментарий.

Обе ваши мысли в точку.
У нас действительно была плохая робастность дизайна. И это очень серьезно снижало полезность написанных юнит тестов. То есть, протоколы закрывались, но в процессе разработки и интреграции они могли быть несколько раз поменяны.
Во-вторых, у нас действительно были проблемы из-за позднего тестирования. Правда, TDD нам эти проблемы не решил. Видимо из-за пункта 1.
Sign up to leave a comment.

Articles