Как создать генератор с фиксированным списком элементов для FsCheck

Первоначально я пытался создать генератор с фиксированными первыми 5 элементами (и в любом тесте, использующем Prop.forAll, всегда будут работать первые пять), но мне это не удалось.

Теперь я пытаюсь упростить это, имея один генератор для случайных данных в пределах диапазона и один генератор для неслучайных данных, то есть фиксированной последовательности. Это похоже на Gen.constant, за исключением того, что вместо одного значения это последовательность значений.

У меня есть это (упрощенный воспроизводимый пример, работает с NUnit и xUnit):

[<Property(Verbose = true, MaxTest=5)>]
static member MultiplyIdentityCornerCases () =
    Gen.elements [0L; -1L; 1L; Int64.MinValue; Int64.MaxValue]
    |> Arb.fromGen 
    |> Prop.forAll <| fun x -> x = x * 1L

Вывод (не знаю, откуда null):

0:
<null>
9223372036854775807L
1:
<null>
-9223372036854775807L
2:
<null>
-9223372036854775807L
3:
<null>
1L
4:
<null>
-9223372036854775807L
Ok, passed 5 tests.

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

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


person Abel    schedule 09.12.2016    source источник
comment
Если подумать вслух, то одним из способов исправить это, возможно, является использование неслучайности генератора чисел (или проверка того, как это делается), для целых чисел, похоже, он работает именно так, как мне нужно (см. вывод FsCheck моего ранее вопрос)   -  person Abel    schedule 09.12.2016


Ответы (1)


Я не знаю, что это возможно, но вы можете сделать это:

open System
open FsCheck
open FsCheck.Xunit

[<Property>]
let MultiplyIdentityCornerCases () =
    Gen.oneof [
        Gen.elements [Int64.MinValue; -1L; 0L; 1L; Int64.MaxValue]
        Arb.generate ]
    |> Arb.fromGen
    |> Prop.forAll <| fun x -> x = x * 1L

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

Gen.elements должен единообразно выбирать из всех значений в предоставленной последовательности, поэтому он будет использовать, например. 0L 20% времени, но только в той половине случаев, когда Gen.oneof использует Gen.elements.

Другими словами, каждое из этих «специальных» значений будет генерироваться 50% * 20% = 10% времени.

По умолчанию свойство выполняет 100 тестовых случаев, поэтому в среднем оно должно генерировать 10 значений 0L, 10 значений Int64.MinValue и т. д. Часто этого должно быть достаточно.


Если это не так, вы всегда можете сделать что-то вроде этого:

open System
open Xunit
open FsCheck
open FsCheck.Xunit
open Swensen.Unquote

[<Theory>]
[<InlineData(Int64.MinValue)>]
[<InlineData(-1L)>]
[<InlineData( 0L)>]
[<InlineData( 1L)>]
[<InlineData(Int64.MaxValue)>]
let MultiplyIdentityCornerCases x = x =! x * 1L

[<Property>]
let MultiplyIdentityCornerCasesProperty x =
    MultiplyIdentityCornerCases x

Здесь вы определяете параметризованный тест, используя функцию [<Theory>] xUnit.net, и передаете ему пять крайних случаев, которые вас интересуют. Когда вы запускаете тесты, программа запуска тестов запустит эти пять тестовых случаев.

Более того, он запустит MultiplyIdentityCornerCasesProperty, потому что он помечен [<Property>], а эта функция просто вызывает другую функцию.

person Mark Seemann    schedule 09.12.2016
comment
Мне больше всего нравится второй фрагмент кода. Это на самом деле довольно аккуратно. - person Nikos Baxevanis; 09.12.2016
comment
Еще раз спасибо, Марк. Мне нравится второй подход, при условии, что я могу заменить статические данные для поставщика данных, но это не должно быть проблемой. На данный момент я использовал вариант вашего первого подхода, описанного выше, который, кажется, отлично работает на практике (и убедившись, что выполняется достаточно тестов, чтобы с определенной уверенностью поразить все вероятности). - person Abel; 10.12.2016