Здравствуйте, gandjustas, Вы писали:
N>>Алгоритм — нет. А вот оптимальная реализация — да, может сильно измениться в зависимости от специфики запросов и обстановки. G>Точно, поэтому и нужны unit-тесты чтобы проверить что новый и старый алгоритм эквивалентны ;)
Против этого я и не возражал.:) Вопрос в том, насколько они unit.
N>>Насчёт "невозможно что-то спланировать" ты сильно неправ. Возможно. Но планировать надо иначе — умея уточнять обстановку и адаптироваться под неё на ходу. Такое себе agile, только на следующем вверх уровне. И таки да, быть готовым морально и практически к тому, что что-то придётся отменять ещё не доделанное, с сожалением, и переходить к другому. G>А ты не в кусре что agile не работает\плохо работает на fixed cost? тебя много не fixed cost проектов было?
Я в курсе. Но я предполагаю, что если заказчик меняет требования посреди реализации, то он должен за это заплатить.
На второй вопрос — я вообще не помню у себя fixed cost проектов за последние лет 15. Все — долгоживущие, рассчитанные на продолжение жизни софтины до тех пор, пока в ней есть хоть какой-то смысл, или прототипы, которые не вышли дальше этого этапа.
В случае жёсткого fixed cost я бы, конечно, любые предложения чего-то подправить сначала бы встречал правым нижним в бубен, а затем начинал бы слушать, что заказчик хочет. (Утрирую, конечно;))
N>>В быстро развивающемся софте, как у нас, может устаревать 20-30% кода в год. И это не самый суровый темп. G>Точно и остается 70%-80% кода в год, часть из которого не меняется на протяжении многих лет. Вернее меняется код, не меняются контракты. G>Ты только что показал существование такого, который редко меняется в плане контрактов и для которого имеет смысл писать тесты.
Категорически согласен. Так вот, и продолжая эту мысль: у нас есть некоторый ресурсный фонд на поддержание своего продукта, а не только на новое (если на поддержание дают 0%, надо немедленно увольняться). В момент введения новой функциональности она протестирована едва-едва, только в самых ключевых точках и на уровне самой верхней целевой функциональности, но как только она выжила — часть этого фонда поддержания должна пойти на усиление тестами того, что уже выжило. Ещё выжило — ещё усилить. И так далее.
N>>>>Ошибка в слове "доказать". Никакими тестами ты не можешь это _доказать_, можешь лишь обещать какую-то вероятность или меру корректности. Она может быть и 99.999%, но не 100%. G>>>Почему это? Если покрываются все варианты использования, то чем это не доказательство? N>>Что ты называешь вариантами использования? G>Это значит все возможные наборы параметров для которых имеются разные пути исполнения.
Для меня такое тестирование имеет очень мало смысла, только для отдельных специфических вариантов функций. Полный набор путей исполнения, мягко говоря, бесконечен. Можно было бы только в целях тестирования выделить отдельные логические подблоки типа "продвинуться на шаг алгоритма", выделяя их в функции, но часто это слишком неудобно.
G>Не надо проверять все параметры. надо написать по одному тесту для каждого варианта использования. Их всегда конечное количество так как путей исполнения кода конечное количество. Как будешь вычислять эти самые варианты — твое дело, хоть автоматически с помощью pex, хоть анализируя код — без разницы. Вообще идеальный вариант — заранее продумывать как будет работать тот или иной метод класса и писать тесты. В любом случае вариантов использование будет очень ограниченное количество.
Под такое ещё надо адаптировать код. Далеко не всегда возможно, хотя, надо заметить, может быть полезно. Для того же примера с подсчётом длины строки, например, разделить код на функцию сдвига на один пункт и код подсчёта пунктов может быть полезно для ловли, например, ситуации, если промежуточная длина хранится в 8 битах (злобный пример того, как можно не заметить фигню на коротких тестах). Но это уже придётся mock'и делать.
А без адаптации, по крайней мере для моей обстановки, никак не получается "очень ограниченное количество" ситуаций. Поэтому для меня whitebox testing имеет совсем другой смысл (см. предыдущие письма).
N>>И что в этом неправильного? Да, это общее руководство. Да, я мог бы сказать, что для наших условий юнит-тест для нетривиальной функции экономит усилия в 10-20-100 раз (в зависимости от местности) по сравнению с выявлением проблемы уже на функциональном тесте приложения и поиском точки проблемы доступными средствами (только логами, поскольку никакой отладчик наши задачи не вытянет), а комплект функциональных тестов хотя бы на базовые сценарии работы приложения экономит ну просто дофига хотя бы на том, что установленная клиенту система в основном работает, а не падает. Но какое другим дело до нашей весьма тяжёлой специфики? У них своя есть, не менее странная. G>Ну вот, уже конкретика пошла, это уже лучше чем просто "хорошо". G>Вообще я заметил склонность многих программистов считать свои проекты\задачи пипец какими уникальными, а по сути сколько не смотрел проекты похожи один на другой даже из разных областей. И ошибки в них одинаковые.
Ты путаешь совершенно разные вещи. Ошибки часто одинаковые, это да. Специфика не уникальна, но она колоссально разная. Я не могу найти сейчас человека здесь на форуме, который заметен и который занимается хотя бы примерно теми же задачами, что я. Если есть, то они не светятся. В то же время я знаю, что в мире ещё около 5 групп идёт схожими путями. Но они не здесь. Поэтому я действительно склонен считать, что в пределах RSDN мои задачи уникальны и специфика их — тоже. Возражать разрешаю только на собственных примерах;)
Здравствуйте, netch80, Вы писали:
N>Здравствуйте, gandjustas, Вы писали:
N>>>Алгоритм — нет. А вот оптимальная реализация — да, может сильно измениться в зависимости от специфики запросов и обстановки. G>>Точно, поэтому и нужны unit-тесты чтобы проверить что новый и старый алгоритм эквивалентны
N>Против этого я и не возражал. Вопрос в том, насколько они unit.
А что им мешает быть unit-тестами?
N>>>>>Ошибка в слове "доказать". Никакими тестами ты не можешь это _доказать_, можешь лишь обещать какую-то вероятность или меру корректности. Она может быть и 99.999%, но не 100%. G>>>>Почему это? Если покрываются все варианты использования, то чем это не доказательство? N>>>Что ты называешь вариантами использования? G>>Это значит все возможные наборы параметров для которых имеются разные пути исполнения.
N>Для меня такое тестирование имеет очень мало смысла, только для отдельных специфических вариантов функций. Полный набор путей исполнения, мягко говоря, бесконечен. Можно было бы только в целях тестирования выделить отдельные логические подблоки типа "продвинуться на шаг алгоритма", выделяя их в функции, но часто это слишком неудобно.
Вообще-то конечен. Но все равно я не предлагаю весь код покрывать, я предлагаю покрывать ту функциональность конракты которой будут меняться редко или не меняться вообще. И как ты понимаешь на fixed cost проектах (каковых большинство) надо заранее планировать объем работ. Из этого объема легко вычленить части, соотвествующие тому что я написал выше.
G>>Не надо проверять все параметры. надо написать по одному тесту для каждого варианта использования. Их всегда конечное количество так как путей исполнения кода конечное количество. Как будешь вычислять эти самые варианты — твое дело, хоть автоматически с помощью pex, хоть анализируя код — без разницы. Вообще идеальный вариант — заранее продумывать как будет работать тот или иной метод класса и писать тесты. В любом случае вариантов использование будет очень ограниченное количество.
N>Под такое ещё надо адаптировать код. Далеко не всегда возможно, хотя, надо заметить, может быть полезно. Для того же примера с подсчётом длины строки, например, разделить код на функцию сдвига на один пункт и код подсчёта пунктов может быть полезно для ловли, например, ситуации, если промежуточная длина хранится в 8 битах (злобный пример того, как можно не заметить фигню на коротких тестах). Но это уже придётся mock'и делать.
Если писать тесты до кода, то он как-то сам адаптируется
Пишешь простой тесты, пишешь для него простой код, простой код не удовлетворяет всем требованиям, пишешь еще один простой тест, правишь под два теста код, пишешь третий тест, начинаешь писать код, понимаешь что друге тесты надо править чтобы не падали, сразу возникает желание вынести код в другой класс\функцию\еще что-нить, а в данном классе оставить mock.
Для TDD (test-first) это естественный процесс, для test-after — нет. Поэтому unit-тесты и рекомендуется для test-first.
N>А без адаптации, по крайней мере для моей обстановки, никак не получается "очень ограниченное количество" ситуаций. Поэтому для меня whitebox testing имеет совсем другой смысл (см. предыдущие письма).
То есть изначально написан плохо тестируемый код. Но эту ситуацию ты экстраполируешь на другие случаи, что далеко неверно.
N>Ты путаешь совершенно разные вещи. Ошибки часто одинаковые, это да. Специфика не уникальна, но она колоссально разная. Я не могу найти сейчас человека здесь на форуме, который заметен и который занимается хотя бы примерно теми же задачами, что я. Если есть, то они не светятся. В то же время я знаю, что в мире ещё около 5 групп идёт схожими путями. Но они не здесь. Поэтому я действительно склонен считать, что в пределах RSDN мои задачи уникальны и специфика их — тоже. Возражать разрешаю только на собственных примерах
Я понятия не имею чем ты занимаешь, но если завесу тайны уберешь, то найдется много людей кто занимается тем-же или похожим.
Посмотрев пару сотен сообщений в твоем профиле видно что большую часть ты тут пишешь в КСВ и о жизни, инода отвечая на темы о сетевых протоколах и С++.
Поэтому то чем ты занимаешься связано с сетями, точно не web, скорее всего linux\C++. Возможно что-то в высокой вычислительной нагрузкой типа аудио\видео конференц связи или обработкой видеопотоков от камер.
Учитывая что основной язык для тебя — C++, то там unit-тесты с трудом возможны, ибо компиляция долгая и нормальных mock-фреймворков нету.
Здравствуйте, gandjustas, Вы писали:
N>>>>Алгоритм — нет. А вот оптимальная реализация — да, может сильно измениться в зависимости от специфики запросов и обстановки. G>>>Точно, поэтому и нужны unit-тесты чтобы проверить что новый и старый алгоритм эквивалентны ;) N>>Против этого я и не возражал.:) Вопрос в том, насколько они unit. G>А что им мешает быть unit-тестами?
Зависит от глубины переделки. Может оказаться, что неизменна и покрыто неизменившимися тестами только самая внешняя функциональность, которая не может быть сведена к простому "выстрелил вызовом функции — получил ответ — проверил", а требует долгой подготовки среды. Для меня это всё-таки не unit тест. Unit — это когда максимум подготовки среды это расстановка заглушек вместо рабочего кода. Да, это деление не академично, но мне оно полезнее.
N>>>>>>Ошибка в слове "доказать". Никакими тестами ты не можешь это _доказать_, можешь лишь обещать какую-то вероятность или меру корректности. Она может быть и 99.999%, но не 100%. G>>>>>Почему это? Если покрываются все варианты использования, то чем это не доказательство? N>>>>Что ты называешь вариантами использования? G>>>Это значит все возможные наборы параметров для которых имеются разные пути исполнения. N>>Для меня такое тестирование имеет очень мало смысла, только для отдельных специфических вариантов функций. Полный набор путей исполнения, мягко говоря, бесконечен. Можно было бы только в целях тестирования выделить отдельные логические подблоки типа "продвинуться на шаг алгоритма", выделяя их в функции, но часто это слишком неудобно. G>Вообще-то конечен.
Ну числа типа 2**2**32 всё равно за пределами представления даже в BER:), поэтому я позволил себе "округлить" их до бесконечности.
G> Но все равно я не предлагаю весь код покрывать, я предлагаю покрывать ту функциональность конракты которой будут меняться редко или не меняться вообще. И как ты понимаешь на fixed cost проектах (каковых большинство) надо заранее планировать объем работ. Из этого объема легко вычленить части, соотвествующие тому что я написал выше.
Я ни разу не планировал сам fixed-cost проекты, поэтому тут ничего не скажу. Приму просто как должное.
G>>>Не надо проверять все параметры. надо написать по одному тесту для каждого варианта использования. Их всегда конечное количество так как путей исполнения кода конечное количество. Как будешь вычислять эти самые варианты — твое дело, хоть автоматически с помощью pex, хоть анализируя код — без разницы. Вообще идеальный вариант — заранее продумывать как будет работать тот или иной метод класса и писать тесты. В любом случае вариантов использование будет очень ограниченное количество.
N>>Под такое ещё надо адаптировать код. Далеко не всегда возможно, хотя, надо заметить, может быть полезно. Для того же примера с подсчётом длины строки, например, разделить код на функцию сдвига на один пункт и код подсчёта пунктов может быть полезно для ловли, например, ситуации, если промежуточная длина хранится в 8 битах (злобный пример того, как можно не заметить фигню на коротких тестах). Но это уже придётся mock'и делать.
G>Если писать тесты до кода, то он как-то сам адаптируется ;)
Писать тесты до кода означает или гарантированно окончательный дизайн, во что я не верю, или тупую наивность. По крайней мере я других вариантов пока не видел. Если есть где-то место, где это проходит, то там кому-то сильно повезло.
Я периодически использовал этот подход — тесты до кода — как стимулятор собственной работы против собственной же лени, то есть проблема была чисто организационная. Но я никогда не предполагал его как средство собственно дизайна. На это годится тестируемость в принципе, а не test-first.
G>Пишешь простой тесты, пишешь для него простой код, простой код не удовлетворяет всем требованиям, пишешь еще один простой тест, правишь под два теста код, пишешь третий тест, начинаешь писать код, понимаешь что друге тесты надо править чтобы не падали, сразу возникает желание вынести код в другой класс\функцию\еще что-нить, а в данном классе оставить mock. G>Для TDD (test-first) это естественный процесс, для test-after — нет. Поэтому unit-тесты и рекомендуется для test-first.
Не так. Оцениваешь функциональность. Пишешь прототип, или даже просто схему на бумаге, что должно делаться и как. Смотришь на него, проводишь фактически design review (хоть в одиночку, но можно и с коллегами), оцениваешь, что и как реализовывать. На этом этапе могут выделяться чёткие подблоки, которые пригодны к тестированию отдельно от остального. Но ещё не под тест, потому что могут интерфейсы и контракты поменяться уже в процессе реализации, когда будет осознан ещё один пласт специфики. Потом всё это приблизительно написано, разделение на составные части, интерфейсы, контракты стабилизировались, теперь нетривиальные внутренние части покрываются базовыми тестами, подтверждающими их работоспособность. В основном это юнит-тесты, с минимальной настройкой среды проверки или вообще без неё, на уровне отдельных функций. Далее рассчитываешь и делаешь функциональные тесты, под базовые юзкейсы компонент, вычищаешь их от багов. В моём случае обычно минимальная единица, обладающая собственным поведением, называется приложением, и делать функциональные тесты для более мелких частей обычно нереально (хотя бывают и такие сущности). На следующем уровне тестируются связки из таких приложений, образующие функциональность части полной системы, например, тракт вокруг конкретной шины от первичных генераторов событий до финальных накопителей; назовём это интеграционными тестами. Только после этого можно говорить о завершении разработки конкретной подсистемы или даже приложения — когда оно показывает корректное прохождение интеграционных тестов. Далее идут общесистемные тесты, это уже делается людьми в QA отделе. Процесс такого развития никак не соответствует продвижению сверху вниз с твёрдой уверенностью в функционале конкретного уровня, а вместо этого можно говорить только о приблизительной чёткости понимания, которая будет доведена до полной уже после пробы на прототипе. Вот-с, где-то так.
N>>А без адаптации, по крайней мере для моей обстановки, никак не получается "очень ограниченное количество" ситуаций. Поэтому для меня whitebox testing имеет совсем другой смысл (см. предыдущие письма). G>То есть изначально написан плохо тестируемый код. Но эту ситуацию ты экстраполируешь на другие случаи, что далеко неверно.
Я продолжу эксплуатировать тот же пример — подсчёт codepoints. Ты действительно считаешь, что если функцию такого рода нельзя разделить на части, проверяемые по методу такого whitebox и подменяемые mock'ами, то это "изначально плохо тестируемый код"? Ответь, пожалуйста, конкретно, а не в сторону. Мне таки очень интересно. Пример простой, но полезный.
N>>Ты путаешь совершенно разные вещи. Ошибки часто одинаковые, это да. Специфика не уникальна, но она колоссально разная. Я не могу найти сейчас человека здесь на форуме, который заметен и который занимается хотя бы примерно теми же задачами, что я. Если есть, то они не светятся. В то же время я знаю, что в мире ещё около 5 групп идёт схожими путями. Но они не здесь. Поэтому я действительно склонен считать, что в пределах RSDN мои задачи уникальны и специфика их — тоже. Возражать разрешаю только на собственных примерах;) G>Я понятия не имею чем ты занимаешь, но если завесу тайны уберешь, то найдется много людей кто занимается тем-же или похожим.
Никакой завесы. Текущая основная задача — система управления и мониторинга HPC кластеров. Основной код на Erlang, всякий клей на Python. Предыдущая — VoIP свич, в основном Python, местами C. Ещё на одну до того — вообще не программизм по сути, хотя много всякой ерунды на Perl и C для автоматизации работы и вокруг. Дальше в историю копать не буду, а то и до матфизики на Фортране докопаемся.
Ожидаю теперь такой же откровенности с твоей стороны:)
G>Посмотрев пару сотен сообщений в твоем профиле видно что большую часть ты тут пишешь в КСВ и о жизни, инода отвечая на темы о сетевых протоколах и С++.
C++? Вот именно в него я не лезу, по крайней мере вглубь.
G>Поэтому то чем ты занимаешься связано с сетями, точно не web, скорее всего linux\C++. Возможно что-то в высокой вычислительной нагрузкой типа аудио\видео конференц связи или обработкой видеопотоков от камер.
Linux — да. Ещё FreeBSD. C++ — нет. VoIP — было, но не сейчас. Вычислительная нагрузка — да. Конференц-связь — нет, бог миловал. Видеопотоки — тоже нет, не путай меня с Лапшиным;)
G>Учитывая что основной язык для тебя — C++,
Ну вот объясни мне — как можно было _так_ читать, чтобы прийти к такому выводу???
G> то там unit-тесты с трудом возможны, ибо компиляция долгая и нормальных mock-фреймворков нету.
А если не заниматься безумными гипотезами и учесть, что у меня Erlang и проблем с юнит-тестами в общем-то нет?
Mock'и подставить тривиально, это условия запуска в рантайме. Адекватная модульность обычно или заложена, или легко закладывается.
Здравствуйте, 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 низкие и ниче сказать не могу.
Здравствуйте, gandjustas, Вы писали:
N>>>>Против этого я и не возражал.:) Вопрос в том, насколько они unit. G>>>А что им мешает быть unit-тестами? N>>Зависит от глубины переделки. Может оказаться, что неизменна и покрыто неизменившимися тестами только самая внешняя функциональность, которая не может быть сведена к простому "выстрелил вызовом функции — получил ответ — проверил", а требует долгой подготовки среды. Для меня это всё-таки не unit тест. Unit — это когда максимум подготовки среды это расстановка заглушек вместо рабочего кода. Да, это деление не академично, но мне оно полезнее.
G>Вполне правильное деление. Вот только непонятно зачем переделка? Если не писались unit-тесты для кода, то скорее всего не будут написаны, а если будут то от этого будет много затрат без наблюдаемого эффекта.
Зачем переделка — надо спросить авторов подветки. Собственно она началась с того, что решался вопрос, нужны юнит-тесты или нет, если будет большой рефакторинг, который поменяет всё внутри. Ты был один из авторов, так что отвечай сам на этот вопрос:)
N>>>>>>Что ты называешь вариантами использования? G>>>>>Это значит все возможные наборы параметров для которых имеются разные пути исполнения. N>>>>Для меня такое тестирование имеет очень мало смысла, только для отдельных специфических вариантов функций. Полный набор путей исполнения, мягко говоря, бесконечен. Можно было бы только в целях тестирования выделить отдельные логические подблоки типа "продвинуться на шаг алгоритма", выделяя их в функции, но часто это слишком неудобно. G>>>Вообще-то конечен. N>>Ну числа типа 2**2**32 всё равно за пределами представления даже в BER:), поэтому я позволил себе "округлить" их до бесконечности. G>По факту их гораздо меньше.
Уверен? Только на своих примерах?
G>>>Если писать тесты до кода, то он как-то сам адаптируется ;) N>>Писать тесты до кода означает или гарантированно окончательный дизайн, во что я не верю, или тупую наивность. По крайней мере я других вариантов пока не видел. Если есть где-то место, где это проходит, то там кому-то сильно повезло. G>Ни разу не означает, потому что есть рефакторинг. Если не рефакторить код, то unit-тесты и не нужны, потому что любое изменение кода будет вызвано или исправлением бага, или изменением требований.
Не вижу никакой логической связи между наличием или отсутствием рефакторинга и необходимости в юнит-тестах. Юнит-тест это функциональный тест уровня модуля, не имеющего самостоятельного поведения в системе. Тест нужен для того, чтобы быть уверенным в соответствии кода поставленным требованиям (как бы они ни назывались: ТЗ, контракты или шкурой чёрта лысого). Понятие, наличие и влияние рефакторинга полностью ортогонально этому.
N>>Я периодически использовал этот подход — тесты до кода — как стимулятор собственной работы против собственной же лени, то есть проблема была чисто организационная. Но я никогда не предполагал его как средство собственно дизайна. На это годится тестируемость в принципе, а не test-first. G>Когда напишешь тест то на него уже не получается просто забить, особенно когда gated checkins включено.
У нас нет никаких gated checkins. Но даже если бы были, это означало бы опять же, что введено административное требование соблюдать какие-то оформительские требования, не более того, и цена ему, как любому подобному административному требованию — высокая, если оно разумно обусловлено ситуацией, и ломаный грош в противном случае.
G> Если ты не пишешь заранее тесты, то рано или поздно у тебя получится забивание на тесты ради скорости написания, а потом тесты сделать, даже с мощными инструментами, очень сложно. Это ты кстати назвал "адаптацией".
Вопрос не в мощных инструментах, вопрос в понимании кода. Нужно вспомнить и войти в контекст. И это не сложно, это просто вопрос времени, которого может на это не быть.
N>>Не так. Оцениваешь функциональность. Пишешь прототип, или даже просто схему на бумаге, что должно делаться и как. Смотришь на него, проводишь фактически design review (хоть в одиночку, но можно и с коллегами), оцениваешь, что и как реализовывать. На этом этапе могут выделяться чёткие подблоки, которые пригодны к тестированию отдельно от остального. Но ещё не под тест, потому что могут интерфейсы и контракты поменяться уже в процессе реализации, когда будет осознан ещё один пласт специфики. Потом всё это приблизительно написано, разделение на составные части, интерфейсы, контракты стабилизировались, теперь нетривиальные внутренние части покрываются базовыми тестами, подтверждающими их работоспособность. В основном это юнит-тесты, с минимальной настройкой среды проверки или вообще без неё, на уровне отдельных функций. Далее рассчитываешь и делаешь функциональные тесты, под базовые юзкейсы компонент, вычищаешь их от багов. В моём случае обычно минимальная единица, обладающая собственным поведением, называется приложением, и делать функциональные тесты для более мелких частей обычно нереально (хотя бывают и такие сущности). На следующем уровне тестируются связки из таких приложений, образующие функциональность части полной системы, например, тракт вокруг конкретной шины от первичных генераторов событий до финальных накопителей; назовём это интеграционными тестами. Только после этого можно говорить о завершении разработки конкретной подсистемы или даже приложения — когда оно показывает корректное прохождение интеграционных тестов. Далее идут общесистемные тесты, это уже делается людьми в QA отделе. Процесс такого развития никак не соответствует продвижению сверху вниз с твёрдой уверенностью в функционале конкретного уровня, а вместо этого можно говорить только о приблизительной чёткости понимания, которая будет доведена до полной уже после пробы на прототипе. Вот-с, где-то так.
G>Многабукафниасилил. Но чтение по диагонали говорит что правильно пишешь, только гораздо шире чем я. Выделенное примерно соответствует тому что я написал, остальное out-of-scope. На этапе прототипирования тесты не нужны. Вообще методика test-first заставляет создавать приложение по слоям сверху вниз. Если заранее не знаешь как оно должно быть, то пишешь прототип без тестов, а потом уже переписываешь (именно переписываешь, нет смысла рефакторить прототип). Это в терминологии называются spike.
Всё это замечательно, но ещё проще и удобнее делать это же без жёсткого test-first :)
N>>Я продолжу эксплуатировать тот же пример — подсчёт codepoints. Ты действительно считаешь, что если функцию такого рода нельзя разделить на части, проверяемые по методу такого whitebox и подменяемые mock'ами, то это "изначально плохо тестируемый код"? Ответь, пожалуйста, конкретно, а не в сторону. Мне таки очень интересно. Пример простой, но полезный. G>Нет, я считаю только одно: если код для тестирования надо адаптировать, то он "изначально плохо тестируемый".
"Жаль, что мы так и не заслушали начальника транспортного цеха."
G>>>Учитывая что основной язык для тебя — C++, N>>Ну вот объясни мне — как можно было _так_ читать, чтобы прийти к такому выводу??? G>Сорри, сейчас редко встретишь тех кто пишет на С, а не С++
Так у меня на >99% и не C. :)
G>>> то там unit-тесты с трудом возможны, ибо компиляция долгая и нормальных mock-фреймворков нету. N>>А если не заниматься безумными гипотезами и учесть, что у меня Erlang и проблем с юнит-тестами в общем-то нет? N>>Mock'и подставить тривиально, это условия запуска в рантайме. Адекватная модульность обычно или заложена, или легко закладывается. G>Так эрланг же динамически типизированный, там вообще необходимо тесты писать, п-другому не проверишь.
Местных немерлистов наслушался?;) Проблемы непроверяемости при динамической типизации сильно преувеличены или относятся к отдельным областям, которые для меня диковинка. В случае Erlang даже dialyzer, производящий статический контроль типов, помогает выловить некоторое количество плюх, но основные проблемы не от них.
Здравствуйте, netch80, Вы писали:
N>Здравствуйте, gandjustas, Вы писали:
N>>>>>Против этого я и не возражал. Вопрос в том, насколько они unit. G>>>>А что им мешает быть unit-тестами? N>>>Зависит от глубины переделки. Может оказаться, что неизменна и покрыто неизменившимися тестами только самая внешняя функциональность, которая не может быть сведена к простому "выстрелил вызовом функции — получил ответ — проверил", а требует долгой подготовки среды. Для меня это всё-таки не unit тест. Unit — это когда максимум подготовки среды это расстановка заглушек вместо рабочего кода. Да, это деление не академично, но мне оно полезнее.
G>>Вполне правильное деление. Вот только непонятно зачем переделка? Если не писались unit-тесты для кода, то скорее всего не будут написаны, а если будут то от этого будет много затрат без наблюдаемого эффекта.
N>Зачем переделка — надо спросить авторов подветки. Собственно она началась с того, что решался вопрос, нужны юнит-тесты или нет, если будет большой рефакторинг, который поменяет всё внутри. Ты был один из авторов, так что отвечай сам на этот вопрос
вообще не понимаю как рефакторинг без тестов делать, нужно знать что поведение не поменяется, кроме тестов тут мало что поможет.
Но если их заранее не было, то для их написания надо будет еще переделать код, а это двойные затраты.
Проще переписать кусок, предварительно написав для него тесты.
N>>>>>>>Что ты называешь вариантами использования? G>>>>>>Это значит все возможные наборы параметров для которых имеются разные пути исполнения. N>>>>>Для меня такое тестирование имеет очень мало смысла, только для отдельных специфических вариантов функций. Полный набор путей исполнения, мягко говоря, бесконечен. Можно было бы только в целях тестирования выделить отдельные логические подблоки типа "продвинуться на шаг алгоритма", выделяя их в функции, но часто это слишком неудобно. G>>>>Вообще-то конечен. N>>>Ну числа типа 2**2**32 всё равно за пределами представления даже в BER, поэтому я позволил себе "округлить" их до бесконечности. G>>По факту их гораздо меньше.
N>Уверен? Только на своих примерах?
На любых. Есть хорошая утилита pex, которая как раз показывает такие пути. Запускал её давно на коде, который писался без тестов. Метод на 200 строк (метрика, исходников почти 1000 строк) — около 15 путей. То есть для полного покрытия надо не более 15 тестов.
Даже если взять 30 тестов на 1000 строк исходников, то это не дофига. А если применять TDD, то будет еще меньше.
G>>>>Если писать тесты до кода, то он как-то сам адаптируется N>>>Писать тесты до кода означает или гарантированно окончательный дизайн, во что я не верю, или тупую наивность. По крайней мере я других вариантов пока не видел. Если есть где-то место, где это проходит, то там кому-то сильно повезло. G>>Ни разу не означает, потому что есть рефакторинг. Если не рефакторить код, то unit-тесты и не нужны, потому что любое изменение кода будет вызвано или исправлением бага, или изменением требований.
N>Не вижу никакой логической связи между наличием или отсутствием рефакторинга и необходимости в юнит-тестах. Юнит-тест это функциональный тест уровня модуля, не имеющего самостоятельного поведения в системе. Тест нужен для того, чтобы быть уверенным в соответствии кода поставленным требованиям (как бы они ни назывались: ТЗ, контракты или шкурой чёрта лысого). Понятие, наличие и влияние рефакторинга полностью ортогонально этому.
Связь сама прямая. Рефакторинг — изменение (улучшение) кода, без изменения наблюдаемого поведения. Как гарантировать неизменность наблюдаемого поведения?
N>>>Я периодически использовал этот подход — тесты до кода — как стимулятор собственной работы против собственной же лени, то есть проблема была чисто организационная. Но я никогда не предполагал его как средство собственно дизайна. На это годится тестируемость в принципе, а не test-first. G>>Когда напишешь тест то на него уже не получается просто забить, особенно когда gated checkins включено.
N>У нас нет никаких gated checkins. Но даже если бы были, это означало бы опять же, что введено административное требование соблюдать какие-то оформительские требования, не более того, и цена ему, как любому подобному административному требованию — высокая, если оно разумно обусловлено ситуацией, и ломаный грош в противном случае.
На gated checkins тесты запускается и код не коммитится если они надают.
G>> Если ты не пишешь заранее тесты, то рано или поздно у тебя получится забивание на тесты ради скорости написания, а потом тесты сделать, даже с мощными инструментами, очень сложно. Это ты кстати назвал "адаптацией".
N>Всё это замечательно, но ещё проще и удобнее делать это же без жёсткого test-first
TDD не предполагает test-first всегда.
Здравствуйте, gandjustas, Вы писали:
N>>Зачем переделка — надо спросить авторов подветки. Собственно она началась с того, что решался вопрос, нужны юнит-тесты или нет, если будет большой рефакторинг, который поменяет всё внутри. Ты был один из авторов, так что отвечай сам на этот вопрос:) G>вообще не понимаю как рефакторинг без тестов делать, нужно знать что поведение не поменяется, кроме тестов тут мало что поможет.
Гм. Кто-то тут преимущества статических языков вспоминал:) почему-то:)
N>>>>>>>>Что ты называешь вариантами использования? G>>>>>>>Это значит все возможные наборы параметров для которых имеются разные пути исполнения. N>>>>>>Для меня такое тестирование имеет очень мало смысла, только для отдельных специфических вариантов функций. Полный набор путей исполнения, мягко говоря, бесконечен. Можно было бы только в целях тестирования выделить отдельные логические подблоки типа "продвинуться на шаг алгоритма", выделяя их в функции, но часто это слишком неудобно. G>>>>>Вообще-то конечен. N>>>>Ну числа типа 2**2**32 всё равно за пределами представления даже в BER:), поэтому я позволил себе "округлить" их до бесконечности. G>>>По факту их гораздо меньше. N>>Уверен? Только на своих примерах? G>На любых. Есть хорошая утилита pex, которая как раз показывает такие пути. Запускал её давно на коде, который писался без тестов. Метод на 200 строк (метрика, исходников почти 1000 строк) — около 15 путей. То есть для полного покрытия надо не более 15 тестов. G>Даже если взять 30 тестов на 1000 строк исходников, то это не дофига. А если применять TDD, то будет еще меньше.
У тебя есть PEX? Мне его поставить некуда, Win7 даже в кошмарах не снится. Напусти его, например, на библиотечный qsort(). mbslen(), если он сам реализован, а не через виндовый рантайм. Больше вариантов пока не вижу, потому что юниксовых функций у вас нет;) И тогда посмотрим, насколько он ценен:) Мне искренне интересно — если там что-то ценное, можно будет применить или хотя бы найти аналог.
G>>>>>Если писать тесты до кода, то он как-то сам адаптируется ;) N>>>>Писать тесты до кода означает или гарантированно окончательный дизайн, во что я не верю, или тупую наивность. По крайней мере я других вариантов пока не видел. Если есть где-то место, где это проходит, то там кому-то сильно повезло. G>>>Ни разу не означает, потому что есть рефакторинг. Если не рефакторить код, то unit-тесты и не нужны, потому что любое изменение кода будет вызвано или исправлением бага, или изменением требований. N>>Не вижу никакой логической связи между наличием или отсутствием рефакторинга и необходимости в юнит-тестах. Юнит-тест это функциональный тест уровня модуля, не имеющего самостоятельного поведения в системе. Тест нужен для того, чтобы быть уверенным в соответствии кода поставленным требованиям (как бы они ни назывались: ТЗ, контракты или шкурой чёрта лысого). Понятие, наличие и влияние рефакторинга полностью ортогонально этому. G>Связь сама прямая. Рефакторинг — изменение (улучшение) кода, без изменения наблюдаемого поведения. Как гарантировать неизменность наблюдаемого поведения?
Мой вопрос был в слове _юнит_. Тесты бывают разные. Программа состоит из множества уровней реализации. Могут быть отдельные функции, модули, группы модулей, приложения (в нашем случае — именно на этом уровне), подсистемы, системы. Для каждого из них может быть свой уровень теста. Всё, что выше модуля — это уже не юнит-тесты. Но рефакторинг может, например, слить две подсистемы в одну, или разделить их. Чем тебе тут помогут именно юнит-тесты?
N>>>>Я периодически использовал этот подход — тесты до кода — как стимулятор собственной работы против собственной же лени, то есть проблема была чисто организационная. Но я никогда не предполагал его как средство собственно дизайна. На это годится тестируемость в принципе, а не test-first. G>>>Когда напишешь тест то на него уже не получается просто забить, особенно когда gated checkins включено. N>>У нас нет никаких gated checkins. Но даже если бы были, это означало бы опять же, что введено административное требование соблюдать какие-то оформительские требования, не более того, и цена ему, как любому подобному административному требованию — высокая, если оно разумно обусловлено ситуацией, и ломаный грош в противном случае. G>На gated checkins тесты запускается и код не коммитится если они надают.
"О наблюдении за наблюдающими за наблюдателями" ((c)): кто гарантирует, что набор тестов адекватен?
G>>> Если ты не пишешь заранее тесты, то рано или поздно у тебя получится забивание на тесты ради скорости написания, а потом тесты сделать, даже с мощными инструментами, очень сложно. Это ты кстати назвал "адаптацией". N>>Всё это замечательно, но ещё проще и удобнее делать это же без жёсткого test-first :) G>TDD не предполагает test-first всегда.
Test-driven development requires developers to create automated unit tests that define code requirements (immediately) before writing the code itself.
Это из википедии. Если ты пользуешься другим источником определений — пожалуйста, но определи его заранее, чтобы не было разногласий только из-за этого.
Здравствуйте, netch80, Вы писали:
G>>Даже если взять 30 тестов на 1000 строк исходников, то это не дофига. А если применять TDD, то будет еще меньше. N>У тебя есть PEX? Мне его поставить некуда, Win7 даже в кошмарах не снится. Напусти его, например, на библиотечный qsort(). mbslen(), если он сам реализован, а не через виндовый рантайм. Больше вариантов пока не вижу, потому что юниксовых функций у вас нет И тогда посмотрим, насколько он ценен Мне искренне интересно — если там что-то ценное, можно будет применить или хотя бы найти аналог.
Конечно есть, то что ты его поставить не можешь — твои проблемы. pex работает только для .NET, и разработчики BCL при написании четвертной версии натравливали pex на библиотеки. Говорили что находили баги о которых ранее не подозревали. Насчет аналогичных инструментов для C не знаю, вряд ли будет. Он слишком близок к ассемблеру, вряд ли там что-то можно проверить. Хотя есть верифицирующий компилятор.
G>>Связь сама прямая. Рефакторинг — изменение (улучшение) кода, без изменения наблюдаемого поведения. Как гарантировать неизменность наблюдаемого поведения? N>Мой вопрос был в слове _юнит_. Тесты бывают разные. Программа состоит из множества уровней реализации. Могут быть отдельные функции, модули, группы модулей, приложения (в нашем случае — именно на этом уровне), подсистемы, системы. Для каждого из них может быть свой уровень теста. Всё, что выше модуля — это уже не юнит-тесты. Но рефакторинг может, например, слить две подсистемы в одну, или разделить их. Чем тебе тут помогут именно юнит-тесты?
А может и не слить. Гораздо больше маленьких рефакторингов выполняется, чем больших. Причем далеко не все из них занимаются механическим изменением код, которое можно автоматизировать.
G>>На gated checkins тесты запускается и код не коммитится если они надают. N>"О наблюдении за наблюдающими за наблюдателями" ((c)): кто гарантирует, что набор тестов адекватен?
Ты гарантируешь, когда пишешь тест. Иначе никак. Юнит-тесты кстати очень хороши, так как будут простыми и линейными.
G>>>> Если ты не пишешь заранее тесты, то рано или поздно у тебя получится забивание на тесты ради скорости написания, а потом тесты сделать, даже с мощными инструментами, очень сложно. Это ты кстати назвал "адаптацией". N>>>Всё это замечательно, но ещё проще и удобнее делать это же без жёсткого test-first G>>TDD не предполагает test-first всегда.
N>
Test-driven development requires developers to create automated unit tests that define code requirements (immediately) before writing the code itself.
Это из википедии. Если ты пользуешься другим источником определений — пожалуйста, но определи его заранее, чтобы не было разногласий только из-за этого.
Это черезмерное упрощение, примерно как ООП — данные+методы (см соседнюю тему).
Здравствуйте, gandjustas, Вы писали:
G>>>Даже если взять 30 тестов на 1000 строк исходников, то это не дофига. А если применять TDD, то будет еще меньше. N>>У тебя есть PEX? Мне его поставить некуда, Win7 даже в кошмарах не снится. Напусти его, например, на библиотечный qsort(). mbslen(), если он сам реализован, а не через виндовый рантайм. Больше вариантов пока не вижу, потому что юниксовых функций у вас нет;) И тогда посмотрим, насколько он ценен:) Мне искренне интересно — если там что-то ценное, можно будет применить или хотя бы найти аналог. G>Конечно есть, то что ты его поставить не можешь — твои проблемы.
Ты всерьёз думаешь, что это мои _проблемы_? Я так не думаю, он мне нафиг не нужен кроме как для этой дискуссии:)
G> pex работает только для .NET,
Значит, в морг.
G>>>Связь сама прямая. Рефакторинг — изменение (улучшение) кода, без изменения наблюдаемого поведения. Как гарантировать неизменность наблюдаемого поведения? N>>Мой вопрос был в слове _юнит_. Тесты бывают разные. Программа состоит из множества уровней реализации. Могут быть отдельные функции, модули, группы модулей, приложения (в нашем случае — именно на этом уровне), подсистемы, системы. Для каждого из них может быть свой уровень теста. Всё, что выше модуля — это уже не юнит-тесты. Но рефакторинг может, например, слить две подсистемы в одну, или разделить их. Чем тебе тут помогут именно юнит-тесты? G>А может и не слить. Гораздо больше маленьких рефакторингов выполняется, чем больших. Причем далеко не все из них занимаются механическим изменением код, которое можно автоматизировать.
Значит, ответа не будет. Ясно.
G>>>На gated checkins тесты запускается и код не коммитится если они надают. N>>"О наблюдении за наблюдающими за наблюдателями" ((c)): кто гарантирует, что набор тестов адекватен? G>Ты гарантируешь, когда пишешь тест. Иначе никак.
Quod erat demonstrandum.
G>>>>> Если ты не пишешь заранее тесты, то рано или поздно у тебя получится забивание на тесты ради скорости написания, а потом тесты сделать, даже с мощными инструментами, очень сложно. Это ты кстати назвал "адаптацией". N>>>>Всё это замечательно, но ещё проще и удобнее делать это же без жёсткого test-first :) G>>>TDD не предполагает test-first всегда. N>>
Test-driven development requires developers to create automated unit tests that define code requirements (immediately) before writing the code itself.
Это из википедии. Если ты пользуешься другим источником определений — пожалуйста, но определи его заранее, чтобы не было разногласий только из-за этого. G>Это черезмерное упрощение, примерно как ООП — данные+методы (см соседнюю тему).
Видимо, есть какое-то тайное знание, которое мне знать не положено — рылом не вышел. OK.
G>>>>Связь сама прямая. Рефакторинг — изменение (улучшение) кода, без изменения наблюдаемого поведения. Как гарантировать неизменность наблюдаемого поведения? N>>>Мой вопрос был в слове _юнит_. Тесты бывают разные. Программа состоит из множества уровней реализации. Могут быть отдельные функции, модули, группы модулей, приложения (в нашем случае — именно на этом уровне), подсистемы, системы. Для каждого из них может быть свой уровень теста. Всё, что выше модуля — это уже не юнит-тесты. Но рефакторинг может, например, слить две подсистемы в одну, или разделить их. Чем тебе тут помогут именно юнит-тесты? G>>А может и не слить. Гораздо больше маленьких рефакторингов выполняется, чем больших. Причем далеко не все из них занимаются механическим изменением код, которое можно автоматизировать. N>Значит, ответа не будет. Ясно.
Ответа на что? Ты выдвинул предположение что при масштабных рефакторингах юнит-тесты не помогают. Я в принципе согласен с тобой, но все равно это не повод не писать их.
Опять приходим к простому выводу: если ты не видишь пользы в юнит-тестах — не пиши их. Возможно у тебя приложение уже написано так что юнит-тестами не покрывается и\или язык неподходящий. Но это все не проблема юнит-тестов.
G>>>>>> Если ты не пишешь заранее тесты, то рано или поздно у тебя получится забивание на тесты ради скорости написания, а потом тесты сделать, даже с мощными инструментами, очень сложно. Это ты кстати назвал "адаптацией". N>>>>>Всё это замечательно, но ещё проще и удобнее делать это же без жёсткого test-first G>>>>TDD не предполагает test-first всегда. N>>>
Test-driven development requires developers to create automated unit tests that define code requirements (immediately) before writing the code itself.
Это из википедии. Если ты пользуешься другим источником определений — пожалуйста, но определи его заранее, чтобы не было разногласий только из-за этого. G>>Это черезмерное упрощение, примерно как ООП — данные+методы (см соседнюю тему).
N>Видимо, есть какое-то тайное знание, которое мне знать не положено — рылом не вышел. OK.
Читать надо, я ссылки не записываю, иначе тебе подкинул бы. Если твое познание в TDD (да или в любом другом предмете) ограничивается википедией, то неудивительно что рано или поздно тебе сообщают вещи, о которых ты не знал.
Здравствуйте, gandjustas, Вы писали:
G>>>>>TDD не предполагает test-first всегда. N>>>>
Test-driven development requires developers to create automated unit tests that define code requirements (immediately) before writing the code itself.
Это из википедии. Если ты пользуешься другим источником определений — пожалуйста, но определи его заранее, чтобы не было разногласий только из-за этого. G>>>Это черезмерное упрощение, примерно как ООП — данные+методы (см соседнюю тему). N>>Видимо, есть какое-то тайное знание, которое мне знать не положено — рылом не вышел. OK. G>Читать надо, я ссылки не записываю, иначе тебе подкинул бы. Если твое познание в TDD (да или в любом другом предмете) ограничивается википедией, то неудивительно что рано или поздно тебе сообщают вещи, о которых ты не знал.
Ну продолжай сражаться с ветряными мельницами ("если" какое-то совершенно отфонарное предположение, "то" ты успешно победил врага в моём лице, я якобы в глубокой заднице, сижу и хнычу;)) Только без меня, OK? Ты продолжаешь косвенно ссылаться на тайное знание, источников не приводишь (видите ли, "я ссылки не записываю"), и ладно бы речь ещё шла о каком-то хитром аспекте — тогда ещё имеет смысл говорить об уровне опыта и о знании неожиданностей. Но ты пытаешься навязать мне свою нормировку базовых принципов (а обязательность test-first это таки базовый принцип, самый базовый из) и при этом ссылаешься на тайное знание. Извини, на такое ответ только один — или приводи то, что может быть названо с какой-то точки зрения стандартом (если нет нормативного документа от какого-нибудь IEEE, ISO, IEC или просто ГОСТ, то и википедия сойдёт), или отправляйся в пешее эротическое путешествие, ибо мне твои иллюзии о нормативах нафиг не сдались.
Собственно, это касается и остальной части сообщения: мне надоело общаться с твоими иллюзиями, баста.
Здравствуйте, ylem, Вы писали:
Y>Может я неправильно все делаю, но именно юнит-тестами удобно отлаживать сколь-нибудь сложную логику. Y>Ну а потом они просто остаются. Есть пить почти не просят.
В разных случаях, рулят разные вещи. Например, при написании компиляторов юнит-тесты имеют смысл только на самых ранних стадиях. Далее в них смысла нет.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, ylem, Вы писали:
Y>>Может я неправильно все делаю, но именно юнит-тестами удобно отлаживать сколь-нибудь сложную логику. Y>>Ну а потом они просто остаются. Есть пить почти не просят.
VD>В разных случаях, рулят разные вещи. Например, при написании компиляторов юнит-тесты имеют смысл только на самых ранних стадиях. Далее в них смысла нет.
Здравствуйте, Abyx, Вы писали:
VD>>В разных случаях, рулят разные вещи. Например, при написании компиляторов юнит-тесты имеют смысл только на самых ранних стадиях. Далее в них смысла нет.
A>почему нет?
Потому что нет смысла.
Зачем писать синтетический тест тестирующий отдельную функцию когда можно написать программу компиляция и запуск которой проверит эту функцию в сто раз лучше?
Язык программирования — это такая штука которая при небольшом количестве базовых блоков дает невероятный комбинаторный взрыв. Ведь почти на любом языке можно написать почти любую программу.
На мой взгляд основная задача тестов (в независимости от их типа) — фиксация и описание поведения. Мы не можем описать поведение через систему типов. И тесты становятся эдаким живым описанием поведения. Ну, а ловля ошибок при рефакторинге — это уже следствие данной фиксации. Программные продукты вещь сложная. Всего не запомнишь. Если зафиксировать некоторую фичу в виде теста, то потом можно полагаться на данную фиксацию.
И совершенно по фигу каким видом тестов мы зафиксировали это самое поведение.
Так же совершенно все равно пишется ли тест до или после написания функциональности. Это предпочтения программиста. Бывают случае когда писать тест до удобнее. Например, при исправлении ошибки. Но бывает, случае когда в процессе написания кода производится исследовательская деятельность. При этом код может меняться по сто раз на дню. И пытаться написать тест для еще не сформировавшегося решения — это самое глупое что только можно придумать.
В общем, фанатизм никогда не дает хороших результатов. В каждом отдельном случае нужно выбирать свои подходы. От того для меня фанатики TDD и те кто орет что тесты не нужны мало чем отличаются. Это люди из одного лагеря — лагеря людей не способных предатель своих предрассудков.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, Abyx, Вы писали:
VD>>>В разных случаях, рулят разные вещи. Например, при написании компиляторов юнит-тесты имеют смысл только на самых ранних стадиях. Далее в них смысла нет.
A>>почему нет?
VD>Потому что нет смысла.
VD>Зачем писать синтетический тест тестирующий отдельную функцию когда можно написать программу компиляция и запуск которой проверит эту функцию в сто раз лучше?
VD>Язык программирования — это такая штука которая при небольшом количестве базовых блоков дает невероятный комбинаторный взрыв. Ведь почти на любом языке можно написать почти любую программу.
Я не верю, что можно написать тестовые программы которые дадут 100% покрытия.
Даже если это возможно, они покроют только удачные сценарии.
Если код компилятора состоит из потенциально реюзабельных частей, они будут делать чуть больше чем требуется в текущей версии программы:
void foo(X* p)
{
if (p == NULL)
bar();
...
}
// единственное использование foo во всей программе (на данный момент)
X x;
foo(&x);
— тут вызов bar() это вообще мертвый код, его может даже не быть в бинарнике программы.
VD>Так же совершенно все равно пишется ли тест до или после написания функциональности. Это предпочтения программиста. Бывают случае когда писать тест до удобнее. Например, при исправлении ошибки. Но бывает, случае когда в процессе написания кода производится исследовательская деятельность. При этом код может меняться по сто раз на дню. И пытаться написать тест для еще не сформировавшегося решения — это самое глупое что только можно придумать.
прототипирование — это отдельный случай.
когда решение сформировалось, тогда уже можно написать тест и затем написать код начисто
Здравствуйте, Abyx, Вы писали:
VD>>Язык программирования — это такая штука которая при небольшом количестве базовых блоков дает невероятный комбинаторный взрыв. Ведь почти на любом языке можно написать почти любую программу.
A>Я не верю, что можно написать тестовые программы которые дадут 100% покрытия.
И правильно делаешь. Потому в любом компиляторе есть ошибки.
A>Даже если это возможно, они покроют только удачные сценарии.
Тут вопрос в другом. То что покрыто является спецификацией поддерживаемой функциональности. По уму дюбсая описанная фича должна иметь один или более тестов.
Я сам не раз нарывался на то, что пытаясь решить некоторую проблему менял (на первый взгляд, незначительно) поведение компилятора и это приводило к падению тестов. Когда я начианал разбираться, то чаще всего оказывалось, что есть поведение о котором я не знал или знал, но не предполагал, что оно может повлиять на данный случай. Если бы таких тестов не было, то я бы испортил компилятор.
Кроме того на тесты можно смотреть как на "спецификацию на примерах". Она значительно надежнее и точнее чем описание того же самого на естественном языке.
A>Если код компилятора состоит из потенциально реюзабельных частей, они будут делать чуть больше чем требуется в текущей версии программы:... A>- тут вызов bar() это вообще мертвый код, его может даже не быть в бинарнике программы.
Зачем обсуждать какие-то гипотетические вещи когда есть реальные тесты? Вот позитивные тесты. Позитивные означает, что они должны пройти успешно не выдав сообщений об ошибках, не свалив компилятор и их выхлоп должен совпасть с описанным в комментариях. Принцип очень простой. Мы компилируем исходник. Если все ОК, запускаем его и сравниваем его консольный выхлоп с тем что описан в комментарии (между BEGIN-OUTPUT и END-OUTPUT). Если блока BEGIN-OUTPUT/END-OUTPUT нет, то проверяется только сам факт компилируемости программы.
Так же есть негативные тесты. Их задача проверить реакцию на наличие ошибок. Ошибки описываются в виде регексов в комментариях в тех строках где они должны появиться.
VD>>Так же совершенно все равно пишется ли тест до или после написания функциональности. Это предпочтения программиста. Бывают случае когда писать тест до удобнее. Например, при исправлении ошибки. Но бывает, случае когда в процессе написания кода производится исследовательская деятельность. При этом код может меняться по сто раз на дню. И пытаться написать тест для еще не сформировавшегося решения — это самое глупое что только можно придумать.
A>прототипирование — это отдельный случай. A>когда решение сформировалось, тогда уже можно написать тест и затем написать код начисто
Когда решение сформулировано, то у нас уже есть решение и ты уже не можешь сначала написать тест, а потом реализацию. Об этом я и говорил.
Могу описать то как я пишу тесты для того же компилятора.
Если я ловлю ошибку, то первым делом я пытаюсь создать минимальный тест воспроизводящий ошибку. Если таковой нельзя создать, то возможно, что и отлов ошибки будет отложен в долгий ящик. И тест я пишу не из религиозных соображений, а просто потому, что без него мне нечего будет отлаживать. На этом этапе тест еще не оформлен так как надо. Он только лишь воспроизводит баг. Далее я помещаю его в некую среду где я могу произвести отладку. В ней я, пользуясь отладчиком, разбираюсь что же не так в компиляторе и исправляю компилятор.
В процессе исправления я многократно перекомпилирую компилятор и прогоняю тот же тест. Если я вижу, что ошибка исправлена, то я запускаю двухпроходную сборку компилятора с прогоном вех тестов.
Обрати внимание, что новый тест при этом в них еще не входит.
Если все ОК и двухпроходная сборка с тестами прошли, то я создаю новый тест, помещаю его в состав тестов и еще раз прогоняю тесты. В этот раз уже на двух палтформах (для дотнет 2+ и 4). Если и в этот раз все ОК, то изменения комитятся в репозиторий. Если возникли проблемы, то начинается дальнейшее разбирательство.
Я не железный, по этому иногда халтурю. Но любое отступление от данного процесса чревато проблемами, которые периодически и появляются. И это подталкивает к более четкому соблюдению процесса.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, DorfDepp, Вы писали:
DD>...если они на практике ничего не ловят. Каждый метод будет прекрасно работать в изоляции, а упадет обязательно на нестыковке компонент, из-за старого формата данных в базе и т.п. Ради чего надо писать 10 минут метод и потом час-два к нему тесты, если пользы ровно ноль?
А зачем вы пишете такие тесты, от которых пользы ровно ноль?
Я знаю только две бесконечные вещи — Вселенную и человеческую глупость, и я не совсем уверен насчёт Вселенной. (c) А. Эйнштейн
P.S.: Винодельческие провинции — это есть рулез!