Юнит-тестирование по своей природе случайных / недетерминированных алгоритмов


41

Мой текущий проект, кратко, включает создание «случайно-случайных событий». Я в основном генерирую график проверок. Некоторые из них основаны на строгих расписаниях; Вы проводите проверку один раз в неделю в пятницу в 10:00. Другие проверки являются «случайными»; Существуют основные настраиваемые требования, такие как «проверка должна проводиться 3 раза в неделю», «проверка должна проводиться в период с 9 до 9 часов вечера» и «не должно быть двух проверок в течение одного 8-часового периода», но в рамках каких-либо ограничений, настроенных для определенного набора проверок, итоговые даты и время не должны быть предсказуемыми.

Модульные тесты и TDD, IMO, имеют большое значение в этой системе, так как их можно использовать для поэтапного построения ее, в то время как ее полный набор требований все еще не завершен, и убедитесь, что я не «чрезмерно разрабатываю» ее, чтобы делать то, что я делаю В настоящее время не знаю, что мне нужно. Строгие графики были сдобными для TDD. Однако мне трудно определить, что именно я тестирую, когда пишу тесты для произвольной части системы. Я могу утверждать, что все времена, создаваемые планировщиком, должны попадать в ограничения, но я мог бы реализовать алгоритм, который проходит все такие тесты без фактического времени, являющегося очень «случайным». На самом деле это именно то, что произошло; Я обнаружил проблему, когда время, хотя и не было предсказуемым, попало в небольшое подмножество допустимых диапазонов даты / времени. Алгоритм по-прежнему проходил все утверждения, которые я чувствовал, что я мог разумно сделать, и я не мог спроектировать автоматизированный тест, который потерпел бы неудачу в этой ситуации, но прошел, когда дали «более случайные» результаты. Я должен был продемонстрировать, что проблема была решена путем реструктуризации некоторых существующих тестов, чтобы они повторялись несколько раз, и визуально проверял, чтобы сгенерированное время находилось в полном допустимом диапазоне.

Есть ли у кого-нибудь советы по разработке тестов, которые должны ожидать недетерминированного поведения?


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

Я создал набор тестов «песочницы», которые содержат подходящие алгоритмы для процесса ограничения (процесс, при котором байтовый массив, который может быть любым длинным, становится длинным между минимальным и максимальным). Затем я запускаю этот код через цикл FOR, который дает алгоритму несколько известных байтовых массивов (значения от 1 до 10 000 000 только для начала), а алгоритм ограничивает каждое значение значением от 1009 до 7919 (я использую простые числа для обеспечения Алгоритм не будет проходить мимо некоторого случайного GCF между входным и выходным диапазоном). Результирующие ограниченные значения подсчитываются и создается гистограмма. Чтобы «пройти», все входные данные должны быть отражены в гистограмме (здравый смысл, чтобы гарантировать, что мы не «потеряли»), и разница между любыми двумя сегментами в гистограмме не может быть больше 2 (она действительно должна быть <= 1 , но следите за обновлениями). Алгоритм выигрыша, если таковой имеется, может быть вырезан и вставлен непосредственно в производственный код, а для регрессии введен постоянный тест.

