Как реализовать генерацию нескольких аргументов с помощью FsCheck?

Как реализовать генерацию нескольких аргументов с помощью FsCheck?

Я реализовал следующее для поддержки генерации нескольких аргументов:

// Setup
let pieces =    Arb.generate<Piece> |> Gen.filter (isKing >> not)
                                    |> Arb.fromGen  

let positionsList = Arb.generate<Space list> |> Arb.fromGen

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

// Test
Prop.forAll pieces <| fun piece ->
    Prop.forAll positionsList <| fun positionsItem ->

        positionsItem |> optionsFor piece 
                      |> List.length <= 2

Является ли вложение выражений Prop.forAll правильным методом при управлении несколькими сгенерированными типами аргументов?

Есть ли альтернативный метод для создания нескольких аргументов для тестируемой функции?

Вот вся функция:

open FsCheck
open FsCheck.Xunit

[<Property(QuietOnSuccess = true)>]
let ``options for soldier can never exceed 2`` () =

    // Setup
    let pieces =    Arb.generate<Piece> |> Gen.filter (isKing >> not)
                                        |> Arb.fromGen  

    let positionsList = Arb.generate<Space list> |> Arb.fromGen

    // Test
    Prop.forAll pieces <| fun piece ->
        Prop.forAll positionsList <| fun positionsItem ->

            positionsItem |> optionsFor piece 
                          |> List.length <= 2

ОБНОВЛЕНИЕ

Вот решение моего вопроса, полученное из ответа Марка:

[<Property(QuietOnSuccess = true, MaxTest=100)>]
let ``options for soldier can never exceed 2`` () =

    // Setup
    let pieceGen =     Arb.generate<Piece> |> Gen.filter (isKing >> not)
    let positionsGen = Arb.generate<Space list>

    // Test
    (pieceGen , positionsGen) ||> Gen.map2 (fun x y -> x,y)
                               |> Arb.fromGen
                               |> Prop.forAll <| fun (piece , positions) -> 
                                                   positions |> optionsFor piece 
                                                             |> List.length <= 2

person Scott Nimrod    schedule 08.08.2016    source источник


Ответы (1)


Как правило, значения Arbitrary сложно составить, а значения Gen — легко. По этой причине я обычно определяю свои строительные блоки FsCheck с точки зрения Gen<'a> вместо Arbitrary<'a>.

Со значениями Gen вы можете составить несколько аргументов, используя Gen.map2, Gen.map3 и т. д., или вы можете использовать выражение вычисления gen.

Генеральные строительные блоки

В примере OP вместо определения pieces и positionsList как Arbitrary определите их как значения Gen:

let genPieces = Arb.generate<Piece> |> Gen.filter (isKing >> not)

let genPositionsList = Arb.generate<Space list>

Это «строительные блоки» типов Gen<Piece> и Gen<Space list> соответственно.

Обратите внимание, что я назвал их genPieces, а не просто pieces, и так далее. Это предотвращает конфликты имен в дальнейшем (см. ниже). (Кроме того, я не уверен в использовании множественного числа s в pieces, потому что genPieces генерирует только одно значение Piece, но, поскольку я не знаю весь ваш домен, я решил оставить то как есть.)

Если вам нужен только один из них, вы можете преобразовать его в Arbitrary, используя Arb.fromGen.

Если вам нужно составить их, вы можете использовать либо одну из функций отображения, либо вычислительные выражения, как показано ниже. Это даст вам Gen кортежей, а затем вы можете использовать Arb.fromGen, чтобы преобразовать их в Arbitrary.

Создать с помощью map2

Если вам нужно составить pieces и positionsList в список аргументов, вы можете использовать Gen.map2:

Gen.map2 (fun x y -> x, y) genPieces genPositionList
|> Arb.fromGen
|> Prop.forAll <| fun (pieces, positionList) -> 
    // test goes here...

Gen.map2 (fun x y -> x, y) возвращает двухэлементный кортеж (пара) значений, который можно преобразовать в (pieces, positionList) в анонимной функции.

Этот пример также должен прояснить, почему genPieces и genPositionList являются лучшими именами для значений Gen: они оставляют место для использования «голых» имен pieces и positionList для сгенерированных значений, передаваемых в тело теста.

Составить с помощью вычислительного выражения

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

Приведенный выше пример также можно было бы написать так:

gen {
    let! pieces = genPieces
    let! positionList = genPositionList
    return pieces, positionList }
|> Arb.fromGen
|> Prop.forAll <| fun (pieces, positionList) -> 
    // test goes here...

Исходное выражение gen также возвращает пару, поэтому оно эквивалентно композиции с Gen.map2.

Вы можете использовать вариант, который вы считаете наиболее читаемым.

Вы можете увидеть больше примеров нетривиальных комбинаций Gen в моей статье римские цифры. через TDD на основе свойств.

person Mark Seemann    schedule 09.08.2016
comment
Спасибо, Марк. Помимо блогов, есть ли книги по этому поводу? Я искал в Интернете и не смог найти справочник. Следовательно, я хотел бы понять внутренности фреймворков тестирования на основе свойств на высоком уровне, не будучи гуру FP. - person Scott Nimrod; 09.08.2016
comment
@ScottNimrod Я не знаю ни одной книги. - person Mark Seemann; 09.08.2016