Здравствуйте, Ikemefula, Вы писали:
I>Здравствуйте, gandjustas, Вы писали:
I>>>ТДД работает при уточнении деталей дизайна. То есть, сначала нужно решить задачу, а именно представить как будет работаь новая система. Потом решить вторую задачу — преобразовать старый код в новую вычислительную модель. И здесь куча нюансов.
G>>А как это будет выглядеть в реальности? G>>Например я собираюсь разрабатывать что-то типа StackOverflow. У меня уже есть эскиз дизайна и я знаю что это будет ASP.NET MVC и SQL Server. G>>В какой момент начинать писать тесты, так чтобы потом не было мучительно больно, когда я захочу дизайн поменять?
I>В TDD нет ничего про тесты уровня приложения. Скажем, Кет Бек ничего конкретного не говорит.
I>В реальности это выглядит так — надо быть в курсе того, как делать дизайн, который можно будет сменить. После того, как ты решишь эту задачу, можно начинать писать тесты. Но это будут тесты в основном компонентов, а не приложения.
Каких компонентов? В какой момент появляются эти "компоненты"?
Например в ASP.NET MVC контроллеры — это компоненты? Роуты — компоненты? Фильтры — компоненты?
I>>В реальности это выглядит так — надо быть в курсе того, как делать дизайн, который можно будет сменить. После того, как ты решишь эту задачу, можно начинать писать тесты. Но это будут тесты в основном компонентов, а не приложения.
G>Каких компонентов? В какой момент появляются эти "компоненты"?
Как только тебе хочется написать какой нибудь код. Вместо этого пишешь тест.
G>Например в ASP.NET MVC контроллеры — это компоненты? Роуты — компоненты? Фильтры — компоненты?
Здравствуйте, Ikemefula, Вы писали:
I>Есть несколькол нюансов, I>1 тесты в ТДД в корне неверно понимать, как тесты.
I> Хотя тесты пишутся, но их назначение это спецификация. Это самая большая дыра в ТДД, Бертран Мейер, например, утверждает что тесты вместо спецификации это просто баловство. Сам Кент Бек утверждает, что тесты в ТДД мало чем похожи на юнит-тесты, хотя и очень похожи
Спецификация какая именно? Если внешняя (что код должен делать независимо от его структуры), то по современной терминологии обеспечение этого называется behavior-driven development (BDD), а не TDD. TDD же "осталось" отвечать за внутренние тесты, насколько оно вообще может быть адекватно определено.
I>2 ТДД это скорее test driven design I> TDD оно изначально про проектирование, а не разработку, тестирование или покрытие тестами и тд.
I>3 Тесты в тдд предназначены быть инструментом разработчика и никак не тестера, архитектора, тим-лида, продукт-овнера и тд и тд и тд.
Разве что в том смысле, что это его дело — определить, как именно покрыть свой код, чтобы обеспечить свою ответственность за него. Но почему для этого исключать ответственность вышестоящих?
Здравствуйте, Шахтер, Вы писали:
Ш>А теперь серьёзно. Существует практический аспект обратного теста Тьюринга. Ш>Это методика обратного тестирования. Я её использую при разработки ответственного софта. Ш>Суть её в следующем. Мы пишем некоторый софтварный компонент. Ш>Далее мы пишем тесты для него. Прогоняем. Ш>Большинство программистов на этой стадии останавливаются. Но останавливаться не надо. Ш>Следующий шаг -- обратное тестирование. Мы берем исходный код и целенапрвленно вводим в него дефекты. И запускаем тесты. Ш>Если тесты не улавливают наличие дефектов, то тест-систему корректируем.
Я об этом давно писал, например, тут. В свою очередь, с моей стороны это переформулировка подходов, которые использовались при разработке военной и т.п. техники и соответственно были применены после постановки вопросов "а откуда уверенность, что оно просто не врёт, говоря, что всё хорошо?"
Но в моей методике дефекты в основном вводятся не в код (хотя и это можно сделать — выставляя через тест-окружение команды "сломаться тут"), а в задания теста. Если тест типа "сделали 10 нажатий мышой в конкретных местах и проверили посылку файла", то инверсия — пропустить одно нажатие и убедиться, что файл не посылается. Необходимости делать такие точки пробной поломки именно в рабочем коде я пока не видел.
Ш>Для проведения теста Тьюринга нужно грамотно выбрать группу экспертов. Иначе это будет профанация. Ш>Как это сделать? А очень просто, с помощью обратного теста Тьюринга. Т.е. если группа экспертов не может отличить человека от компьютера, то мы её выгоняем.
Ну так оно в общем и делается, независимо от того, что проверяется. Странно, что это где-то ещё приходится явно формулировать. Вот что обычно требует уточнения — это требования к надёжности такого детектирования, они берутся из теории мат. статистики.
Здравствуйте, jazzer, Вы писали:
Ш>>Тесты валится не должны. Они валится могут, если ты сделал ошибку в коде.
J>Так в том и дело. Ты стартуешь с (предположительно) правильно написанного кода и только потом пишешь к нему тесты. J>А в TDD стартуют с неправильного (или вообще отсутствующего) кода. Т.е. правильного кода еще нет, а тесты уже есть, и они все не срабатывают. И твоя цель — сделать тесты срабатывающими.
И это работает только в индусском оффшоре А почему? А потому, что если у тебя уже есть правильный код, а спецификация поменялась, тебе приходится или плевать на принципы TDD и заводить тест к уже работающему коду, или явно ломать код, чтобы убедиться, что тест его проверяет (то, что как раз описал Шахтёр). Например, тебе добавили в спеку "должно работать с числами до миллиарда". Ты задумчиво лезешь в код, видишь везде int64_t и начинаешь гадать, что сделать: отрапортовать, что всё и так работает? переписать весь код на short, чтобы поломалось, затем обратно, чтобы починить?
А почему индусском оффшоре — потому что только там могут позволить себе выкинуть весь код, чтобы написать с нуля, зато строго по TDD
J>И ты думаешь о достаточности тестов до того, как начнешь писать код (т.е. фактически формулируешь требования к коду и излагаешь их на языке тестов). J>И тесты становятся своего рода документацией к (ненаписанному еще) коду, показывая, что должно срабатывать, а что не должно.
После чего законно вешаешь над рабочим местом табличку "Я — бог!" И все с этим согласны, потому что простой смертный не может делать сколь-нибудь сложный код без нескольких попыток написания (и к которому тесты будут написаны уже после того, как код заработал).
Впрочем, задача тривиально решается после этого с использованием любого клана norton commander. После того, как всё сделано, создаём пустое репо, кладём туда вычищенные тесты (после 30-й итерации, когда наконец поняли, как же код должен и может работать, с учётом всех тараканов библиотек). Коммитим. Дальше в течение пары дней в перерывах между фишками и контактиком лениво делаем F5 на файл, коммитим, радуемся сокращению количества красных огней на пару штук и повторяем цикл. Первая часть, когда код реально писался, списывается на R&D работы. Вторая — типа таки наконец писали код. Топ-менеджмент радуется отчёту о покрытии разработки нормами TDD на 146% и заказывает себе новый Q7 взамен разбитого по пьяни, а программист получает премию по перевыполнению KLOC/day. Для такой цели — да, TDD рулит.
Здравствуйте, мыщъх, Вы писали:
М>это очень древняя методика. она использовалась еще когда ассемблера не было.
М>в программу умышленно вносится определенное число ошибок. пускай для определенности сто. если в результате тестирования выявлено только 50, то это позволяет нам оценить качество тестирования и кол-во скрытых ошибок в релизе.
О! В варианте проверить и сам отдел QA/QC таким образом — да, это надо отметить особо.
Хоть и старое, но используется крайне редко.
Здравствуйте, Ikemefula, Вы писали:
I>ТДД это не написание системы тестов. ТДД оно про проектирование. Знать код не нужно, но вот представлять, как решается задача очень даже нужно. Скажем, нужно хотя бы представлять более-менее детально вычислительную модель.
Могу тут только присоединиться к комментариям Sinclair. Как известно, только с третьей версии любая программа становится пригодной к использованию При этом она может пройти принципиальную переделку дизайна, и не одну. Поэтому любая методика, рассчитанная на то, что дизайн известен заранее, работать не будет.
I>ТДД это пример как хорошая методика из за плохого названия трактуется как попало.
Нет. Это методика, которая заведомо не работает за пределами крайне узкой области ситуаций, когда кода ещё нет, а дизайн известен на 100% до мельчайших уровней. Как только ставится вопрос о её применении на практике, какие-то её постулаты отменяются и заменяются на более реальные. Так как методов это сделать — бразиллион и маленькая тележка, получается, что те, кто называют свою реальную методику "TDD", имеют в виду свою специфическую адаптацию. Поэтому и трактовка "как попало".
А название у неё как раз 100% адекватное
Здравствуйте, netch80, Вы писали:
N>Здравствуйте, Ikemefula, Вы писали:
I>>ТДД это не написание системы тестов. ТДД оно про проектирование. Знать код не нужно, но вот представлять, как решается задача очень даже нужно. Скажем, нужно хотя бы представлять более-менее детально вычислительную модель.
N>Могу тут только присоединиться к комментариям Sinclair. Как известно, только с третьей версии любая программа становится пригодной к использованию При этом она может пройти принципиальную переделку дизайна, и не одну.
Переделки дизайна это нормально. Главное что нужно в ТДД это представлять направление движения.
I>>ТДД это пример как хорошая методика из за плохого названия трактуется как попало.
N>Нет. Это методика, которая заведомо не работает за пределами крайне узкой области ситуаций, когда кода ещё нет, а дизайн известен на 100% до мельчайших уровней.
На 100% до мельчайших уровней будет известно только перед релизом. Ты точно про ТДД ?
Здравствуйте, Ikemefula, Вы писали:
I>>>ТДД это не написание системы тестов. ТДД оно про проектирование. Знать код не нужно, но вот представлять, как решается задача очень даже нужно. Скажем, нужно хотя бы представлять более-менее детально вычислительную модель. N>>Могу тут только присоединиться к комментариям Sinclair. Как известно, только с третьей версии любая программа становится пригодной к использованию При этом она может пройти принципиальную переделку дизайна, и не одну. I>Переделки дизайна это нормально. Главное что нужно в ТДД это представлять направление движения.
Тогда расскажи подробнее, как по-твоему и по-бековски (не смешивая эти два варианта) реагировать на _изменение_ этого "направления движения".
I>>>ТДД это пример как хорошая методика из за плохого названия трактуется как попало. N>>Нет. Это методика, которая заведомо не работает за пределами крайне узкой области ситуаций, когда кода ещё нет, а дизайн известен на 100% до мельчайших уровней. I>На 100% до мельчайших уровней будет известно только перед релизом. Ты точно про ТДД ?
Да, точно про TDD. Неважно, речь про полный релиз или промежуточный релиз любого субкомпонента, но по строгой TDD дизайн должен быть известен до написания кода.
Здравствуйте, netch80, Вы писали:
I>>Переделки дизайна это нормально. Главное что нужно в ТДД это представлять направление движения.
N>Тогда расскажи подробнее, как по-твоему и по-бековски (не смешивая эти два варианта) реагировать на _изменение_ этого "направления движения".
У Бека именно это не раскрывается. Можно делать как все остальное — писать тесты, менять тесты и добиваться, что бы код их проходил. Но вообще лучше сделать чтото навроде разведки — выяснить, что именно должно поменяться и каким образм и написать просто черновой вариант, мжно даже без компиляния. Далее станет понятно, как именно все меняется и после этого становится понятно, как писать тесты.
I>>>>ТДД это пример как хорошая методика из за плохого названия трактуется как попало. N>>>Нет. Это методика, которая заведомо не работает за пределами крайне узкой области ситуаций, когда кода ещё нет, а дизайн известен на 100% до мельчайших уровней. I>>На 100% до мельчайших уровней будет известно только перед релизом. Ты точно про ТДД ?
N>Да, точно про TDD. Неважно, речь про полный релиз или промежуточный релиз любого субкомпонента, но по строгой TDD дизайн должен быть известен до написания кода.
Нет, ТДД это инструмент создания дизайна. Изначально нужна только вычислительная модель. Скажем, ты вспотеешь рефакторить lr в ll парсер. Более того, обычный recursive descent рефакторить в комбинаторы парсеров тоже не самая простая задача, хотя и возможная.
Здравствуйте, Sinix, Вы писали:
S>На мой взгляд, это шаманство в самом плохом смысле слова. Ну, как гадать на погоду по мокрому пальцу, когда рядом стоит метеостанция.
Если уж подбирать аналогию, то она будет выглядеть так. Видим, что приближается дождь, и накрываем станцию колпаком. Если из отчёта системы получаем, что метеостанция сказала, что идёт дождь — значит, или система врёт, что получила данные в том числе и от этой станции, или сама станция работает не так, как положено.
В варианте Шахтёра делается, считаем, чуть иначе — в станции выдёргивается датчик воды в накопителе. Но результат должен быть тем же — если она не сказала, что дождя нет, что-то происходит не так.
S>Уже есть стабильные, автоматизируемые, повторяемые методики тестирования. Ты сам наверно знаешь, просто перечислю:
S>* Для анализа покрытия тестами есть само покрытие тестами плюс поиск непокрытых вариантов через code flow analysis (см chess/pex). S>* Для случайных ошибок-опечаток — компилятор, статический анализ, code contracts, валидация аргументов и ассерты. S>* Для поиска неочевидных ошибок и нарушений любых инвариантов — всё те же ассерты и инструментирование через AOP (postsharp или инварианты в CodeContracts). Этот пункт великолепно уживается с отладочными сборками, интеграционными и ui-тестами. И, в отличие от юнит-тестов, как раз позволяют тестировать детали реализации. S>* Для софта с экстремальными требованиями по надёжности есть полная статическая верификация и генерация кода по верифицированной матмодели + дифф-тесты по эталону.
S>Что может дать нового методика "ломаем код, запускаем" (aka мета-тестирование) с учётом всего вышеперечисленного?
Она может дать:
1. Обнаружение проблем значительно дешевле, чем описанные методики.
2. Обнаружение проблем в самих применяемых инструментах.
Если ты не понял контекст — уточняю, что речь не идёт о функциональных тестах самого нижнего уровня, известных также как юнит-тесты. Там такое нужно очень редко (хотя имеет смысл, если придуманы какие-то кастомные сложные условия проверки, логика которых не прозрачна с первого взгляда). Описанная методика — для тестов более высокого уровня, которые в некоторых традициях называются интеграционными и проверяют не одну функцию или даже модуль, а целые компоненты.
Кроме того, она (по крайней мере в моём варианте, а не варианте Шахтёра) является прямой проекцией логики BDD на средства тестирования, добавляя не покрытый ранее случай. Например, если у системы на входе A, на выходе должно быть B. А если на входе A+, на выходе должно быть B-. Обычно из такой постановки делают только тесткейс "A => B". Но почему не делают тесткейс "!(A+ => B)"? Ведь это тоже однозначно следует из условий. Но почему-то ограничиваются только положительными условиями. Если ты знаешь, что A+ => B-, то можешь написать для него прямой тест, ok. Но есть ситуации, когда точный ответ заранее в принципе неизвестен (определяется случайностью или слишком сложно вычисляемыми факторами), зато известно, чем он не может быть. Тогда естественная формулировка условия — отрицательная. И вот тут его реализовать поможет инверсия: вложенный тест упал — значит, внешнее условие проверено (особенно, если уточнить, как именно он упал).
S>Ну ок, допустим, тесты упали. Значит ли это что всё хорошо и тесты корректны? Нифига, нет никаких гарантий, что тесты не пропустят любую другую ошибку.
Гарантий нет, как и с любыми другими тестами. Тесты покрывают ничтожную часть данных и ситуаций, говорит кэп. Но по ним тот, кто знает код, может организовать проверку узких мест и граничных условий. Точно так же и с инверсиями.
S>В любом из вариантов мы узнаем только одно — надо разбираться в коде, чтобы определить где косяк: в тестах, в коде или в техзадании. Сорри, но то же самое известно и так, без лишних приседаний.
На практике инверсии мне помогали несколько раз выявлять проблемы, которые прямыми тестами интеграционного уровня не опознавались, а тотальное покрытие по названным тобой методикам было невозможно по недостатку ресурсов. Соответственно "известно" оно не было до срабатывания контроля.
S>О чём и было сказано выше: любая попытка при помощи юнит-тестов "заглянуть глубже" публичного API неизменно приводит к тому, что расходы на тестирование перекрывают выигрыш на порядок.
Я не понял связи описанной методики с юнит-тестами, тем более, "заглядывающими ниже".
S>tzlom S> Едет ли машина у которой не горят фары? S>[/q] S>Так вот, тестирование через поломку кода нифига не поможет найти нерабочие фары.
Оно поможет найти другое: что фары горят не от того, что их явно включили, а от того, что что-то закоротило, или перепутали провода и подключили их к питанию инжектора, или ещё от чего-то аналогичного. На каком-то из сайтов анекдотов была история, как на Ладе загорался дальний, когда выжато сцепление — это как раз из таких ситуаций.
И спроси любого автомеханика, обратит ли он внимание на то, что у машины загорелись фары, когда только включено зажигание. Даже если это машина из Европы с DRL, такого не должно быть, хотя бы пока двигатель не завёлся.
S> Максимум, поймать ДПС-ника, пропустившего машину, сдающую задним ходом с негорящим белым. Ну и нафига оно надо производителю?
А если кто-то отвечает за систему в комплексе, а не только машину?
Здравствуйте, Ikemefula, Вы писали:
I>>>Переделки дизайна это нормально. Главное что нужно в ТДД это представлять направление движения. N>>Тогда расскажи подробнее, как по-твоему и по-бековски (не смешивая эти два варианта) реагировать на _изменение_ этого "направления движения". I>У Бека именно это не раскрывается.
Именно! Потому что его методика в принципе не предусматривает ситуации типа "код уже есть, работает, спецификация поменялась/уточнилась". Как только такое возникает — мы выходим за пределы методики, и надо ставить костыли какого-то вида, но в любом случае костыли.
А всё потому, что он единственным методом проверки теста выбрал то, что кода ещё нет, и тест не проходит. При этом, заметь, ни слова не сказано ни про адекватность кода задаче, ни про адекватность самого теста. Код может состоять из одного switch с проверкой условий, перечисленных в тестах. Тест может не проверять целевое условие вообще. Думаешь, я утрирую? Сравни варианты:
и тут вдруг оказывается, что из-за сверхпутаных установок компиляции почему-то именно в файле, куда перенесли assert(), оказалось #define NDEBUG; а в случае assertEquals() из-за опять же путаных настроек в каком-нибудь setUp() сбился метод сверки, тупо выдавая true на все сравнения на равенство, и проблемы тупо замалчиваются. Ты допишешь код, и у тебя тест вдруг заработал, а с чем ты сравнивал — ему пофиг, оно и 3 и 8 приняло бы в качестве ответа.
И это ещё относительно простой код и простые проверки. А у меня как-то было, что из-за кривого стартапа включилось в цепочку два экземпляра компоненты параллельно(!)
Суммируя — если у тебя после сработавшего теста хоть что-то изменилось (начиная от версии ядра на тестовой машине, продолжая апгрейдом модуля unittest и вплоть до перегруппировки тестов по файлам) — у тебя есть шансы, что тесты не заработают как следует. А обычная проверка срабатывает только на новые false negative, но не на false positive.
I> Можно делать как все остальное — писать тесты, менять тесты и добиваться, что бы код их проходил. Но вообще лучше сделать чтото навроде разведки — выяснить, что именно должно поменяться и каким образм и написать просто черновой вариант, мжно даже без компиляния. Далее станет понятно, как именно все меняется и после этого становится понятно, как писать тесты.
Ну о чём я и говорил в предыдущем. Назовём эту методику "Нортон в помощь". Перемещаем вбок всё и восстанавливаем далее по частям. Только вот я боюсь, что после 5-го повторения на большом проекте тебя закидают помидорами... Инверсии дешевле и надёжнее, и автоматизируются. Прогонять их раз в сутки обычно более чем достаточно.
N>>Да, точно про TDD. Неважно, речь про полный релиз или промежуточный релиз любого субкомпонента, но по строгой TDD дизайн должен быть известен до написания кода. I>Нет, ТДД это инструмент создания дизайна.
По описанному тобой это скорее инструмент типа "сделаю дизайн неизменяемым. дорого."
I> Изначально нужна только вычислительная модель. Скажем, ты вспотеешь рефакторить lr в ll парсер. Более того, обычный recursive descent рефакторить в комбинаторы парсеров тоже не самая простая задача, хотя и возможная.
У меня задачи совсем другого рода. Всякие системы управления и мониторинга. Там дизайн нижнего уровня может меняться радикально, но поведение типа "если сработали датчики Q1 и Q14, не позже чем через 2 секунды должно быть отправлено сообщение F176, и повторяться каждые 1-3 секунд до изменения условия" остаётся неизменным. Вот такие поведения и проверяются.
Здравствуйте, netch80, Вы писали:
N>Суммируя — если у тебя после сработавшего теста хоть что-то изменилось (начиная от версии ядра на тестовой машине, продолжая апгрейдом модуля unittest и вплоть до перегруппировки тестов по файлам) — у тебя есть шансы, что тесты не заработают как следует. А обычная проверка срабатывает только на новые false negative, но не на false positive.
Да, всё так.
N>Ну о чём я и говорил в предыдущем. Назовём эту методику "Нортон в помощь". Перемещаем вбок всё и восстанавливаем далее по частям. Только вот я боюсь, что после 5-го повторения на большом проекте тебя закидают помидорами... Инверсии дешевле и надёжнее, и автоматизируются. Прогонять их раз в сутки обычно более чем достаточно.
Я не сильно в курсе терминов, что значит инверсии ?
N>>>Да, точно про TDD. Неважно, речь про полный релиз или промежуточный релиз любого субкомпонента, но по строгой TDD дизайн должен быть известен до написания кода. I>>Нет, ТДД это инструмент создания дизайна.
N>По описанному тобой это скорее инструмент типа "сделаю дизайн неизменяемым. дорого."
Да, в случае с говнотестами так и получается. Их очень тяжело майнтейнить. В тдд нужны в первую очередь тесты сценариев.
N>У меня задачи совсем другого рода. Всякие системы управления и мониторинга. Там дизайн нижнего уровня может меняться радикально, но поведение типа "если сработали датчики Q1 и Q14, не позже чем через 2 секунды должно быть отправлено сообщение F176, и повторяться каждые 1-3 секунд до изменения условия" остаётся неизменным. Вот такие поведения и проверяются.
То есть, это просто тесты сценариев. Чем же тебе тдд не нравится ?
Здравствуйте, netch80, Вы писали:
N>Если ты не понял контекст — уточняю, что речь не идёт о функциональных тестах самого нижнего уровня, известных также как юнит-тесты. Там такое нужно очень редко (хотя имеет смысл, если придуманы какие-то кастомные сложные условия проверки, логика которых не прозрачна с первого взгляда). Описанная методика — для тестов более высокого уровня, которые в некоторых традициях называются интеграционными и проверяют не одну функцию или даже модуль, а целые компоненты.
Не совсем понятно, что ты понимаешь под функциональными, юнит-тестами и интеграциооными. И что значит самый нижний уровень ? Поясни пожалуйста.
Здравствуйте, Ikemefula, Вы писали:
N>>Ну о чём я и говорил в предыдущем. Назовём эту методику "Нортон в помощь". Перемещаем вбок всё и восстанавливаем далее по частям. Только вот я боюсь, что после 5-го повторения на большом проекте тебя закидают помидорами... Инверсии дешевле и надёжнее, и автоматизируются. Прогонять их раз в сутки обычно более чем достаточно. I>Я не сильно в курсе терминов, что значит инверсии ?
Это изменения, которые принуждают код выдать неподходящий результат (а тест, соответственно, должен сломаться), но чтобы код теста или как минимум его проверочный код не проверял их. (Генерирующий входные данные — может и часто должен знать.)
Конкретная реализация задания этих изменений — по обстановке. Главное — повторюсь, отсутствие знания факта такого изменения в реализующем и проверяющем коде (в моём варианте), в тестирующем коде — полностью (у Шахтёра).
N>>По описанному тобой это скорее инструмент типа "сделаю дизайн неизменяемым. дорого." I>Да, в случае с говнотестами так и получается. Их очень тяжело майнтейнить. В тдд нужны в первую очередь тесты сценариев.
См. ниже.
N>>У меня задачи совсем другого рода. Всякие системы управления и мониторинга. Там дизайн нижнего уровня может меняться радикально, но поведение типа "если сработали датчики Q1 и Q14, не позже чем через 2 секунды должно быть отправлено сообщение F176, и повторяться каждые 1-3 секунд до изменения условия" остаётся неизменным. Вот такие поведения и проверяются. I>То есть, это просто тесты сценариев.
Не только. Главное, что покрытие такими тестами идёт сверху, а не снизу. Но в заметной мере ты прав, их существенная цель — проверить логику каждого уровня, а не реализацию.
I> Чем же тебе тдд не нравится ?
1. Как я уже говорил, обычно TDD соотносится именно с юнит-тестами, то есть (в стандартных терминах) тестах конечных функций и методов. Теме соответствия спецификациям и порождённых из тех сценариев посвящён BDD.
2. Я уже подробно расписывал, чем.
Здравствуйте, Ikemefula, Вы писали:
N>>Если ты не понял контекст — уточняю, что речь не идёт о функциональных тестах самого нижнего уровня, известных также как юнит-тесты. Там такое нужно очень редко (хотя имеет смысл, если придуманы какие-то кастомные сложные условия проверки, логика которых не прозрачна с первого взгляда). Описанная методика — для тестов более высокого уровня, которые в некоторых традициях называются интеграционными и проверяют не одну функцию или даже модуль, а целые компоненты.
I>Не совсем понятно, что ты понимаешь под функциональными, юнит-тестами и интеграциооными. И что значит самый нижний уровень ? Поясни пожалуйста.
Прости, что непонятно в написанном выше?? Я даже квотинг привожу полностью.
Здравствуйте, netch80, Вы писали:
N>>>Если ты не понял контекст — уточняю, что речь не идёт о функциональных тестах самого нижнего уровня, известных также как юнит-тесты. Там такое нужно очень редко (хотя имеет смысл, если придуманы какие-то кастомные сложные условия проверки, логика которых не прозрачна с первого взгляда). Описанная методика — для тестов более высокого уровня, которые в некоторых традициях называются интеграционными и проверяют не одну функцию или даже модуль, а целые компоненты.
I>>Не совсем понятно, что ты понимаешь под функциональными, юнит-тестами и интеграциооными. И что значит самый нижний уровень ? Поясни пожалуйста.
N>Прости, что непонятно в написанном выше?? Я даже квотинг привожу полностью.
Здравствуйте, netch80, Вы писали:
I>> Чем же тебе тдд не нравится ?
N>1. Как я уже говорил, обычно TDD соотносится именно с юнит-тестами, то есть (в стандартных терминах) тестах конечных функций и методов. Теме соответствия спецификациям и порождённых из тех сценариев посвящён BDD.
Сам Кент Бек утверждает, что тесты в ТДД это не юнит-тесты. Вот в этом весь фокус.
Для того, что бы писать тесты, нужно уже более менее знать, какая будет вычислительая модель и какие у ней свойства. Далее, основываясь на этих знаниях, можно писать тесты. Какая будет реализация самого компонента унутре, совершенно неважно.
В твоем случае это будет "если сработали датчики Q1 и Q14, не позже чем через 2 секунды должно быть отправлено сообщение F176, и повторяться каждые 1-3 секунд до изменения условия"
Ш>Суть её в следующем. Мы пишем некоторый софтварный компонент. Ш>Далее мы пишем тесты для него. Прогоняем. Ш>Большинство программистов на этой стадии останавливаются. Но останавливаться не надо.
Добавлю в копилку ко всему обсуждению. Есть такая малоизвестная штука под названием property-based testing. Настолько малоизвестная, что даже в википедии про нее ничего нет, кроме описания самого главного продукта для этого дела, QuickCheck'а [1][2] Эта штука, правда, тербует определенного ломания мозга, но она прикольная.
Общая идея такая:
— у нас есть спецификация того, что должен реализовывать код
— мы максимально декларативно описываем:
-- спецификацию
-- тип входных данных
-- условия, описывающие поведение кода
После этого генерится толпа входных данных и проверяются условия. В случае, если некий набор данных ломает тест, некоторые реализации (типа QuickCheck'а и PropEr'а) умеют сузить этот набор до минимального набора, гарантированно ломающего тест.
Что это дает?
1. Мы тестируем не реализацию, а спецификацию.
Чем это хорошо? Тем, что программисты чаще всего тестируют именно реализацию (которая может сильно отличаться от спецификации, потому что мы все люди-человеки, и часто смотрим в описание задачи, а видим фигу).
2. Мы генерируем множество входных данных
Чем это хорошо? Это позволяет в автоматическом режиме проверить вещи, которые при написании тестов вручную можно забыть (пограничные значения? проверка на ноль и отрицательные значения, когда проверяем функцию, работающую с числами? «магические» комбинации чисел? и т.п.)
Как это выглядит
Примерно так:
для всех X, Y, Z таких, что
X = целые числа,
Y = список различных комбинаций целых чисел
Z = пользовательский тип
проверить следующее условие:
вызов функции F(X, Y, Z) приводит к таким-то и таким-то результатам
Ну или например в Erlang'е (откуда есть пошел Quickcheck)[3]
?FORALL( {X, Y, Z}
, {int(), list(int()), custom_type()}
, F(X, Y, Z) =:= F(-X, Y, Z) %% зависит от того, что ваша функция будет делать
).
QuickCheck сгенерит рандомные[4] целые числа, рандомные списки рандомных чисел и рандомные custom_type'ы и будет проверять эти условия. По умолчанию 100 раз на разных наборах данных, но можно — сколько угодно раз (есть люди, которые оставляют QuickCheck на ночь — генерировать входные данные миллионами).
Если в реализации спецификации будет ошибка, она будет выявлена
А теперь — слайды! примеры!
В Erlang'е есть функция lists:seq, спецификация которой выглядит так:
lists:seq(From, To, Incr)
Генерит список чисел от From до To с шагом Incr. Для любых последовательностей справделиво следующее:
— length(lists:seq(From, To)) == To-From+1
— length(lists:seq(From, To, Incr)) == (To-From+Incr) div Incr
Функция вылетает с ошибкой если
— To < From-Incr, Incr > 0
— To > From-Incr, Incr < 0
— Incr == 0, From /= To.
... в этом месте обычно задается вопрос: какие тесты вы бы написали для проверки вручную ...
Давайте напишем тест:
Для всех From, To, Incr, где
From = int(),
To = int(),
Incr = int()
проверить следующее условие:
попытаться получить список из lists:seq(From, To, Incr)
если список получен, то length(Список) == (To-From+Incr) div Incr
если функция вылетела с ошибкой, то одно из следующих верно:
- To < From-Incr, Incr > 0
- To > From-Incr, Incr < 0
- Incr == 0, From /= To.
На Erlang'е:
prop_seq4() ->
?FORALL( {From, To, Incr}
, {int(), int(), int()}
, try
List = lists:seq(From, To, Incr),
length(List) == (To - From + Incr) div Incr
catch
error:_ ->
((To < From - Incr) andalso Incr > 0) orelse
((To > From - Incr) andalso Incr < 0) orelse
(From /= To andalso Incr == 0)
end
).
Давайте проверим:
> eqc:quickcheck(lists_eqc:prop_seq4()).
Failed! After 1 tests.
{0,0,0}
Опаньки. Если From == 0, To == 0, Inc == 0, то тест вылетает к чертям. То есть код у нас неправильно реализует спецификацию. Ладно, можно добавить одно условие в тест
На мелком примере это видно плохо, но property-based testing позволяет (при должном подходе) избегать необходимости вносить ошибки в код для проверки тестов. При условии, конечно, что спецификации заданы правильно и тесты проверяют то, что надо, а не что бог на душу положит
В Кларне при помощи QuickCheck'а нашли плохо воспризводимый баг в Мнезии (терялись данные при какой-то последовательности операций). QC нашел последовательность в 14 шагов, потом сузил ее до трех. Можно почитать про подобное и в LevelDB. Можно еще почитать Эрикссоновский отчет про тестирование протокола Megaco.
В общем, весьма рекомендую и советую к использованию.
[1] Quickcheck сильно коммерческий, но существует его упрощенная бесплатная версия, QuickCheck Mini
[2] Греческий парень по имени Kostis Sagonas, увидев Quickcheck, решил его реверс-инжинирнуть и создать бесплатную версию с таким же интерфейсом. Называется PropEr
[3] Да-да, он сначала был реализован на Хаскеле, но потом его переписали на Erlang'е, и уходить с Erlang'а авторы, вроде, не собираются
[4] Генерация не прямо рандомная, но выборка идет ближе к «левому» краю распределения данных. Поэтому иногда надо запускать много тестов (тысячу-две), чтобы покрыть все данные. Ну или использовать helper functions для изменения распределения данных.
Здравствуйте, Ikemefula, Вы писали:
N>>Прости, что непонятно в написанном выше?? Я даже квотинг привожу полностью. I>По моему функциональные тесты != юнит-тесты.
Функциональные тесты — это те, что проверяют собственно функциональность. То есть что при таких входных параметрах/условиях получаются такие выходные. Кроме них могут быть тесты производительности, защиты, побочных эффектов, и так далее.
Юнит-тесты это важный частный случай функциональных тестов — для отдельно взятой функции, метода, наконец, класса.