Метод JUnit Test со случайным характером

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

У меня есть класс Deck, представляющий колоду карт (это очень просто и, честно говоря, я могу быть уверен, что он работает без unit test, но, как я уже сказал, я привык к использованию модульные тесты) и имеет метод shuffle(), который изменяет порядок карт в колоде.

Реализация очень проста и, безусловно, будет работать:

public void shuffle()
{
    Collections.shuffle(this.cards);
}

Но как я мог реализовать unit test для этого метода. Моя первая мысль состояла в том, чтобы проверить, отлична ли верхняя карта колоды после вызова shuffle(), но, конечно, есть вероятность, что она будет такой же. Моя вторая мысль состояла в том, чтобы проверить, изменился ли весь порядок карточек, но опять же они могут быть в том же порядке. Итак, как я могу написать тест, который гарантирует, что этот метод работает во всех случаях? И вообще, как вы можете unit test методы, для которых результат зависит от некоторой случайности?

Приветствия,

Пит

Ответ 1

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

Однако вы можете проверить, являются ли инварианты этого метода.

  • Если вы выходите из метода, в колоде должно быть одинаковое количество карт, как при вводе.
  • Метод shuffle не должен вводить дубликаты.

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

Что-то еще, что нужно учитывать, это генератор случайных чисел. Если это всего лишь игрушечный проект, достаточно java.util.Random. Если вы намереваетесь создать какую-либо карточную игру в сети, подумайте об использовании java.security.SecureRandom.

Ответ 2

Во-первых, подумайте о вероятностях:

  • Вы не можете гарантировать, что тасовка не будет размещать карты в точном порядке. Однако вероятность сделать это с колодой из 52 карт составляет 1/52! (т.е. минимально и, вероятно, не стоит беспокоиться.)

  • Вам определенно нужно будет проверить всю колоду, хотя из-за вероятности того, что верхняя карта была такой же, как и до тасования, 1/52.

В общем случае, и предположим, что вы используете генератор чисел java.util.Random, просто инициализируйте его одним и тем же семенем. Затем выход для предварительно определенного входа должен быть повторяемым.

Однако, в частности, для этого случая, если вы не внедрили свой собственный List, я действительно не вижу смысла в тестировании Collections.shuffle(List<?> list) или Collections.shuffle(List<?> list, Random rnd) (ссылка API), поскольку они являются частью Java API.

Ответ 3

Другим подходом было бы использовать метод shuffle(List<?> list, Random random) и ввести экземпляр Random, засеянный константой.

Таким образом, ваш тест JUnit может запускать серию вызовов и проверять вывод как ожидаемый результат.

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

Ответ 4

Вы фактически делегируете всю тяжелую работу классу java.util.Collections. Это центральный класс в API Java collection API, и вы должны просто предположить, что он работает, как вы, вероятно, со классом java.lang.String.

Я бы предпочел рекомендовать код для интерфейсов и макет/заглушить ваш класс реализации с помощью метода shuffle(). Тогда вы можете просто утверждать, что ваши вызовы по методу shuffle() на самом деле вызываются из вашего теста вместо тестирования точно так же, как ребята из Sun/Oracle тестировали до этого.

Это позволяет вам больше сосредоточиться на тестировании собственного кода, где, вероятно, 99,9% всех ошибок. И если вы, например, замените метод java.util.Collections.shuffle() на один из другого фреймворка или свою собственную реализацию, ваш интеграционный тест все равно будет работать!

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

Ответ 5

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

Итак, ответ: проверьте, что порядок для всей колоды отличается.

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

Ответ 6

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

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

Ответ 7

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

Но вы не должны тестировать сам язык Java.

Должен быть какой-то принцип тестирования, например "Не тестировать PlusEquals".

Ответ 8

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

Ответ 9

Вы можете перетасовать несколько раз, отслеживая, сколько раз Туз пик (или другая карта или все другие карты) заканчивается первой карточкой в ​​колоде. Теоретически карта должна быть на вершине около 1 из 52 тасов. После того, как все данные собраны, сравните фактическую частоту с номером 1/52 и проверьте, не меньше ли значение (абсолютное значение), чем какое-то выбранное значение эпсилона. Чем больше вы перетасовываете, тем меньше будет значение вашего эпсилона. Если ваш метод shuffle() помещает карту в верхнюю часть вашего порога epsilon, вы можете быть уверены, что она рандомизирует карты, как вам хотелось бы.

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