我认为现在是时候尝试FsCheck,但事实证明它比我想象的更难。有关Arb
,生成器等的大量文档,但似乎没有任何关于如何应用这些知识的指导。或者我只是没有得到它。
可能让人难以理解的是,测试,属性,生成器,仲裁,缩小之间的关系,以及在我的情况下,随机性(一些测试自动生成随机数据,其他测试不同)之间的关系并不清楚我。我没有Haskell背景,因此也没有多大帮助。
现在提出问题:如何生成随机整数?
我的测试场景可以用乘法的属性来解释,让我们说分配:
static member ``Multiplication is distributive`` (x: int64) y z =
let res1 = x * (y + z)
let res2 = x * y + x * z
res1 = res2
// run it:
[<Test>]
static member FsCheckAsUnitTest() =
Check.One({ Config.VerboseThrowOnFailure with MaxTest = 1000 }, ``Multiplication is distributive``)
当我使用Check.Verbose
或NUnit集成运行时,我得到的测试序列如下:
0:
(-1L, -1L, -1L)
1:
(-1L, -1L, 0L)
2:
(-1L, -1L, -1L)
3:
(-1L, -1L, -1L)
4:
(-1L, 0L, -1L)
5:
(1L, 0L, 2L)
6:
(-2L, 0L, -1L)
7:
(-2L, -1L, -1L)
8:
(1L, 1L, -2L)
9:
(-2L, 2L, -2L)
经过1000次测试后,它还没有超过100L
。不知怎的,我想象这会自动&#34;选择在int64
的整个范围内均匀分布的随机数,至少我是如何解释文档的。
由于它没有,我开始尝试并提出如下的愚蠢解决方案以获得更高的数字:
type Generators =
static member arbMyRecord =
Arb.generate<int64>
|> Gen.where ((<) 1000L)
|> Gen.three
|> Arb.fromGen
但这变得非常缓慢,显然不是正确的方法。我确信必须有一个我想念的简单解决方案。我尝试使用Gen.choose(Int64.MinValue, Int64.MaxValue)
,但这只支持整数,而不是长期(但即使只是整理,我也无法使其工作)。
最后,我需要一个适用于所有原始数值数据类型的解决方案,包括它们的最大值和分钟数,它们的零和1,以及从内部的任何内容随机选择。
答案 0 :(得分:5)
正如this other FsCheck question中所述,大多数Check
函数的默认配置都为EndSize = 100
。您可以增加该数字,但也可以按照建议使用Gen.choose
。
尽管如此,int
生成器还是intentionally well-behaved。例如,它不包含Int32.MinValue
和Int32.MaxValue
,因为这可能会导致溢出。
FsCheck也提供了生成器,可以在整个范围内为您提供统一的分布:Arb.Default.DoNotSizeInt16
,Arb.Default.DoNotSizeUInt64
,等等。
对于浮点值,有Arb.Default.Float32
,根据其文档,它生成“任意浮点数,NaN,NegativeInfinity,PositiveInfinity,Maxvalue,MinValue,Epsilon包含相当频繁”。
对于'just'任何数字都没有统一的API,因为F#没有类型类(这是你能在Haskell中表达的东西)。
另外,我不确定您的典型单元测试框架是否能够运行通用测试,但至少在xUnit.net中,您可以使用this trick to run generically typed tests。
具体来说,您可以使用FsCheck.Xunit编写上述测试:
open FsCheck
open FsCheck.Xunit
[<Property>]
let ``Multiplication is distributive`` () =
Arb.generate<DoNotSize<int64>>
|> Gen.map (fun (DoNotSize x) -> x)
|> Gen.three
|> Arb.fromGen
|> Prop.forAll <| fun (x, y, z) ->
let res1 = x * (y + z)
let res2 = x * y + x * z
res1 = res2
这可能假设没有溢出,但在运行了大约1,000,000个案例后,我还没有看到它失败。
然而,生成器确实看起来像是从全部64位整数中挑选值:
> Arb.generate<DoNotSize<int64>> |> Gen.sample 1 10;;
val it : DoNotSize<int64> list =
[DoNotSize -28197L; DoNotSize -123346460471168L; DoNotSize -28719L;
DoNotSize -125588489564554L; DoNotSize -29241L;
DoNotSize 7736726437182770284L; DoNotSize -2382327248148602956L;
DoNotSize -554678787L; DoNotSize -1317194353L; DoNotSize -29668L]
请注意,即使我将size
的{{1}}参数绑定到Gen.sample
,它也会选择“任意”大的正负值。