Re[15]: Нафига нужны юнит-тесты?
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 06.10.11 19:27
Оценка:
Здравствуйте, netch80, Вы писали:

N>Здравствуйте, gandjustas, Вы писали:


N>>>>>Алгоритм — нет. А вот оптимальная реализация — да, может сильно измениться в зависимости от специфики запросов и обстановки.

G>>>>Точно, поэтому и нужны unit-тесты чтобы проверить что новый и старый алгоритм эквивалентны
N>>>Против этого я и не возражал. Вопрос в том, насколько они unit.
G>>А что им мешает быть unit-тестами?

N>Зависит от глубины переделки. Может оказаться, что неизменна и покрыто неизменившимися тестами только самая внешняя функциональность, которая не может быть сведена к простому "выстрелил вызовом функции — получил ответ — проверил", а требует долгой подготовки среды. Для меня это всё-таки не unit тест. Unit — это когда максимум подготовки среды это расстановка заглушек вместо рабочего кода. Да, это деление не академично, но мне оно полезнее.


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

N>>>>>Что ты называешь вариантами использования?

G>>>>Это значит все возможные наборы параметров для которых имеются разные пути исполнения.
N>>>Для меня такое тестирование имеет очень мало смысла, только для отдельных специфических вариантов функций. Полный набор путей исполнения, мягко говоря, бесконечен. Можно было бы только в целях тестирования выделить отдельные логические подблоки типа "продвинуться на шаг алгоритма", выделяя их в функции, но часто это слишком неудобно.
G>>Вообще-то конечен.
N>Ну числа типа 2**2**32 всё равно за пределами представления даже в BER, поэтому я позволил себе "округлить" их до бесконечности.
По факту их гораздо меньше.


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


N>>>Под такое ещё надо адаптировать код. Далеко не всегда возможно, хотя, надо заметить, может быть полезно. Для того же примера с подсчётом длины строки, например, разделить код на функцию сдвига на один пункт и код подсчёта пунктов может быть полезно для ловли, например, ситуации, если промежуточная длина хранится в 8 битах (злобный пример того, как можно не заметить фигню на коротких тестах). Но это уже придётся mock'и делать.


G>>Если писать тесты до кода, то он как-то сам адаптируется


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

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

N>Я периодически использовал этот подход — тесты до кода — как стимулятор собственной работы против собственной же лени, то есть проблема была чисто организационная. Но я никогда не предполагал его как средство собственно дизайна. На это годится тестируемость в принципе, а не test-first.

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


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

G>>Для TDD (test-first) это естественный процесс, для test-after — нет. Поэтому unit-тесты и рекомендуется для test-first.

N>Не так. Оцениваешь функциональность. Пишешь прототип, или даже просто схему на бумаге, что должно делаться и как. Смотришь на него, проводишь фактически design review (хоть в одиночку, но можно и с коллегами), оцениваешь, что и как реализовывать. На этом этапе могут выделяться чёткие подблоки, которые пригодны к тестированию отдельно от остального. Но ещё не под тест, потому что могут интерфейсы и контракты поменяться уже в процессе реализации, когда будет осознан ещё один пласт специфики. Потом всё это приблизительно написано, разделение на составные части, интерфейсы, контракты стабилизировались, теперь нетривиальные внутренние части покрываются базовыми тестами, подтверждающими их работоспособность. В основном это юнит-тесты, с минимальной настройкой среды проверки или вообще без неё, на уровне отдельных функций. Далее рассчитываешь и делаешь функциональные тесты, под базовые юзкейсы компонент, вычищаешь их от багов. В моём случае обычно минимальная единица, обладающая собственным поведением, называется приложением, и делать функциональные тесты для более мелких частей обычно нереально (хотя бывают и такие сущности). На следующем уровне тестируются связки из таких приложений, образующие функциональность части полной системы, например, тракт вокруг конкретной шины от первичных генераторов событий до финальных накопителей; назовём это интеграционными тестами. Только после этого можно говорить о завершении разработки конкретной подсистемы или даже приложения — когда оно показывает корректное прохождение интеграционных тестов. Далее идут общесистемные тесты, это уже делается людьми в QA отделе. Процесс такого развития никак не соответствует продвижению сверху вниз с твёрдой уверенностью в функционале конкретного уровня, а вместо этого можно говорить только о приблизительной чёткости понимания, которая будет доведена до полной уже после пробы на прототипе. Вот-с, где-то так.


Многабукафниасилил. Но чтение по диагонали говорит что правильно пишешь, только гораздо шире чем я. Выделенное примерно соответствует тому что я написал, остальное out-of-scope. На этапе прототипирования тесты не нужны. Вообще методика test-first заставляет создавать приложение по слоям сверху вниз. Если заранее не знаешь как оно должно быть, то пишешь прототип без тестов, а потом уже переписываешь (именно переписываешь, нет смысла рефакторить прототип). Это в терминологии называются spike.

N>>>А без адаптации, по крайней мере для моей обстановки, никак не получается "очень ограниченное количество" ситуаций. Поэтому для меня whitebox testing имеет совсем другой смысл (см. предыдущие письма).

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

N>Я продолжу эксплуатировать тот же пример — подсчёт codepoints. Ты действительно считаешь, что если функцию такого рода нельзя разделить на части, проверяемые по методу такого whitebox и подменяемые mock'ами, то это "изначально плохо тестируемый код"? Ответь, пожалуйста, конкретно, а не в сторону. Мне таки очень интересно. Пример простой, но полезный.


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

N>>>Ты путаешь совершенно разные вещи. Ошибки часто одинаковые, это да. Специфика не уникальна, но она колоссально разная. Я не могу найти сейчас человека здесь на форуме, который заметен и который занимается хотя бы примерно теми же задачами, что я. Если есть, то они не светятся. В то же время я знаю, что в мире ещё около 5 групп идёт схожими путями. Но они не здесь. Поэтому я действительно склонен считать, что в пределах RSDN мои задачи уникальны и специфика их — тоже. Возражать разрешаю только на собственных примерах

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

N>Никакой завесы. Текущая основная задача — система управления и мониторинга HPC кластеров. Основной код на Erlang, всякий клей на Python. Предыдущая — VoIP свич, в основном Python, местами C. Ещё на одну до того — вообще не программизм по сути, хотя много всякой ерунды на Perl и C для автоматизации работы и вокруг. Дальше в историю копать не буду, а то и до матфизики на Фортране докопаемся.


N>Ожидаю теперь такой же откровенности с твоей стороны


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

G>>Учитывая что основной язык для тебя — C++,

N>Ну вот объясни мне — как можно было _так_ читать, чтобы прийти к такому выводу???
Сорри, сейчас редко встретишь тех кто пишет на С, а не С++


G>> то там unit-тесты с трудом возможны, ибо компиляция долгая и нормальных mock-фреймворков нету.

N>А если не заниматься безумными гипотезами и учесть, что у меня Erlang и проблем с юнит-тестами в общем-то нет?
N>Mock'и подставить тривиально, это условия запуска в рантайме. Адекватная модульность обычно или заложена, или легко закладывается.
Так эрланг же динамически типизированный, там вообще необходимо тесты писать, п-другому не проверишь.
Но к сожалению мои познания в erlang низкие и ниче сказать не могу.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.