Модульное тестирование с функциями, возвращающими случайные результаты
Я не думаю, что это специфично для языка или фреймворка, но я использую xUnit.net и C#.
У меня есть функция, которая возвращает случайное дату в определенном диапазоне. Я передаю дату, и дата возвращения всегда находится в диапазоне от 1 до 40 лет до данной даты.
теперь мне просто интересно, есть ли хороший способ проверить это. Лучшим подходом, по-видимому, является создание цикла и запуск функции, т. е. 100 раз, и утверждение, что каждый из этих 100 результатов находятся в желаемом диапазоне, что является моим текущим подходом.
Я также понимаю, что если я не смогу управлять своим случайным генератором, не будет идеального решения (в конце концов, результат случайный), но мне интересно, какие подходы вы принимаете, когда вам нужно проверить функциональность, которая возвращает случайный результат в определенном диапазоне?
11 ответов:
в дополнение к проверке того, что функция возвращает дату в желаемом диапазоне, вы хотите убедиться, что результат хорошо распределен. Тест, который вы описываете, передаст функцию, которая просто вернет дату, которую вы отправили!
поэтому в дополнение к вызову функции несколько раз и проверке того, что результат остается в желаемом диапазоне, я бы также попытался оценить распределение, возможно, поместив результаты в ведра и проверив, что ведра примерно равны количество результатов после того как вы сделали. Возможно, вам потребуется более 100 вызовов для получения стабильных результатов, но это не похоже на дорогостоящую (во время выполнения) функцию, поэтому вы можете легко запустить ее за несколько K итераций.
У меня раньше была проблема с неоднородными "случайными" функциями.. они могут быть настоящей болью, это стоит проверить на ранней стадии.
макет или подделать генератор случайных чисел
сделать что-то подобное... Я не компилировал его, так что может быть несколько синтаксических ошибок.
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, чтобы вернуть что-то консервированное.
Я думаю, что есть три различных аспекта этой проблемы, которые вы тестируете.
первый: является ли мой алгоритм правильным? То есть, учитывая правильно функционирующий генератор случайных чисел, будет ли он производить даты, которые случайным образом распределены по диапазону?
второй: правильно ли алгоритм обрабатывает граничные случаи? То есть, когда генератор случайных чисел производит самые высокие или самые низкие допустимые значения, что-нибудь ломается?
в третий: работает ли моя реализация алгоритма? То есть, учитывая известный список псевдослучайных входов, он создает ожидаемый список псевдослучайных дат?
первые две вещи-это не то, что я бы встроил в комплект модульного тестирования. Это то, что я бы доказал при разработке системы. Вероятно, я бы сделал это, написав тестовый жгут, который сгенерировал миллион дат и выполнил тест хи-квадрат, как Даниэль.- предположил риковски. Я бы также убедился, что этот тест жгут не прекращался, пока он не обработал оба крайних случая (предполагая, что мой диапазон случайных чисел достаточно мал, чтобы я мог уйти с этим). И я бы задокументировал это, чтобы любой, кто придет и попытается улучшить алгоритм, знал, что это критическое изменение.
последний и то,что я бы сделал модульный тест. Мне нужно знать, что в код ничего не прокралось, что нарушает его реализацию этого алгоритма. Первый знак, что я буду когда это произойдет, что тест будет провален. Затем я вернусь к коду и узнаю, что кто-то еще думал, что они что-то исправляют и вместо этого сломали его. Если кто-то сделал исправить алгоритм, это было бы на них, чтобы исправить этот тест тоже.
вам не нужно управлять системой, чтобы результаты были детерминированы. Вы находитесь на правильном подходе: решите, что важно о выходе функции и проверить для этого. В этом случае важно, чтобы результат был в диапазоне 40 дней, и ты испытываешь к этому. Также важно, чтобы он не всегда возвращал один и тот же результат, поэтому проверьте и это. Если вы хотите быть более причудливым, вы можете проверить, что результаты проходят какой-то тест на случайность..
обычно я использую именно ваш предложенный подход: управление случайным генератором. Инициализируйте его для теста с семенем по умолчанию (или замените его прокси-сервером, возвращающим номера, которые соответствуют моим тестовым случаям), поэтому у меня есть детерминированное/тестируемое поведение.
Если вы хотите проверить качество случайных чисел (в плане независимости) есть несколько способов сделать это. Один хороший способ-это хи квадрат тест.
в зависимости от того, как ваша функция создает случайную дату, вы также можете проверить наличие незаконных дат: невозможные високосные годы или 31-й день 30-дневного месяца.
методы, которые не показывают a детерминированное поведение не может быть должным образом проверен блок, так как результаты будут отличаться от одного выполнения к другому. Один из способов обойти это -seed генератор случайных чисел с фиксированным значением для единичного теста. Вы также можете извлечь случайность класса генерации даты (и, таким образом, применяя Принцип Единой Ответственности), и ввести известные значения для юнит-тестов.
конечно, использование генератора случайных чисел с фиксированным семенем будет работать просто отлично, но даже тогда вы просто пытаетесь проверить то, что вы не можете предсказать. И это нормально. Это эквивалентно тому, чтобы иметь кучу фиксированных тестов. Однако, помните, что это важно, но не пытайтесь проверить все. Я считаю, что случайные тесты-это способ попытаться проверить все, и это не эффективно (или быстро). Возможно, вам придется выполнить очень много рандомизированных тестов, прежде чем попасть в жук.
Я пытаюсь понять, что вы должны просто написать тест для каждой ошибки, которую вы найдете в своей системе. Вы тестируете крайние случаи, чтобы убедиться, что ваша функция работает даже в экстремальных условиях, но на самом деле это лучшее, что вы можете сделать, не тратя слишком много времени или замедляя выполнение модульных тестов или просто тратя циклы процессора.
Я бы рекомендовал переопределить функцию Random. Я юнит-тестирование в 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, как мне требуется для каждого теста.
проверка истинной случайности случайной функции является отдельным тестом в целом. Я бы избегал тестирования случайности в модульных тестах и вместо этого делал бы отдельные тесты и google истинную случайность случайной функции на языке программирования, который вы используете. Недетерминированные тесты (если таковые имеются) должны быть исключены из модульных тестов. Возможно, есть отдельный набор для этих тестов, который требует человеческого ввода или гораздо более длительного времени работы, чтобы свести к минимуму шансы на неудачу, которая действительно является пропуском.
Я не думаю, что модульное тестирование предназначено для этого. Вы можете использовать модульное тестирование для функций, которые возвращают стохастическое значение, но используют фиксированное семя, и в этом случае они не являются стохастическими, так сказать, для случайного семени я не думаю, что модульное тестирование-это то, что вы хотите, например, для RNGs то, что вы имеете в виду, - это системный тест, в котором вы запускаете RNG много раз и смотрите на распределение или моменты его.