代码之家  ›  专栏  ›  技术社区  ›  Ray

如何在fscheck中运行异步测试?

  •  2
  • Ray  · 技术社区  · 7 年前

    如何使用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() 不是可重复的,似乎反复生成相同的字符串。

    还有,还有更好的写作方法吗? PoPixAsiNyc() ?

    1 回复  |  直到 7 年前
        1
  •  2
  •   Kurt Schelfthout    7 年前

    fscheck的行为与 async 在这里,但事实上 异步的 你在用 Gen.sample . 基因样本 为每次调用选择一个新的基于时间的种子,因此它在fscheck属性中的行为是不可复制的。换句话说,你不应该在一个属性中使用它,当你写一个新的生成器时,它只是为了探索的目的。由于种子是基于时间的,并且您的属性非常小,因此多个调用将使用相同的种子,因此您可以看到相同的值。例如,这里有一个没有任何 异步的 具有相同行为:

    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.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 类型(这些值当然都是 () 这是有效的 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 我的建议是不要那样做。注册的 任意的 实例本质上是可变的静态状态,在并发性中通常不会表现得那么好。

    我想合二为一 异步的 属性是绝对可能的,但它绝对是第2版的一场艰苦战斗。fscheck 3(当前在alpha中)直接支持异步和多线程执行。