Единичное тестирование с функциями, возвращающими случайные результаты

Я не думаю, что это специфично для языка или фреймворка, но я использую xUnit.net и С#.

У меня есть функция, которая возвращает случайную дату в определенном диапазоне. Я передаю дату, и дата возвращения всегда находится в диапазоне от 1 до 40 лет до указанной даты.

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

Я также понимаю, что, если я не могу контролировать генератор Random, не будет идеального решения (в конце концов, результат IS случайный), но мне интересно, какие подходы вы предпринимаете, когда вам нужно проверить функциональность, которая возвращает случайный результат в определенном диапазоне?

Ответ 1

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

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

Раньше у меня была проблема с неравномерными "случайными" функциями.. они могут быть настоящей болью, ее стоит проверить на раннем этапе.

Ответ 2

Макет или подделка генератора случайных чисел

Сделайте что-то вроде этого... Я не скомпилировал его, чтобы могло быть несколько синтаксических ошибок.

public interface IRandomGenerator
{
    double Generate(double max);
}

public class SomethingThatUsesRandom
{
    private readonly IRandomGenerator _generator;

    private class DefaultRandom : IRandomGenerator
    {
        public double Generate(double max)
        {
            return (new Random()).Next(max);
        }
    }

    public SomethingThatUsesRandom(IRandomGenerator generator)
    {
        _generator = generator;
    }

    public SomethingThatUsesRandom() : this(new DefaultRandom())
    {}

    public double MethodThatUsesRandom()
    {
        return _generator.Generate(40.0);
    }
}

В вашем тесте просто подделайте или издевайтесь над IRandomGenerator, чтобы вернуть что-то консервированное.

Ответ 3

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

Первый: мой алгоритм правильный? То есть, учитывая правильно функционирующий генератор случайных чисел, будут ли они создавать даты, которые случайным образом распределены по диапазону?

Второй: правильно ли обрабатываются граничные случаи? То есть, когда генератор случайных чисел создает наивысшие или самые низкие допустимые значения, что-нибудь сломается?

Третий: моя реализация алгоритма работает? То есть, учитывая известный список псевдослучайных входов, создается ли ожидаемый список псевдослучайных дат?

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

Последнее, что я сделал бы для unit test для. Мне нужно знать, что в код не влезло, что нарушает его реализацию этого алгоритма. Первый признак, который я получу, когда это произойдет, это то, что тест не удастся. Затем я вернусь к коду и узнаю, что кто-то еще подумал, что они что-то исправляют и сломали его. Если кто-то исправит алгоритм, это также поможет им исправить этот тест.

Ответ 4

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

Ответ 5

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

Ответ 6

Если вы хотите проверить качество случайных чисел (с точки зрения независимости), есть несколько способов сделать это. Одним из хороших способов является Chi square test.

Ответ 7

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

Ответ 8

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

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

Ответ 9

Методы, которые не демонстрируют детерминированное поведение, не могут быть правильно проверены на единицу, так как результаты будут отличаться от одного исполнения к другому. Один из способов обойти это - seed генератор случайных чисел с фиксированным значением для unit test. Вы также можете извлечь случайность класса генерации дат (и, таким образом, применить принцип единой ответственности) и ввести известные значения для модульных тестов,

Ответ 10

Я бы рекомендовал переопределить случайную функцию. Я являюсь модульным тестированием на PHP, поэтому я пишу этот код:

// If we are unit testing, then...
if (defined('UNIT_TESTING') && UNIT_TESTING)
{
   // ...make our my_rand() function deterministic to aid testing.
   function my_rand($min, $max)
   {
      return $GLOBALS['random_table'][$min][$max];
   }
}
else
{
   // ...else make our my_rand() function truly random.
   function my_rand($min = 0, $max = PHP_INT_MAX)
   {
      if ($max === PHP_INT_MAX)
      {
         $max = getrandmax();
      }
      return rand($min, $max);
   }
}

Затем я устанавливаю случайную_таблицу, как я требую ее для каждого теста.

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

Ответ 11

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