Вот код:

    private void TestConstraintAlgorithm(int min, int max, Func<byte[], long, long, long> constraintAlgorithm)
    {
        var histogram = new int[max-min+1];
        for (int i = 1; i <= 10000000; i++)
        {
            //This is the stand-in for the PRNG; produces a known byte array
            var buffer = BitConverter.GetBytes((long)i);

            long result = constraintAlgorithm(buffer, min, max);

            histogram[result - min]++;
        }

        var minCount = -1;
        var maxCount = -1;
        var total = 0;
        for (int i = 0; i < histogram.Length; i++)
        {
            Console.WriteLine("{0}: {1}".FormatWith(i + min, histogram[i]));
            if (minCount == -1 || minCount > histogram[i])
                minCount = histogram[i];
            if (maxCount == -1 || maxCount < histogram[i])
                maxCount = histogram[i];
            total += histogram[i];
        }

        Assert.AreEqual(10000000, total);
        Assert.LessOrEqual(maxCount - minCount, 2);
    }

    [Test, Explicit("sandbox, does not test production code")]
    public void TestRandomizerDistributionMSBRejection()
    {
        TestConstraintAlgorithm(1009, 7919, ConstrainByMSBRejection);
    }

    private long ConstrainByMSBRejection(byte[] buffer, long min, long max)
    {
        //Strip the sign bit (if any) off the most significant byte, before converting to long
        buffer[buffer.Length-1] &= 0x7f;
        var orig = BitConverter.ToInt64(buffer, 0);
        var result = orig;
        //Apply a bitmask to the value, removing the MSB on each loop until it falls in the range.
        var mask = long.MaxValue;
        while (result > max - min)
        {
            mask >>= 1;
            result &= mask;
        }
        result += min;

        return result;
    }

    [Test, Explicit("sandbox, does not test production code")]
    public void TestRandomizerDistributionLSBRejection()
    {
        TestConstraintAlgorithm(1009, 7919, ConstrainByLSBRejection);
    }

    private long ConstrainByLSBRejection(byte[] buffer, long min, long max)
    {
        //Strip the sign bit (if any) off the most significant byte, before converting to long
        buffer[buffer.Length - 1] &= 0x7f;
        var orig = BitConverter.ToInt64(buffer, 0);
        var result = orig;

        //Bit-shift the number 1 place to the right until it falls within the range
        while (result > max - min)
            result >>= 1;

        result += min;
        return result;
    }

    [Test, Explicit("sandbox, does not test production code")]
    public void TestRandomizerDistributionModulus()
    {
        TestConstraintAlgorithm(1009, 7919, ConstrainByModulo);
    }

    private long ConstrainByModulo(byte[] buffer, long min, long max)
    {
        buffer[buffer.Length - 1] &= 0x7f;
        var result = BitConverter.ToInt64(buffer, 0);

        //Modulo divide the value by the range to produce a value that falls within it.
        result %= max - min + 1;

        result += min;
        return result;
    }

... и вот результаты:

введите описание изображения здесь

Отклонение LSB (сдвиг битов числа до тех пор, пока оно не попадает в диапазон) было УЖАСНЫМ по очень простой для объяснения причине; когда вы делите любое число на 2 до тех пор, пока оно не станет меньше максимума, вы выйдете из системы, как только оно произойдет, и для любого нетривиального диапазона это сместит результаты в верхнюю треть (как было видно из подробных результатов гистограммы). ). Это было именно то поведение, которое я видел из законченных дат; все время было днем, в очень конкретные дни.

Отклонение MSB (удаление наиболее значимого бита из номера один за раз, пока он не окажется в пределах диапазона) лучше, но опять же, поскольку вы отбираете очень большие числа с каждым битом, он распределяется неравномерно; маловероятно, что вы получите числа в верхнем и нижнем концах, поэтому вы получаете уклон в сторону средней трети. Это может принести пользу кому-то, кто хочет «нормализовать» случайные данные в кривую округлости, но сумма двух или более случайных чисел меньшего размера (аналогично бросанию игральных костей) даст вам более естественную кривую. Для моих целей это не удается.

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


2
Итак, в конечном счете, вам нужна программа, которая просматривает выходные данные генератора случайных чисел и решает, является ли он случайным? Как в «5,4,10,31,120,390,2,3,4» было случайно, а «49,39,1,10,103,12,4,189» не было?
PSR

Нет, но было бы неплохо избежать смещения между фактическим PRNG и конечным результатом.
KeithS

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

Вам также следует проверить комбинации. Примерно равные ожидаемые проверки в час не защитят от случая, когда, скажем, во вторник в 11 часов утра всегда следуют 2 часа дня в четверг и 10 часов утра в пятницу.
Дэвид Торнли

Это больше испытание самого PRNG; проверка механизма (ов) ограничения, как описано выше, всегда будет проваливать такой тест, потому что ему дается совершенно неслучайный набор данных. Предполагая, что механизм ограничения не пытается «упорядочить» случайные данные, я бы назвал это «внешним тестированием», которое не должно выполняться модульным тестом.
KeithS

Ответы:


17

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

Если это то, что вы ищете, то смоделируйте рандомизатор, чтобы сделать его детерминированным в рамках теста.

У меня обычно есть фиктивные объекты для всех видов недетерминированных или непредсказуемых (на момент написания теста) данных, включая генераторы GUID и DateTime.Now.

