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