Подтвердить что ты не робот

Как TDD работает, когда могут быть миллионы тестовых примеров для производственной функциональности?

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

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

Позвольте мне объяснить, что я имею в виду более ясно, на примере ниже:

 internal static List<long> GetPrimeFactors(ulong number)
        {
            var result = new List<ulong>();

            while (number % 2 == 0)
            {
                result.Add(2);
                number = number / 2;
            }

            var divisor = 3;

            while (divisor <= number)
            {
                if (number % divisor == 0)
                {
                    result.Add(divisor);
                    number = number / divisor;
                }
                else
                {
                    divisor += 2;
                }
            }

            return result;
        }

Вышеприведенный код возвращает все простые коэффициенты заданного числа. ulong имеет 64 бита, что означает, что он может принимать значения от 0 до 18 446 744 073 709 551 615!

Итак, как TDD работает, когда могут быть миллионы тестовых примеров для производственной функциональности?!

Я имею в виду, сколько тестовых примеров достаточно для написания, чтобы я мог сказать, что я использовал TDD для достижения этого производственного кода?

Эта концепция в TDD, в которой говорится, что вы должны написать достаточно кода для прохождения теста, кажется мне неправильным, как видно из приведенного выше примера?

Когда достаточно достаточно?

Мои собственные мысли состоят в том, что я выбираю только некоторые тестовые примеры, например. для верхней полосы, нижней полосы и еще нескольких, например. 5 тестов, но это не TDD, не так ли?

Большое спасибо за ваши мысли о TDD для этого примера.

4b9b3361

Ответ 1

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

Классическим примером в истории наук является вопрос "все ли лебеди белые?". Независимо от того, сколько разных белых лебедей вы найдете, вы не можете сказать, что гипотеза "все лебеди белые" верна. С другой стороны, принесите мне одного черного лебедя, и я знаю, что гипотеза неверна.

Хороший TDD unit test находится вдоль этих линий; если он пройдет, он не скажет вам, что все правильно, но если он терпит неудачу, он сообщает вам, где ваша гипотеза неверна. В этом фрейме тестирование для каждого номера не так ценно: одного случая должно быть достаточно, потому что, если он не работает для этого случая, вы знаете, что что-то не так.

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

Ответ 2

Вы забываете третий шаг:

  • Красный
  • Green
  • Refactor

Написание тестовых примеров позволяет вам краснеть.

Написание достаточного кода для передачи этих тестовых файлов приведет вас к зеленому.

Обобщая ваш код для работы больше, чем только те сценарии, которые вы написали, но все равно не нарушая их, является рефакторинг.

Ответ 3

Кажется, вы рассматриваете TDD, как будто это тестирование черного ящика. Не это. Если бы это было тестирование с помощью "черного ящика", только полные (миллионы тестовых случаев) множество тестов удовлетворяли бы вас, потому что любой данный случай может быть непроверенным, и поэтому демоны в черном ящике смогут уйти с читом.

Но это не демоны в черном ящике вашего кода. Это ты, в белом ящике. Вы знаете, обманываете вы или нет. Практика Fake It Til You Make It тесно связана с TDD и иногда путается с ней. Да, вы пишете поддельные реализации, чтобы удовлетворить ранние тестовые примеры, но вы знаете, что вы это притворяетесь. И вы также знаете, когда перестали притворяться. Вы знаете, когда у вас есть реальная реализация, и вы получили там прогрессивную итерацию и пробное вождение.

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

Ответ 4

Из моего POV шаг рефакторинга, похоже, не состоялся на этом фрагменте кода...

В моей книге TDD НЕ означает писать тестовые окна для каждой возможной перестановки всех возможных параметров ввода/вывода...

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

TDD работает только в реальном мире, если вы не бросаете здравый смысл из окна...

Что касается

Достаточно написать достаточно кода для прохождения теста

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

BTW "Конструкция тестовой системы" - это то, чем вы становитесь лучше, чем больше вы ее практиковали - нет книги/руководства, которые могут рассказать вам, какие тестовые тесты лучше всего подходят для любой конкретной ситуации... опыт окупается, когда дело доходит до построение тестовых ящиков...

Ответ 5

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

Вы можете написать один тестовый пример, который вызывает функцию под тестированием более одного раза, передавая разные аргументы. Это предотвращает "писать код для факторизации 1", "писать код для факторизации 2", "писать код для факторизации 3" - это отдельные задачи разработки.

Сколько различных значений для проверки действительно зависит от того, сколько времени вам нужно для запуска тестов. Вы хотите протестировать все, что может быть угловым (например, в случае факторизации не менее 0, 1, 2, 3, LONG_MAX+1, поскольку оно имеет наибольшее количество факторов, какое значение имеет самые различные факторы, число Кармайкл, и несколько совершенных квадратов с различным количеством простых факторов) плюс большой диапазон ценностей, как вы можете в надежде покрыть то, что вы не понимали, было угловым, но есть. Это может означать запись теста, а затем запись функции, а затем корректировку размера диапазона в зависимости от его наблюдаемой производительности.