Редактировать, из комментариев: Вы должны высмеивать PRNG (этот термин ускользнул от меня прошлой ночью) на самом низком возможном уровне - т.е. когда он генерирует массив байтов, а не после того, как вы превратите их в Int64. Или даже на обоих уровнях, так что вы можете проверить, что ваше преобразование в массив Int64 работает как задумано, а затем отдельно проверить, что ваше преобразование в массив DateTimes работает как задумано. Как сказал Джонатон, вы можете просто сделать это, задав ему начальное число, или вы можете дать ему массив байтов для возврата.

Я предпочитаю последнее, потому что оно не сломается, если изменится реализация PRNG. Тем не менее, одно преимущество, которое дает начальное значение, состоит в том, что если вы обнаружите, что дело в производстве не работает должным образом, вам нужно зарегистрировать только одно число, чтобы иметь возможность его реплицировать, а не весь массив.

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


1
Нет. То, что я хочу проверить в этом случае, это сам рандомизатор, и утверждаю, что «случайные» значения, генерируемые рандомизатором, попадают в указанные ограничения, оставаясь при этом «случайными», то есть не смещаясь в сторону неравномерного распределения по допустимым временные диапазоны. Я могу и действительно детерминистически проверять, что конкретная дата / время правильно проходит или не соответствует определенному ограничению, но реальная проблема, с которой я столкнулся, заключалась в том, что даты, произведенные рандомизатором, были смещены и, следовательно, предсказуемы.
KeithS

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

1
Это испытание, которое иногда и непредсказуемо сломается. Вы этого не хотите. Я думаю, что вы не поняли мою точку зрения, если честно. Где-то внутри того, что вы называете рандомизатором, должна быть строка кода, которая генерирует случайное число, не так ли? Эту строку я называю рандомизатором, а остальное, что вы называете рандомизатором (распределение дат на основе «случайных» данных), - это то, что вы хотите проверить. Или я что-то упустил?
фунтовые

надлежащим образом статистические метрики случайных последовательностей (корреляция, блочная корреляция, среднее значение, стандартные отклонения и т. д.) не будут соответствовать вашему диапазону ожиданий, только если вы сделаете действительно маленькие выборки. Увеличьте наборы выборок и / или увеличьте допустимые
уровни

1
«некоторый уклон даже на этом уровне». Если вы используете хороший PRNG, то вы не сможете найти ни одного теста (с реалистичными вычислительными границами), который мог бы отличить его от реальной случайности. Таким образом, на практике можно предположить, что хороший PRNG не имеет смещения вообще.
CodesInChaos

23

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

Отсоедините ваш код от PRNG - передайте начальное значение рандомизации во весь код, который использует рандомизацию. Затем вы можете определить «рабочие» значения из одного семени (или нескольких семен, которые помогут вам чувствовать себя лучше). Это даст вам возможность адекватно протестировать свой код без необходимости полагаться на закон больших чисел.

Это звучит глупо, но так поступают военные (или они используют «случайную таблицу», которая на самом деле не случайна вообще)


Точно: если вы не можете протестировать элемент алгоритма, абстрагируйте его и высмеивайте
Стив Грейтрекс,

Я не указал детерминированное начальное значение; вместо этого я полностью удалил «случайный» элемент, поэтому мне даже не пришлось полагаться на конкретный алгоритм PRNG. Я смог проверить, что, учитывая большой диапазон равномерно распределенных чисел, алгоритм, с которым я работал, может ограничить их меньшим диапазоном без внесения смещения. Сам PRNG должен быть надлежащим образом проверен тем, кто его разработал (я использую RNGCryptoServiceProvider).
KeithS

Что касается подхода «случайных таблиц», вы также можете использовать тестовую реализацию, которая содержит «обратимый» алгоритм генерации чисел. Это позволяет вам «перематывать» PRNG или даже запрашивать его, чтобы увидеть, какими были последние N выходных данных. Это позволило бы гораздо больше углубленной отладки в определенных сценариях.
Darien

Это не так глупо - это тот же метод, который Google использует для воспроизведения ошибок ввода в тестах Шпаннера согласно их статье :)
Акшат Махаджан

6

