Я работаю над компаратором списков, чтобы помочь сортировке неупорядоченного списка результатов поиска по очень специфическим требованиям нашего клиента. Требования требуют ранжированного алгоритма релевантности со следующими правилами в порядке важности:
- Точное совпадение по имени
- Все слова поискового запроса по имени или синониму результата
- Несколько слов поискового запроса по названию или синониму результата (% по убыванию)
- Все слова поискового запроса в описании
- Несколько слов поискового запроса в описании (% по убыванию)
- Дата последнего изменения по убыванию
Естественный выбор дизайна для этого компаратора, казалось, был оцененным рейтингом, основанным на степенях 2. Сумма менее важных правил никогда не может быть больше, чем положительное совпадение с правилом более высокой важности. Это достигается с помощью следующей оценки:
- 32
- 16
- 8 (Вторичный тай-брейк, основанный на% по убыванию)
- 4
- 2 (Вторичный тай-брейк, основанный на% по убыванию)
- 1
В духе TDD я решил начать с моих юнит-тестов. Чтобы иметь контрольный пример для каждого уникального сценария, было бы как минимум 63 уникальных тестовых случая, не рассматривая дополнительные тестовые примеры для логики вторичного прерывателя связей по правилам 3 и 5. Это кажется властным.
Фактические тесты будут на самом деле меньше, хотя. Основываясь на самих реальных правилах, определенные правила гарантируют, что нижние правила всегда будут истинными (например, когда «все слова поискового запроса появляются в описании», тогда правило «некоторые слова поискового запроса появляются в описании» всегда будет истинным). Тем не менее, стоит ли усилий при написании каждого из этих тестов? Это тот уровень тестирования, который обычно требуется, когда речь идет о 100% тестовом покрытии в TDD? Если нет, то какова будет приемлемая альтернативная стратегия тестирования?
источник
Ответы:
Ваш вопрос подразумевает, что TDD имеет какое-то отношение к «написанию всех тестовых случаев в первую очередь». ИМХО, это не "в духе TDD", на самом деле это против . Помните , что TDD означает «тест ведомого развитие», так что вам нужна только те тестовые случаи , которые на самом деле «диск» ваша реализация, не больше. И до тех пор, пока ваша реализация не разработана таким образом, чтобы число блоков кода росло экспоненциально с каждым новым требованием, вам также не понадобится экспоненциальное количество тестовых случаев. В вашем примере цикл TDD, вероятно, будет выглядеть так:
Затем начните со 2-го требования:
Здесь есть одна загвоздка : когда вы добавляете тестовые случаи для номера требования / категории «n», вам нужно будет только добавить тесты, чтобы убедиться, что оценка категории «n-1» выше, чем оценка для категории «n» , Вам не нужно будет добавлять какие-либо тестовые примеры для каждой другой комбинации категорий 1, ..., n-1, так как тесты, которые вы написали ранее, убедятся, что результаты этих категорий будут по-прежнему в правильном порядке.
Таким образом, это даст вам ряд тестовых случаев, которые растут примерно линейно с количеством требований, а не экспоненциально.
источник
Подумайте о написании класса, который просматривает заранее определенный список условий и умножает текущий счет на 2 для каждой успешной проверки.
Это можно проверить очень легко, используя всего лишь пару проверочных тестов.
Затем вы можете написать класс для каждого условия и есть только 2 теста для каждого случая.
Я не совсем понимаю ваш вариант использования, но, надеюсь, этот пример поможет.
Вы заметите, что ваши 2 ^ условия испытаний быстро сводятся к 4+ (2 * условия). 20 гораздо менее властен, чем 64. И если вы добавите еще один позже, вам не нужно менять ЛЮБОЙ из существующих классов (принцип открытого-закрытого), поэтому вам не нужно писать 64 новых теста, вы просто добавить еще один класс с двумя новыми тестами и добавить его в свой класс ScoreBuilder.
источник
Вам нужно будет определить «стоит». Проблема такого рода сценариев заключается в том, что тесты будут приносить все меньшую отдачу от полезности. Конечно, первый тест, который вы напишите, будет полностью оправдан. Он может найти очевидные ошибки в приоритете и даже такие вещи, как синтаксический анализ ошибок при попытке разбить слова.
Второй тест будет того стоить, потому что он охватывает другой путь в коде, вероятно, проверяя другое отношение приоритетов.
63-й тест, вероятно, не стоит того, потому что вы уверены, что на 99,99% он покрыт логикой вашего кода или другого теста.
Насколько я понимаю, покрытие на 100% означает, что все пути кода выполнены. Это не означает, что вы выполняете все комбинации своих правил, но все пути, по которым ваш код может идти вниз (как вы отмечаете, некоторые комбинации не могут существовать в коде). Но так как вы делаете TDD, пока нет «кода» для проверки путей. Буква процесса сказала бы сделать все 63+.
Лично я считаю 100% покрытие несбыточной мечтой. Кроме того, это непрагматично. Модульные тесты существуют, чтобы служить вам, а не наоборот. По мере того, как вы проводите больше тестов, вы получаете все меньшую отдачу от выгоды (вероятность того, что тест предотвратит ошибку + уверенность в правильности кода). В зависимости от того, что делает ваш код, определяет, где на этой скользящей шкале вы прекращаете делать тесты. Если ваш код работает на ядерном реакторе, то, возможно, все 63+ тестов того стоят. Если ваш код организует ваш музыкальный архив, то вы, вероятно, можете сэкономить гораздо меньше.
источник
Я бы сказал, что это идеальный случай для TDD.
У вас есть известный набор критериев для тестирования с логической разбивкой по этим случаям. Предполагая, что вы собираетесь провести их модульное тестирование либо сейчас, либо позже, кажется, имеет смысл взять известный результат и опираться на него, гарантируя, что вы на самом деле выполняете каждое из правил независимо.
Кроме того, по ходу дела вы узнаете, нарушает ли добавление нового правила поиска существующее правило. Если вы делаете все это в конце кода, вы, вероятно, рискуете изменить один, чтобы исправить один, который сломает другой, сломает другой ... И вы узнаете, как вы реализуете правила, действителен ли ваш дизайн или нуждается в настройке.
источник
Я не фанат строгой интерпретации 100% покрытия тестами как написания спецификаций для каждого отдельного метода или тестирования каждой перестановки кода. Это фанатично ведет к созданию управляемых тестами проектов ваших классов, которые неправильно инкапсулируют бизнес-логику и дают тесты / спецификации, которые, как правило, не имеют смысла с точки зрения описания поддерживаемой бизнес-логики. Вместо этого я сосредотачиваюсь на структурировании тестов во многом так же, как сами бизнес-правила, и стараюсь использовать каждую условную ветвь кода с тестами с явным ожиданием, что тестеры будут легко поняты тестировщиком как обычные сценарии использования и фактически описывают бизнес-правила, которые были реализованы.
Помня об этой идее, я бы тщательно проверил 6 факторов ранжирования, которые вы перечислили в отрыве друг от друга, а затем 2 или 3 теста стиля интеграции, которые гарантируют, что вы свернете свои результаты до ожидаемых общих значений ранжирования. Например, в случае № 1 «Точное совпадение по имени» у меня было бы по крайней мере два модульных теста, чтобы проверить, когда он точен, а когда нет, и что два сценария возвращают ожидаемый результат. Если он чувствителен к регистру, то также случай проверки «Точного соответствия» и «Точного соответствия» и, возможно, других вариантов ввода, таких как знаки препинания, лишние пробелы и т. Д., Также возвращает ожидаемые оценки.
После того, как я проработал все отдельные факторы, влияющие на рейтинговые оценки, я, по сути, предполагаю, что они правильно функционируют на уровне интеграции, и сосредоточился на том, чтобы их объединенные факторы правильно вносили свой вклад в окончательную ожидаемую рейтинговую оценку.
Предполагая, что случаи # 2 / # 3 и # 4 / # 5 обобщены для одних и тех же базовых методов, но, передавая различные поля, вам нужно написать только один набор модульных тестов для базовых методов и написать простые дополнительные модульные тесты для проверки конкретного поля (название, имя, описание и т. д.) и оценки при назначенном факторинге, так что это дополнительно снижает избыточность ваших общих усилий по тестированию.
При таком подходе описанный выше подход, вероятно, даст 3 или 4 модульных теста для случая # 1, возможно, будет учтено 10 спецификаций для некоторых / всех w / синонимов - плюс 4 спецификации для правильной оценки случаев # 2 - # 5 и 2 до 3-х спецификаций на конечную дату упорядоченного ранжирования, затем от 3 до 4 тестов уровня интеграции, которые измеряют все 6 случаев, объединенные вероятным образом (пока забудьте о непонятных крайних случаях, если вы четко не видите проблему в своем коде, которую необходимо выполнить, чтобы гарантировать это условие выполняется) или убедитесь, что не будет нарушено / нарушено в результате последующих изменений. Это дает около 25 или около того спецификаций для выполнения 100% написанного кода (даже если вы напрямую не вызывали 100% написанных методов).
источник
Я никогда не был фанатом 100% тестового покрытия. По моему опыту, если что-то достаточно просто для тестирования только с одним или двумя контрольными случаями, то это достаточно просто, чтобы редко проваливаться. Когда это терпит неудачу, это обычно из-за архитектурных изменений, которые в любом случае потребовали бы тестовых изменений.
При этом для таких требований, как ваши, я всегда тщательно тестирую модули, даже в личных проектах, где меня никто не заставляет, потому что это те случаи, когда модульное тестирование экономит ваше время и усугубляет ситуацию. Чем больше модульных тестов требуется для проверки чего-либо, тем больше времени вы сэкономите.
Это потому, что вы можете одновременно держать в голове столько всего. Если вы пытаетесь написать код, который работает для 63 различных комбинаций, зачастую сложно исправить одну комбинацию, не нарушая другую. В конечном итоге вы вручную тестируете другие комбинации снова и снова. Ручное тестирование намного медленнее, поэтому вам не нужно перезапускать каждую возможную комбинацию каждый раз, когда вы вносите изменения. Это повышает вероятность того, что вы что-то пропустите, и, скорее всего, будет тратить время на поиск путей, которые не работают во всех случаях.
Помимо сэкономленного времени по сравнению с ручным тестированием, умственное напряжение намного меньше, что позволяет сосредоточиться на проблеме, не беспокоясь о случайном введении регрессий. Это позволяет вам работать быстрее и дольше без выгорания. На мой взгляд, одни только преимущества для психического здоровья стоят затрат на модульное тестирование сложного кода, даже если это не сэкономит вам время.
источник