Вам также разрешено читать спецификацию функции и реализовывать функцию так, как если бы было проверено больше значений, чем будет на самом деле. Это на самом деле не противоречит "только реализовать то, что проверено", он просто признает, что до даты отправки недостаточно времени для запуска всех 2 ^ 64 возможных входов, и поэтому фактический тест является репрезентативной выборкой "логического" теста что вы побежали бы, если бы успели. Вы все еще можете запрограммировать то, что хотите протестировать, а не то, что на самом деле у вас есть время для тестирования.

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

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

Вы также можете позволить читтеру тестера, выходя за пределы TDD. Сначала напишите тест, затем напишите код, чтобы пройти тест, затем вернитесь назад и напишите больше тестов с белым ящиком, чтобы (a) включать значения, которые выглядят так, как будто они могут быть крайними случаями в фактически написанной реализации; и (b) включить достаточное количество значений, чтобы получить 100% -ный охват кода, для любого показателя покрытия кода у вас есть время и сила воли для работы. Часть TDD процесса по-прежнему полезна, она помогает писать код, но затем вы повторяете. Если какой-либо из этих новых тестов завершится неудачно, вы можете назвать это "добавлением новых требований", и в этом случае я полагаю, что вы делаете это все еще чисто TDD. Но это исключительно вопрос того, как вы это называете, на самом деле вы не добавляете новые требования, вы тестируете оригинальные требования более тщательно, чем это было до написания кода.

Ответ 6

Когда вы пишете тест, вы должны принимать значимые случаи, а не каждый случай. Значимые случаи включают общие случаи, угловые случаи...

Вы просто НЕ МОЖЕТЕ писать тест для каждого отдельного случая (иначе вы могли бы просто поместить значения в таблицу и ответить на них, чтобы вы были на 100% уверены, что ваша программа будет работать: P).

Надеюсь, что это поможет.

Ответ 7

Такой первый вопрос, который у вас есть для любого тестирования. TDD здесь не имеет значения.

Да, есть много и много дел; кроме того, есть комбинации и комбинации случаев, если вы начинаете строить систему. Это действительно приведет к комбинаторному взрыву.

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

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

Далее... Ну, по всем практическим причинам, это нормально, чтобы остановиться здесь. Тем не менее, взгляните на эти примечания к лекции: http://www.scs.stanford.edu/11au-cs240h/notes/testing.html

PS. Кстати, использование TDD "по книге" для математических задач - не очень хорошая идея. Кент Бек в своей книге TDD доказывает, что, реализуя наихудшую возможную реализацию функции, вычисляющей числа Фибоначчи. Если вы знаете закрытую форму или имеете статью, описывающую проверенный алгоритм, просто сделайте проверки на работоспособность, как описано выше, и не делайте TDD со всем циклом рефакторинга - это сэкономит ваше время.

ПФС. На самом деле, хорошая статья, которая (удивление!) Упоминает бот о проблеме Фибоначчи и о проблеме с TDD.

Ответ 8

Я никогда не делал TDD, но то, о чем вы спрашиваете, не связано с TDD: речь идет о том, как написать хороший тестовый пакет.

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

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

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

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

После этого я устал в течение дня и ухожу домой:) И у меня, вероятно, около 10-20 тестовых случаев на хорошо продуманный и разумно короткий метод или 50-100 на алгоритм/класс. Не 10 000 000.

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

Ключи здесь:

  • Моделируйте свои алгоритмы/объекты/код, по крайней мере, в вашей голове. Ваш код больше дерева, чем script
  • Полностью определить все переходы состояния внутри этой модели (каждая операция, которая может выполняться независимо, и каждая часть каждого выражения, которое оценивается)
  • Используйте граничное тестирование, поэтому вам не нужно придумывать бесконечные варианты.
  • Макет, когда вы можете

И нет, вам не нужно писать рисунки FSM, если вам не нравится делать что-то подобное. Я не:)

Ответ 9

Существует не миллионы тестовых случаев. Только несколько. Вы можете попробовать PEX, который позволит вам узнать различные тестовые примеры реальных в вашем алгоритме. Конечно, вам нужно только протестировать их.

Ответ 10

Что вы обычно делаете, оно проверяет "тестовые граничные условия" и несколько случайных условий.

например: ulong.min, ulong.max и некоторые значения. Почему вы даже делаете GetPrimeFactors? Вам нравится рассчитать их вообще, или вы делаете это, чтобы делать что-то конкретное? Проверьте, почему вы это делаете.

Что вы также можете сделать это Assert for result.Count, а не все отдельные элементы. Если вы знаете, сколько предметов вы хотите получить, а также некоторые конкретные случаи, вы все равно можете реорганизовать свой код, и если эти случаи и общий счет совпадают, предположите, что функция все еще работает.

Если вы действительно хотите это проверить, вы также можете посмотреть на тестирование белого ящика. Например Pex и Moles довольно хорошо.

Ответ 11

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

В основном это означает, что я пишу тесты в двух сценариях: 1) некоторый код, который я написал, сложный или сложный и/или имеет слишком много предположений и 2) ошибка возникает в процессе производства.

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