«Это случайно (достаточно)» оказывается невероятно тонким вопросом. Короткий ответ заключается в том, что традиционный модульный тест просто не урежет его - вам нужно сгенерировать кучу случайных значений и отправить их в различные статистические тесты, которые дают вам высокую уверенность в том, что они достаточно случайны для ваших нужд.

Будет шаблон - мы используем генераторы псевдослучайных чисел в конце концов. Но в какой-то момент все будет «достаточно хорошо» для вашего приложения (где достаточно хорошее варьирует много между, скажем, играми на одном конце, где достаточно относительно простых генераторов, вплоть до криптографии, где вам действительно нужны последовательности, которые невозможно определить решительным и хорошо экипированным атакующим).

В статье Википедии http://en.wikipedia.org/wiki/Randomness_tests и ее последующих ссылках содержится больше информации.


Даже посредственные PRNG не будут показывать никаких изменений в статистических тестах. Для хороших PRNG практически невозможно отличить их от реальных случайных чисел.
CodesInChaos

4

У меня есть два ответа для вас.

=== ПЕРВЫЙ ОТВЕТ ===

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

Но потом я читаю ваш вопрос. И для конкретной проблемы, которую вы описываете, это не ответ. Ваша проблема заключалась не в том, что вам нужно было сделать предсказуемый процесс, использующий случайные числа (так что это было бы проверяемым). Скорее, ваша проблема состояла в том, чтобы убедиться, что ваш алгоритм отображал равномерно случайный выходной сигнал от вашего ГСЧ на выходной сигнал с единообразным ограничением из вашего алгоритма - что если базовый ГСЧ был однородным, это привело бы к равномерно распределенному времени проверки (при условии проблемные ограничения).

Это действительно сложная (но довольно четко определенная) проблема. Это означает, что это ИНТЕРЕСНАЯ проблема. Сразу же начал думать о некоторых действительно хороших идеях о том, как решить эту проблему. Когда я был горячим программистом, я мог начать что-то делать с этими идеями. Но я больше не программист ... Мне нравится, что я теперь более опытный и опытный.

Таким образом, вместо того, чтобы погрузиться в сложную проблему, я подумал про себя: какова ценность этого? И ответ был неутешительным. Ваша ошибка уже решена, и вы будете прилежны к этой проблеме в будущем. Внешние обстоятельства не могут вызвать проблему, только изменения в вашем алгоритме. ЕДИНСТВЕННАЯ причина для решения этой интересной проблемы состояла в том, чтобы удовлетворить методы TDD (Test Driven Design). Если есть одна вещь, которую я узнал, так это то, что слепое следование любой практике, когда она не является ценной, вызывает проблемы. Мое предложение таково: просто не пишите тест для этого и продолжайте.


=== ВТОРОЙ ОТВЕТ ===

Вау ... какая классная проблема!

Здесь вам нужно написать тест, который подтвердит, что ваш алгоритм выбора дат и времени проверки будет давать выходные данные, которые будут равномерно распределены (в пределах ограничений задачи), если используемый им ГСЧ выдает равномерно распределенные числа. Вот несколько подходов, отсортированных по уровню сложности.

  1. Вы можете применить грубую силу. Просто запустите алгоритм целую кучу раз, с реальным RNG в качестве входных данных. Проверьте выходные результаты, чтобы увидеть, распределены ли они равномерно. Ваш тест должен будет провалиться, если распределение варьируется от идеально однородного более чем до определенного порогового значения, и чтобы гарантировать, что вы обнаружите проблемы, пороговое значение не может быть установлено СЛИШКОМ низким. Это означает, что вам понадобится ОГРОМНОЕ количество прогонов, чтобы быть уверенным, что вероятность ложного срабатывания (случайный сбой теста) очень мала (хорошо <1% для кодовой базы среднего размера; еще меньше для большая кодовая база).

  2. Рассмотрим ваш алгоритм как функцию, которая принимает конкатенацию всех выходных данных ГСЧ в качестве входных данных, а затем выдает время проверки в качестве выходных данных. Если вы знаете, что эта функция является кусочно-непрерывной, то есть способ проверить ваше свойство. Замените ГСЧ на поддельный ГСЧ и запустите алгоритм несколько раз, создавая равномерно распределенный выход ГСЧ. Таким образом, если вашему коду потребовалось 2 вызова RNG, каждый в диапазоне [0..1], вы можете выполнить тестовый прогон алгоритма 100 раз, возвращая значения [(0.0,0.0), (0.0,0.1), (0.0, 0,2), ... (0,0,0,9), (0,1,0,0), (0,1,0,1), ... (0,9,0,9)]. Затем вы можете проверить, был ли выход 100 прогонов (приблизительно) равномерно распределен в пределах допустимого диапазона.

  3. Если вам ДЕЙСТВИТЕЛЬНО необходимо проверить алгоритм надежным способом и вы не можете делать предположения относительно алгоритма ИЛИ запускаться большое количество раз, то вы все равно можете атаковать проблему, но вам могут потребоваться некоторые ограничения на то, как вы программируете алгоритм , Посмотрите на PyPy и подход к объектному пространству в качестве примера. Вы можете создать объектное пространство, которое вместо того, чтобы фактически выполнять алгоритм, вместо этого просто рассчитало форму выходного распределения (предполагая, что вход ГСЧ является однородным). Конечно, это требует, чтобы вы собрали такой инструмент и чтобы ваш алгоритм был построен в PyPy или каком-либо другом инструменте, где легко внести радикальные изменения в компилятор и использовать его для анализа кода.


