gpt4 book ai didi

f# - FsCheck 设置和拆卸

转载 作者:行者123 更新时间:2023-12-04 03:44:26 25 4
gpt4 key购买 nike

总结

是否有任何事件可以在每个属性案例之前运行,以便我可以为属性的每次运行运行设置和拆卸?

完整版

我希望能够使用属性测试配对行为,例如“我总是可以获取书面记录”或“readAllLines 的输出等于 writeAllLines 的输入”。我还希望该属性不关心操作集的实现方式(即是否需要清理任何资源)。

属性的每次运行都应该

  • 独立于其他运行
  • 在单次运行中维护操作调用之间的状态
  • 不知道操作如何保持状态
  • 不是资源泄漏

我正在使用 FsCheck 和 Expecto。示例将在 Expecto 中,但问题并非特定于框架。

使用基于示例的测试编写此类设置和拆卸非常容易。它们采用可预测的参数集,因此我可以在添加事件前后的包装器中运行它们。

let testWithEnv setup cleanup name test = 
let testWrap () =
let (api, env) = setup ()
test api
cleanup env
testCase name testWrap

属性测试无法做到这一点。他们有未知数量的参数,其中大部分将填充随机数据。

我可以很容易地应用这组成对的行为,但任何创建的资源(如流)都不会被处置。

let testPropertyWithEnv setup cleanup name test = 
let testWrap () =
let (api, env) = setup () // this is actually run once, but immutable so the individual runs don't leak state
test api // have to return this to pass along unapplied parameters
testProperty name testWrap

我调查过

运行者事件

查看how to run FsCheck tests最近的钩子(Hook)似乎是

  • OnStartFixture 每个测试类只运行一次
  • OnArguments 在每次通过后运行,并可能用于运行清理
基于模型的特征

还有实验Model-based testing features那可以工作。但是,考虑到我只关心操作的外部一致性,这似乎真的很重。我不想访问支持状态。

放弃和内联

我总能写

testProperty "name" (fun arg1 arg2 ->
let (api,env) = setup ()
//test code here
cleanup env
)

但我想避免每个属性中的样板文件和支持状态的暴露。

IDisposables

一次性对象也没有解决缺少设置 Hook 的问题。

更多实际运行循环

我研究了在我的包装器中运行属性测试的方法,但最小的运行器 Check.one 是针对单个属性的,在属性运行之间没有 Hook 。

惰性包装器

使包装器变得懒惰也不起作用 testProperty name lazy(testWithSetup)

最佳答案

FsCheck 中实际上没有任何可以帮助您的东西,即使有,我也不认为它会直接暴露在 Expecto 中。我也不认为在 FsCheck 方面添加是那么简单 - 如果您在 FsCheck 存储库中提出问题,我很乐意进一步讨论。

无论如何,通过巧妙地使用部分应用程序并以一些轻微的样板为代价,实际上可以包装“可变参数”函数,我认为这本质上就是您要问的。

看,代码:


// these types are here to make the signatures look nicer
type Api = Api
type Env = Env

let testProperty k =
// call the property with "random" arguments
for i in 0..2 do
k i (char i) (string i)

let setup() =
printfn "setup ran"
(Api, Env)

let teardown Env =
printfn "teardown ran"

let test0 Api arg1 =
printfn "test0 %A" arg1

let test1 Api (arg1:int) (arg2:char) =
printfn "test1 %A %A" arg1 arg2

let test2 Api arg1 arg2 arg3 =
printfn "testFun %A %A %A" arg1 arg2 arg3

let testWithEnv (setup:unit -> Api*Env) (teardown: Env -> unit) (test: Api -> 'a) (k: 'a -> unit) :unit =
let (api, env) = setup()
k (test api)
teardown env

let (<*>) (f,k) arg =
f, (fun c -> k c arg)

let (<!>) f arg =
f, (fun k -> k arg)

let run (f, k) = f k

testProperty (fun arg1 arg2 arg3 ->
testWithEnv setup teardown test2 <!> arg1 <*> arg2 <*> arg3 |> run
)

这里的想法是您使用运算符 <!><*>将任意数量的任意类型的参数串在一起,将其传递给 testWithEnv函数然后调用run在结果上。运算符和 run基本上是建立然后应用可变参数列表所必需的。

它都是类型安全的,即如果您忘记传递一个参数或者它的类型错误,您将得到一个类型错误,尽管它可能不像正常的函数应用那样清晰。

我建议将其粘贴到 IDE 中并检查类型,这将极大地帮助理解正在发生的事情。

您可以使用其他几种样式来编写此代码。例如定义略有不同,并且是 the函数你可以写类似的东西

let (<+>) k arg  =
fun c -> k c arg

let the arg =
fun k -> k arg

testWithEnv setup teardown test2 (the arg1 <+> arg2 <+> arg3)

这取代了 run函数 the只需要一名运算符(operator) <+> .可能还有其他方法可以削减它,选择你的毒药。

关于f# - FsCheck 设置和拆卸,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65401727/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com