.net 将Foq与F#函数类型一起使用

.net 将Foq与F#函数类型一起使用,.net,unit-testing,f#,mocking,foq,.net,Unit Testing,F#,Mocking,Foq,例如,我使用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 |>

例如,我使用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
type IType1 = 
    abstract member doSomething : int -> int

type func1 () =
    interface IType1 with
        member this.doSomething (i: int) = i * i
产出x:“4”

显然,这是非常人为的和简单的,但是想象一下依赖关系是一个解析器和一个分类器或者其他什么。我正在编写的更小的功能颗粒

我正在尝试使用Foq来帮助我的单元测试夹具。这是我正确使用F#的第一周,我很难找到如何配置这些类型的mock

有两件事值得一提:

1-如果我使用抽象类,我可以让它工作,但我不想这样做,因为对于完全相同的最终结果来说,这会带来更多麻烦。比如说

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
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 @>)

我希望我只是在语法方面做得很蠢,我想做什么就做什么。我尝试了我能想到的每一种变体,包括.Setup(conditions).Create()的变体,但在源代码中找不到任何示例

很明显,我可以很容易地做出自己喜欢的嘲弄

let mT1 (i : int) : int = 5
因为任何符合int->int签名的东西都是有效的,但是如果我想检查函数是否传递了某个特定的值,我必须在日志记录步骤中等等。。如果有Foq来做一些繁重的工作那就太好了

编辑
我刚刚注意到根Mock对象的签名中有“requires reference type”(即Mock),您不必进行模拟。如果您的依赖项只是函数类型,您可以只提供函数:

let mT1= fun x -> 5
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
对象模拟的整个概念是(必须)由面向对象的人发明的,以补偿对象组合不好(或根本不好)的事实。当整个系统正常工作时,您可以当场创建功能。无需模拟

如果您真的很想使用Foq的功能,如日志记录和验证(我敦促您重新考虑:您的测试将变得更容易、更具弹性),那么您可以将自己打造成一个对象,充当您功能的代理主机:

let mT1= fun x -> 5
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
类型ISurrogate=
抽象成员f:'t->'r
//设置
让mT1=Mock.Create()
mT1.设置(…)。。。
让mT2=Mock.Create()
mT2.设置。。。
higherFunc mT1.f mT2.f 42
mT1.Received(1.Call(…)//验证
这样,丑陋只限于您的测试,不会使您的生产代码复杂化

显然,这只适用于单参数函数。对于具有多个curried参数的函数,您必须对参数进行元组化,并在注入站点将调用封装在lambda中:

// Setup
let mT1 = Mock.Create<ISurrogate<int * int, int>>()

higherFunc (fun x y -> mT1.f(x, y)) ...
//设置
让mT1=Mock.Create()
higherFunc(乐趣x y->mT1.f(x,y))。。。
如果您发现这种情况经常发生,可以打包lambda创建以供重用:

let inject (s: ISurrogate<_,_>) x y = s.f (x,y)

higherFunc (inject mT1) ...
让注入(s:ISurrogate)xy=s.f(x,y)
higherFunc(注入mT1)。。。

实际上,我在我的帖子中用最后一个代码示例解决了这个问题。我知道我可以做到这一点,关键是如果我想检查我的函数是否传递了正确的参数,或者调用了一定的次数,我必须写这些东西。这不是世界末日,这是我现在正在做的事情,但如果我想ted to。像我们都提到过的那种简单的赝品很容易,但更丰富的模拟需要更多的努力。是的,我只是在答案中添加了这一点。请查看更新。r.e.编辑-再次,我意识到我可以用抽象类来做这件事,但我正试图坚持使用函数值类型,因为语法更清晰,而且里面没有对象我知道在实现等方面测试行为是个好主意,但是-例如,我有一个函数,它使用内部函数的api键来构建原始字符串,并将其传递给作为依赖项的哈希器。验证哈希器是否传递了有效字符串的唯一方法是a。)模拟它并记录输入或b.)取消嵌套api key builder函数以使其公开事实证明,仔细的重构消除了几乎所有需要创建Foq模拟的情况,而不仅仅是编写一个快速func以传递到测试中。我使用它的一次是检查是否已使用正确的args,因为它节省了我编写日志步骤的时间,但它实际上是微不足道的。最终,当我从我的OO大脑设计出的更大的聚合函数转向更小、更集中的函数时,测试变得更简单,正如Fyodor所建议的那样。@Arwin-你仍然可以按合同进行设计。它甚至比在OO中更容易,因为类型非常容易使用。我发现并修改了一个非常简单的静态IOC容器,以根据其类型注册我的func值(而不是对象)——这里有一个要点,我应该说我知道我不需要注释所有类型(也不需要在我的代码中注释),我只是为了清楚起见在这里这么做
let inject (s: ISurrogate<_,_>) x y = s.f (x,y)

higherFunc (inject mT1) ...