3

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

Вы не хотите, чтобы ваши модульные тесты игнорировали, например, отдельные ошибки, возникающие, когда Random.nextInt (1000) возвращает 0 или 999.


3

Возможно, вы захотите взглянуть на Sevcikova et al: «Автоматизированное тестирование стохастических систем: статистически обоснованный подход» ( PDF ).

Методология реализована в различных тестовых примерах для платформы моделирования UrbanSim .


Это хорошо, там.
KeithS

2

Простой гистограммный подход - хороший первый шаг, но его недостаточно для доказательства случайности. Для равномерного PRNG вы также (как минимум) сгенерировали бы двумерный график рассеяния (где x - предыдущее значение, а y - новое значение). Этот сюжет также должен быть равномерным. Это сложно в вашей ситуации, потому что в системе существуют преднамеренные нелинейности.

Мой подход будет следующим:

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

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

Что касается природы алгоритма преобразования / ограничения:

Дано: метод генерации псевдослучайного значения p, где 0 <= p <= M

Потребность: вывод y в (возможно, прерывистом) диапазоне 0 <= y <= N <= M

Алгоритм:

  1. рассчитать r = floor(M / N), то есть количество полных выходных диапазонов, которые соответствуют входному диапазону.
  2. Рассчитаем максимально допустимое значение для р :p_max = r * N
  3. генерировать значения для p, пока не p_maxбудет найдено значение, меньшее или равное
  4. высчитывает y = p / r
  5. если у приемлемо, верните его, в противном случае повторите с шагом 3

Ключ в том, чтобы отбрасывать неприемлемые значения, а не складывать неравномерно.

в псевдокоде:

# assume prng generates non-negative values
def randomInRange(min, max, prng):
    range = max - min
    factor = prng.max / range

    do:
        value = prng()
    while value > range * factor
    return (value / factor) + min

def constrainedRandom(constraint, prng):
    do:
        value = randomInRange(constraint.min, constraint.max, prng)
    while not constraint.is_acceptable(value)

1

Помимо проверки того, что ваш код не дает сбоев или выдает правильные исключения в нужных местах, вы можете создать действительные пары ввода / ответа (даже вычисляя это вручную), передать входные данные в тесте и убедиться, что он возвращает ожидаемый ответ. Не очень, но это все, что ты можешь сделать, имхо. Тем не менее, в вашем случае это не совсем случайно, когда вы создаете расписание, вы можете проверить соответствие правилам - должно быть 3 проверки в неделю, между 9-9; нет никакой реальной необходимости или возможности проверять точное время, когда произошла проверка.


1

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


Но если это действительно случайно, то иногда какое-то расписание вообще не будет использоваться; и иногда, некоторое расписание будет использоваться более 20 раз. Я не знаю, как вы намереваетесь проверить, что каждое расписание используется «почти 10 раз», но при любом условии, которое вы здесь тестируете, у вас будет тест, который иногда дает сбой, когда программа работает по спецификации.
Дауд говорит восстановить Монику

