如果随机生成的输入没有用,我如何重新尝试基于属性的测试?

Posted

技术标签:

【中文标题】如果随机生成的输入没有用,我如何重新尝试基于属性的测试?【英文标题】:How can I re-try a property-based-test if the randomly-generated inputs are not useful? 【发布时间】:2016-04-11 22:28:05 【问题描述】:

我是单元测试的 n00b。我已经从 Nuget 安装了 FsCheck.NunitNUnitTestAdapter,并且我正在尝试进行基于属性的测试,很大程度上受到 the inestimable Scott Wlaschin 的启发。

我正在使用[<Property>] 属性,我希望能够“跳过”不符合测试要求的输入:

[<Property(MaxTest=10)>]
let ``Calling unzipTo with an invalid destination will yield a failure.`` badDest =
    if Directory.Exists(badDest)
    then // somehow skip to the next randomized input
    else // do the actual test

最简单的方法是什么?

如果存在 FsCheck/NUnit,我更喜欢它的答案,但我也会考虑任何其他可以在 Visual Studio 中运行测试的框架。 (我以为我看到了一些框架,其中有一个简单的函数可以做到这一点,但我不知道它是什么。)

到目前为止,我更喜欢 FsCheck.NUnit,因为它可以为 F# 类型(可区分联合等)生成随机输入,而无需额外工作。

【问题讨论】:

您需要一个自定义生成器。 IIRC 对于 then 语句,您只需返回表明测试成功的结果而不运行测试。像true 这样简单的东西可能会起作用。几年前我遇到了这个问题,问题是过度思考问题。请记住,测试只是一个函数,测试驱动程序只想要一个指示成功或失败的结果,所以给它一个成功。 @Guy Coder 问题在于,如果生成的随机输入没有用,我希望使用不同的随机输入重新运行测试——我不希望函数在不合适的情况下运行多次如果它从未实际执行过测试,则输入并声明自己成功。 【参考方案1】:

你应该可以做这样的事情:

open FsCheck
open FsCheck.Xunit

[<Property(MaxTest=10)>]
let ``Calling unzipTo with an invalid destination will yield a failure.`` badDest =
    (not Directory.Exists(badDest)) ==> lazy
    // do the actual test

第一行是一个布尔条件,==&gt;FsCheck 模块定义的自定义运算符。如果右侧的条件计算为 true,它只会强制计算惰性表达式。

不过,请考虑重构此测试,使其不依赖于文件系统。文件系统是持久化的,所以会自动创建一个持久化Fixture,即a lot of trouble to manage;并非不可能处理,但最好避免。

此示例使用 FsCheck.Xunit,但 IIRC FsCheck.Nunit 的工作方式相同。不过,您应该认真考虑使用 FsCheck.Xunit 而不是 FsCheck.Nunit。 NUnit 2 的可扩展性模型非常差,这意味着大多数试图扩展 NUnit 的 Glue 库都会遇到很多问题。这不是 FsCheck.Nunit 的问题,而是 NUnit 本身的问题,但它会给你带来很多麻烦。

【讨论】:

太棒了,这正是我想要的。我可能一开始在你的博客上看到了它。也感谢关于 NUnit 与 Xunit 的提示。 通常我会尝试使测试独立于文件系统、数据库状态等,但是对于我在这里开发的功能,操作文件系统是它们的主要目的。我还能如何测试它们? @OverlordZurg 也许这个答案回答了这个问题:codereview.stackexchange.com/a/99290/3878 ... 或者它没有:)【参考方案2】:

FsCheck.Prop.discard() 似乎在做我想做的事——当我使用日志记录运行测试时,我可以看到一些尝试被丢弃了,但有 10 次运行完成而没有被丢弃。

==&gt; 运算符用于运行带有FsCheck.Quick 或类似的测试。但是,这需要惰性部分采用“可测试”的格式,我目前正在编写的测试只是&lt;inputs&gt;-&gt;unit

【讨论】:

“要求惰性部分的格式为'Testable” 这应该不是问题。编译器将为您推断适当的类型。有关示例,请参见此处:blog.ploeh.dk/2015/09/08/ad-hoc-arbitraries-with-fscheckxunit 在你的then 分支中调用discard,或者使用==&gt; 的方法应该是完全等价的。尽管==&gt; 是首选方法 - 它在 imo 中读取效果更好,而discard 通过在后台抛出和捕获异常来工作,因此可能会很慢并且对调试有破坏性。 发现问题 - 我试图返回单位,但我需要返回 bool。不幸的是,从惰性部分(而不是 Assert.AreEqual())返回 bool 会使我失去明确说明预期值与实际值的良好格式。我正在继续探索选项,但至少我现在明白了:) wrt 我之前的评论,想到(最终)我可以调用 Assert.Whatever(),如果不满足会抛出异常,然后在最后返回“true”以实现相同的结果(对错误有用的格式化)。【参考方案3】:

我对 F# 或 fscheck 不是很熟悉,但是 NUnit 提供了 Assert.Ignore() 函数,它会立即停止测试并将其标记为“忽略”。如果您认为这些状态更合适,您也可以使用Assert.Inconclusive()Assert.Pass()

【讨论】:

我尝试过“Assert.Ignore()”,但这会导致标有 [] 属性的测试失败,这不是我想要的行为。

以上是关于如果随机生成的输入没有用,我如何重新尝试基于属性的测试?的主要内容,如果未能解决你的问题,请参考以下文章

随机生成4位验证码,由用户输入并验证是否输入正确,如果输入错误就生成新的验证码让用户重新输入,最多输入5次

随机生成4位验证码,由用户输入并验证是否输入正确,如果输入错误就生成新的验证码让用户重新输入,最多输入5次

如何生成随机数并将其存储在随机数量的可重新调用的变量中? [重复]

伪随机目录树生成?

如何使用jquery从DOM中删除随机生成的属性?

如何让C#产生不重复的随机数