Как реализовать генерацию нескольких аргументов с помощью 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

1 ответ

Решение

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

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

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, и так далее. Это предотвращает конфликт имен позже (см. Ниже). (Также я не уверен насчет использования множественного числа в 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.

Другие вопросы по тегам