Модульное тестирование с функциями, которые возвращают случайные результаты - PullRequest
67 голосов
/ 23 ноября 2008

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

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

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

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

Ответы [ 11 ]

51 голосов
/ 23 ноября 2008

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

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

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, чтобы вернуть что-то консервированное.

32 голосов
/ 23 ноября 2008

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

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

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

9 голосов
/ 23 ноября 2008

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

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

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

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

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

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

8 голосов
/ 23 ноября 2008

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

5 голосов
/ 23 ноября 2008

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

4 голосов
/ 23 ноября 2008

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

2 голосов
/ 29 января 2010

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

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

2 голосов
/ 11 ноября 2009

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

2 голосов
/ 23 ноября 2008

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

1 голос
/ 02 февраля 2010

Я бы рекомендовал переопределить случайную функцию. Я занимаюсь модульным тестированием в 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);
   }
}

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

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

...