将 Foq 与 F# 函数类型结合使用
Using Foq with F# function types
我正在使用 F# 类型定义来防止函数之间的硬依赖,例如
type IType1 = int -> int
type IType2 = int-> string
let func1 (i : int) : int = i * i
let func2 (i : int) : string = i |> string
let higherFunc (dep1 : IType1) (dep2 : IType2) (input : int) : string =
input |> dep1 |> dep2
let curriedFunc = higherFunc func1 func2
let x = curriedFunc 2
输出 x:“4”
显然,这是非常人为和简单的,但可以想象依赖项是解析器和排序器或其他任何东西。我正在编写的功能更小。
我正在尝试使用 Foq 来帮助我进行单元测试。这是我第一周正确使用 F#,我很难弄清楚如何配置这些类型的模拟。
有两点值得一提:
1 - 如果我使用抽象 类,我可以使它工作,但我不想这样做,因为对于完全相同的最终结果来说,它要麻烦得多。例如
type IType1 =
abstract member doSomething : int -> int
type func1 () =
interface IType1 with
member this.doSomething (i: int) = i * i
允许我像
一样设置一个模拟
let mT1= Mock.With (fun (x : IType1) -> <@ x.doSomething(any()) --> 5 @>)
但我真的不想这样做。
2 - 如果我只使用
type IType1 = int -> int
let mT1 = Mock.Of<IType1>()
然后我得到一个有效值,但如果我尝试以任何方式配置它,比如
let mT1= Mock<IType1>.With (fun x -> <@ x(any()) --> 5 @>)
或
let mT1= Mock<IType1>.With (fun x -> <@ any() --> 5@>)
然后我得到一个例外
System.NotSupportedException : Expected standard function application: Call
或
System.NotSupportedException : Expected standard function application: ValueWithName
我希望我只是对语法很愚蠢,并且可以做我想做的事。我已经尝试了所有我能想到的变体,包括 .Setup(conditions).Create() 的变体,但我在源代码中找不到任何示例。
我显然可以很容易地制作我自己的模拟像
let mT1 (i : int) : int = 5
因为任何符合 int -> int 签名的东西都是有效的,但是如果我想检查函数是否传递了某个值,我必须输入日志记录步骤等等。它会让 Foq 来做一些繁重的工作真是太好了。
编辑
我刚刚注意到根 Mock 对象的签名中有 'requires reference type' (即 Mock<'TAbstract(requires reference type)> )——这是否意味着我没有机会模拟值?我不配置 mock 怎么会管得了?
你不必嘲笑。如果你的依赖只是函数类型,你可以只提供函数:
let mT1= fun x -> 5
对象模拟的整个概念是(必须)由 object-oriented 人发明的,以补偿对象组合不好(或根本没有)的事实。当您的整个系统正常运行时,您可以在现场创建功能。无需嘲笑。
如果你真的对使用 Foq 的工具如日志记录和验证很感兴趣(我敦促你重新考虑:你的测试会变得更容易和更有弹性),你总是可以让自己成为一个对象,作为您的功能的代理主机:
type ISurrogate<'t, 'r> =
abstract member f: 't -> 'r
// Setup
let mT1 = Mock.Create<ISurrogate<int, int>>()
mT1.Setup(...)...
let mT2 = Mock.Create<ISurrogate<int, string>>()
mT2.Setup...
higherFunc mT1.f mT2.f 42
mT1.Received(1).Call( ... ) // Verification
这样,丑陋的东西就只限于您的测试,不会使您的生产代码复杂化。
显然,这仅适用于 single-argument 函数。对于具有多个 curried 参数的函数,您必须将参数元组化并将调用包装在注入站点的 lambda 中:
// Setup
let mT1 = Mock.Create<ISurrogate<int * int, int>>()
higherFunc (fun x y -> mT1.f(x, y)) ...
如果您发现这种情况经常发生,您可以打包 lambda 创建以供重用:
let inject (s: ISurrogate<_,_>) x y = s.f (x,y)
higherFunc (inject mT1) ...
我正在使用 F# 类型定义来防止函数之间的硬依赖,例如
type IType1 = int -> int
type IType2 = int-> string
let func1 (i : int) : int = i * i
let func2 (i : int) : string = i |> string
let higherFunc (dep1 : IType1) (dep2 : IType2) (input : int) : string =
input |> dep1 |> dep2
let curriedFunc = higherFunc func1 func2
let x = curriedFunc 2
输出 x:“4”
显然,这是非常人为和简单的,但可以想象依赖项是解析器和排序器或其他任何东西。我正在编写的功能更小。
我正在尝试使用 Foq 来帮助我进行单元测试。这是我第一周正确使用 F#,我很难弄清楚如何配置这些类型的模拟。
有两点值得一提:
1 - 如果我使用抽象 类,我可以使它工作,但我不想这样做,因为对于完全相同的最终结果来说,它要麻烦得多。例如
type IType1 =
abstract member doSomething : int -> int
type func1 () =
interface IType1 with
member this.doSomething (i: int) = i * i
允许我像
一样设置一个模拟let mT1= Mock.With (fun (x : IType1) -> <@ x.doSomething(any()) --> 5 @>)
但我真的不想这样做。
2 - 如果我只使用
type IType1 = int -> int
let mT1 = Mock.Of<IType1>()
然后我得到一个有效值,但如果我尝试以任何方式配置它,比如
let mT1= Mock<IType1>.With (fun x -> <@ x(any()) --> 5 @>)
或
let mT1= Mock<IType1>.With (fun x -> <@ any() --> 5@>)
然后我得到一个例外
System.NotSupportedException : Expected standard function application: Call
或
System.NotSupportedException : Expected standard function application: ValueWithName
我希望我只是对语法很愚蠢,并且可以做我想做的事。我已经尝试了所有我能想到的变体,包括 .Setup(conditions).Create() 的变体,但我在源代码中找不到任何示例。
我显然可以很容易地制作我自己的模拟像
let mT1 (i : int) : int = 5
因为任何符合 int -> int 签名的东西都是有效的,但是如果我想检查函数是否传递了某个值,我必须输入日志记录步骤等等。它会让 Foq 来做一些繁重的工作真是太好了。
编辑 我刚刚注意到根 Mock 对象的签名中有 'requires reference type' (即 Mock<'TAbstract(requires reference type)> )——这是否意味着我没有机会模拟值?我不配置 mock 怎么会管得了?
你不必嘲笑。如果你的依赖只是函数类型,你可以只提供函数:
let mT1= fun x -> 5
对象模拟的整个概念是(必须)由 object-oriented 人发明的,以补偿对象组合不好(或根本没有)的事实。当您的整个系统正常运行时,您可以在现场创建功能。无需嘲笑。
如果你真的对使用 Foq 的工具如日志记录和验证很感兴趣(我敦促你重新考虑:你的测试会变得更容易和更有弹性),你总是可以让自己成为一个对象,作为您的功能的代理主机:
type ISurrogate<'t, 'r> =
abstract member f: 't -> 'r
// Setup
let mT1 = Mock.Create<ISurrogate<int, int>>()
mT1.Setup(...)...
let mT2 = Mock.Create<ISurrogate<int, string>>()
mT2.Setup...
higherFunc mT1.f mT2.f 42
mT1.Received(1).Call( ... ) // Verification
这样,丑陋的东西就只限于您的测试,不会使您的生产代码复杂化。
显然,这仅适用于 single-argument 函数。对于具有多个 curried 参数的函数,您必须将参数元组化并将调用包装在注入站点的 lambda 中:
// Setup
let mT1 = Mock.Create<ISurrogate<int * int, int>>()
higherFunc (fun x y -> mT1.f(x, y)) ...
如果您发现这种情况经常发生,您可以打包 lambda 创建以供重用:
let inject (s: ISurrogate<_,_>) x y = s.f (x,y)
higherFunc (inject mT1) ...