作为一般观察,Arbitrary
值很难组合,而Gen
价值观很容易。出于这个原因,我倾向于根据以下方面来定义我的 FsCheck 构建块:Gen<'a>
代替Arbitrary<'a>
.
With Gen
值,您可以使用组合多个参数Gen.map2
, Gen.map3
等,或者您可以使用gen
计算表达式。
Gen 积木
在OP示例中,不是定义pieces
and positionsList
as Arbitrary
,将它们定义为Gen
values:
let genPieces = Arb.generate<Piece> |> Gen.filter (isKing >> not)
let genPositionsList = Arb.generate<Space list>
这些是类型的“构建块”Gen<Piece>
and Gen<Space list>
, 分别。
请注意我给它们起了名字genPieces
而不是简单地pieces
, 等等。这可以防止以后发生名称冲突(见下文)。 (另外,我不确定复数的使用s in pieces
, 因为genPieces
只生成一个Piece
值,但由于我不知道您的整个域,所以我决定保持原样。)
如果您只需要其中之一,可以将其转换为Arbitrary
using Arb.fromGen
.
如果需要组合它们,可以使用映射函数或计算表达式之一,如下所示。这会给你一个Gen
元组,然后你可以使用Arb.fromGen
将其转换为Arbitrary
.
使用map2编写
如果您需要作曲pieces
and 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)
返回一个二元素元组(apair)的值,您可以将其解构为(pieces, positionList)
在匿名函数中。
这个例子也应该可以清楚地说明原因genPieces
and genPositionList
是更好的名字Gen
价值观:他们为使用“裸名”留出空间pieces
and 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 的罗马数字 http://blog.ploeh.dk/2016/06/28/roman-numerals-via-property-based-tdd.