f# - FsCheck 与安装和拆卸
问题描述
概括
是否有任何事件可以在每个属性案例之前运行,以便我可以为属性的每次运行运行设置和拆卸?
完整版本
我希望能够通过属性测试成对的行为,例如“我总是可以获取书面记录”或“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
我调查过
跑步者活动查看如何运行 FsCheck 测试最接近的钩子似乎是
OnStartFixture
每个测试类只运行一次OnArguments
在每次通过后运行,并且可能会运行清理
还有一些实验性的基于模型的测试功能可以工作。但是,考虑到我只关心操作的外部一致性,这似乎真的很重。我不想访问支持状态。
放弃和内联我总能写
testProperty "name" (fun arg1 arg2 ->
let (api,env) = setup ()
//test code here
cleanup env
)
但我想避免每个属性中的样板和支持状态的暴露。
一次性用品一次性对象也没有解决缺少设置挂钩的问题。
更多动手操作的运行循环我研究了在包装器中运行属性测试的方法,但最小的运行器Check.one
是针对单个属性的,在属性的运行之间没有挂钩。
使包装器变得懒惰也不起作用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
并且只需要一个操作符<+>
。可能还有其他方法可以切断它,选择你的毒药。
推荐阅读
- django - 如何使用 Django 3 发送“Access-Control-Allow-Origin”标头?
- mysql - MySql 中将 ID 列加一的命令
- excel - Excel 或 Google 工作表单元格值在不同的单元格中创建
- azure-artifacts - 通过 Azure API 创建 Azure Artifacts 通用包源 - 示例
- html - 两个 div 水平居中放置在包装 div 中
- python - 两个版本的python安装在两个地方
- javascript - 如何在不提供父名称的情况下访问 firebase 中相同命名的子元素
- c++ - 从向量转换
to char* 包含垃圾数据 - csvhelper - CsvHelper 使用 ClassMap 格式化十进制
- c - 当我认为它应该在 linux 上时,c fputc 不会返回错误,在 windows 上按预期工作