asynchronous - 如何在 FsCheck 中运行异步测试?

标签 asynchronous f# fscheck

如何使用 FsCheck 获得可重复的异步测试?以下是我在 FSI 中运行的示例代码:

let prop_simple() = gen {
    let! s = Arb.generate<string>
    printfn "simple: s = %A" s
    return 0 < 1
}
let prop_async() =
    async {
        let s = Arb.generate<string> |> Gen.sample 10 1 |> List.head
        // let! x = save_to_db s // for example
        printfn "async: s = %A" s
        return 0 < 1
    } 
    |> Async.RunSynchronously

let check_props() = 
    //FC2.FsCheckModifiers.Register()
    let config = 
        { FsCheck.Config.Default with 
            MaxTest = 5
            Replay = Random.StdGen(952012316,296546221) |> Some
        }
    Check.One(config, prop_simple)
    Check.One(config, prop_async)

输出看起来像这样:

simple: s = "VDm2JQs5z"
simple: s = "NVgDf2mQs8zaWELndK"
simple: s = "TWz3Yjl2tHFERyrMTvl0HOqgx"
simple: s = "KRWC92vBdZAHj6qcf"
simple: s = "CTJbQGXzpLBNn0RY6MCvlfUtbQhCUKm9tbXFhLSu0RcYmi"
Ok, passed 5 tests.
async: s = "aOE"
async: s = "y8"
async: s = "y8"
async: s = "q"
async: s = "q"
Ok, passed 5 tests.

另一次运行可能如下所示:

simple: s = "VDm2JQs5z"
simple: s = "NVgDf2mQs8zaWELndK"
simple: s = "TWz3Yjl2tHFERyrMTvl0HOqgx"
simple: s = "KRWC92vBdZAHj6qcf"
simple: s = "CTJbQGXzpLBNn0RY6MCvlfUtbQhCUKm9tbXFhLSu0RcYmi"
Ok, passed 5 tests.
async: s = "g"
async: s = "g"
async: s = "g"
async: s = ""
async: s = ""
Ok, passed 5 tests.

因此 prop_simple() 工作正常且可重复(给定 StdGen(952012316,296546221))。

但是 prop_async() 不可重复,并且似乎会一遍又一遍地生成相同的字符串。

另外,有没有更好的方法来编写prop_async()

最佳答案

FsCheck 的行为实际上与 async 没有任何关系。在这里,而是在 async 内的事实您正在使用Gen.sampleGen.sample为每次调用选择一个新的基于时间的种子 - 因此它在 FsCheck 属性内的行为是不可重现的。换句话说,您不应该在属性中使用它,当您编写新的生成器时,它只是用于探索目的。由于种子是基于时间的,并且您的属性非常小,因此多次调用将使用相同的种子,因此您会看到相同的值。例如,这是一个没有任何 async 的属性具有相同的行为:

let prop_simple2() =         
    let s = Arb.generate<string> |> Gen.sample 10 1 |> List.head
    // let! x = save_to_db s // for example
    printfn "simple2: s = %A" s
    0 < 1

打印例如

simple2: s = "nrP?.PFh^y"
simple2: s = "nrP?.PFh^y"
simple2: s = "nrP?.PFh^y"
simple2: s = "nrP?.PFh^y"
simple2: s = "nrP?.PFh^y"
Ok, passed 5 tests.

现在至于如何写async属性,我会将异步保留在属性内,然后使用 Async.RunSynchronously 解决它。为正常值。作为示例的变体:

let prop_async2 =
    gen {
        let! s = Arb.generate<string>
        // let! x = save_to_db s // for example
        let r = 
            async {
                printfn "async2: s = %A" s
            }
            |> Async.RunSynchronously
        return 0 < 1
    }

具有确定性的输出。 (另请注意,如果您已经创建了 Gen<'T> 实例,则不需要将该属性设为函数。可以,但这仅意味着 FsCheck 将为 unit 类型生成 100 个值(这些值是当然所有 () 实际上是 null ,所以它不会造成伤害,但性能会有所提高。)

你也可以反过来做:

let prop_async3 =
    async {
        let r = gen {
            let! s = Arb.generate<string>
            printfn "async3: s = %A" s
            return 0 < 1
        }
        return r
    }
    |> Async.RunSynchronously

需要注意的一些问题。

  • 顺序异步代码通常不会产生什么问题,但请继续阅读。

  • 异步和并发代码可能会遇到像 munn 在评论中所说的问题,即多个线程/任务使用相同的值。重现性也会受到影响。您也许可以仔细编写属性代码,这样就不会遇到这种情况(例如,通过在属性中添加前奏,首先以顺序方式生成所有必要的值,然后启动异步函数),但这需要一些工作和思考。

  • 如果您覆盖 Arbitrary使用 Arb.register 的实例它们将以线程本地方式被覆盖;即它们不会传播到异步 Task 序列s。我的建议是不要这样做。已注册Arbitrary实例本质上是可变的静态状态,通常在并发性方面表现不佳。

综合起来我认为 async属性绝对是可能的,但在 v2 中这绝对是一场艰苦的战斗。 FsCheck 3(目前处于 alpha 版本)直接支持异步和多线程执行。

关于asynchronous - 如何在 FsCheck 中运行异步测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54270260/

相关文章:

javascript - 如何停止 Node.js/Javascript 中的无限循环

python - 使用 Popen 的异步子进程

asp.net-mvc - 使用 MVC Futures 的异步 Controller 在 .NET 3.5 SP1 中的 ASP.NET MVC 1 中长轮询的服务器和连接限制

asynchronous - 具有异步操作的面向铁路的编程

f# - 如何使用 FsCheck 生成器生成两条相同类型的记录,其中一条记录的属性与另一条记录不同

javascript - 将回调传递给异步函数甚至再次包装它是否正常?

list - 在 F# 中,将类型化列表向上转换为 seq<_> 的最简单方法是什么?

f# - Array2D到Array

.net - 如何让 FsCheck 生成尊重 MaxLengthAttribute 的随机字符串?

c# - 使用 C# 使用 FsCheck 生成随机字符串