@DawoodibnKareem с достаточным размером выборки (и разумными ограничениями на однородность) вы можете уменьшить вероятность того, что тест не пройдёт, до 1 на миллиарды. И, как правило, такая статистика является экспоненциальной с n, поэтому она занимает меньше, чем вы ожидаете, чтобы получить эти цифры.
mbrig

1

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

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


2
Вы можете абсолютно точно определить случайность с помощью автоматического тестирования. Вы просто генерируете достаточно большое количество выборок и применяете стандартные тесты случайности, чтобы обнаружить отклонения в системе. Это довольно стандартное упражнение по программированию для студентов.
Фрэнк Щерба

0

Просто запишите выходные данные вашего рандомизатора (будь то псевдо или квантово / хаотично или в реальном мире). Затем сохраните и воспроизведите те «случайные» последовательности, которые соответствуют вашим требованиям к тестам или которые раскрывают потенциальные проблемы и ошибки, по мере того, как вы создаете свои тестовые примеры.


0

Этот случай кажется идеальным для тестирования на основе свойств .

В двух словах, это режим тестирования, при котором инфраструктура тестирования генерирует входные данные для тестируемого кода, а тестовые утверждения проверяют свойства выходных данных. Фреймворк может быть достаточно умным, чтобы «атаковать» тестируемый код и попытаться превратить его в ошибку Фреймворк, как правило, также достаточно умен, чтобы захватить ваш генератор случайных чисел. Как правило, вы можете сконфигурировать платформу для генерации не более N тестовых случаев или выполнения не более N секунд, а также запоминать неудачные тестовые примеры из последнего запуска и заново запускать их для более новой версии кода. Это учитывает быстрый цикл итерации во время разработки и медленное, всестороннее тестирование вне полосы / в CI.

Вот (тупой, неудачный) пример тестирования sumфункции:

@given(lists(floats()))
def test_sum(alist):
    result = sum(alist)
    assert isinstance(result, float)
    assert result > 0
  • Тестовая структура будет генерировать один список за раз
  • содержимое списка будет числами с плавающей запятой
  • sum вызывается и свойства результата проверяются
  • результат всегда плавающий
  • результат положительный

Этот тест обнаружит кучу «ошибок» sum(прокомментируйте, если вы смогли угадать все это самостоятельно):

  • sum([]) is 0 (int, а не float)
  • sum([-0.9]) отрицательно
  • sum([0.0]) не является строго положительным
  • sum([..., nan]) is nan что не является положительным

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

В случае OP проверенные свойства будут более сложными: присутствует тип проверки A, вид проверки A трижды в неделю, время проверки B всегда в 12:00, тип проверки C с 9 до 9, [данный график рассчитан на неделю] проверки типов A, B, C все присутствует и т. Д.

Самая известная библиотека - QuickCheck для Haskell, список страниц на других языках см. На странице Википедии ниже:

https://en.wikipedia.org/wiki/QuickCheck

Гипотеза (для Python) имеет хорошее описание этого вида тестирования:

https://hypothesis.works/articles/what-is-property-based-testing/


-1

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

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


-1

Ваша цель - не писать модульные тесты и проходить их, а убедиться, что ваша программа соответствует ее требованиям. Единственный способ сделать это - в первую очередь точно определить ваши требования. Например, вы упомянули «три еженедельные проверки в случайное время». Я бы сказал, что следующие требования: (а) 3 проверки (не 2 или 4), (б) время от времени, которые не могут быть предсказаны людьми, которые не хотят, чтобы их неожиданно осматривали, и (в) не слишком близко друг к другу - две инспекции с интервалом в пять минут, вероятно, не имеют смысла, возможно, не слишком далеко друг от друга.

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

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


Нет, просто нет. Модульные тесты доказывают, что программа соответствует ее требованиям, поэтому оба являются одним и тем же. И я не занимаюсь написанием прогностического программного обеспечения для обратной разработки случайных алгоритмов. Если бы я был, я бы не стал рассказывать вам об этом, я бы совершил убийство, взломав безопасные веб-сайты, предсказав их ключи и распродав секреты самой высокой цене. Моя задача - написать планировщик, который создает ограниченные, но непредсказуемые времена в рамках ограничений, и мне нужны детерминированные тесты, чтобы доказать, что я это сделал, а не вероятностные, которые говорят, что я почти уверен.
KeithS
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.