Dependency injection F中验收测试的模拟功能#

Dependency injection F中验收测试的模拟功能#,dependency-injection,f#,functional-programming,mocking,Dependency Injection,F#,Functional Programming,Mocking,我有一个生成随机数的函数。在进行验收测试时,我想用一个生成已知数字的代码来替换它 在面向对象语言中,我将使用依赖项注入,并在测试设置中以不同的方式连接该组件。在函数式语言中,我能看到的唯一方法是通过程序传递依赖关系,从根开始,直到它到达需要它的函数(这里的“turtles一路向下”是正确的短语吗?) 有更好的方法吗?在纯函数式语言中实现这一点的唯一方法是将状态贯穿始终。F#是不纯的,所以你可以用OO中的典型方式来做。但是我不清楚你是在问语用学还是功能设计如何解决这个问题。有很多选择-你可以传递函

我有一个生成随机数的函数。在进行验收测试时,我想用一个生成已知数字的代码来替换它

在面向对象语言中,我将使用依赖项注入,并在测试设置中以不同的方式连接该组件。在函数式语言中,我能看到的唯一方法是通过程序传递依赖关系,从根开始,直到它到达需要它的函数(这里的“turtles一路向下”是正确的短语吗?)


有更好的方法吗?

在纯函数式语言中实现这一点的唯一方法是将状态贯穿始终。F#是不纯的,所以你可以用OO中的典型方式来做。但是我不清楚你是在问语用学还是功能设计如何解决这个问题。

有很多选择-你可以传递函数,你可以使用依赖注入机制(许多标准的.NET库都可以在F中工作,但你也可以找到一些F专用的轻量级工具)或者您甚至可以使用monad隐式地传递状态

但是,您是否需要在运行时在生产设置和测试设置之间做出选择?如果没有,您可以将要以不同方式实现以进行测试的功能移动到一个单独的文件中,并拥有两个不同的项目构建


这可能不是纯粹的解决方案,但它非常简单(只需使用
Random.Test.fs
而不是
Random.Runtime.fs
),而且功能很好-您只需提供单个(或多个)函数的不同实现,其余的代码就可以使用它们。

大概您有以下类似的功能:

let generateRandomNumber =
    let generator = new System.Random()
    fun () -> generator.Next()

let addFiveToRandomNumber() =
    let next = generateRandomNumber()
    next + 5
其中,
addFiveToRandomNumber
是您要测试的函数

解决您的问题的一个好办法,也是F#中的一个好做法,就是将纯函数和非纯函数分开:

let generateRandomNumber =
    let generator = new System.Random()
    fun () -> generator.Next()

let addFiveToGeneratedNumber generator =
    let next = generator()
    next + 5

let addFiveToRandomNumber() = addFiveToGeneratedNumber generateRandomNumber
现在您要测试的函数是
addFiveToGeneratedNumber
,例如
addFiveToGeneratedNumber(fun()->1)
应始终返回6。我甚至可能不会费心去测试
generateRandomNumber
addFiveToRandomNumber


因此,这里我们的解决方案是“传递依赖项”,但它不一定要从根开始。

我也会想到最后一个问题。F#在你称之为“未结支票”时做了类似的事情。它只是用一个新的(+)操作符来超越(例如)范围。您可以在不更改测试代码的情况下对随机数生成器执行类似操作。@mwilson没错-使用不同的
打开
(可能在
#if
)将是另一种方法。我认为这在F#中更有意义,因为程序倾向于使用单独模块(不同文件中)中的函数进行结构化,因此您可以引用不同的模块或文件。在C语言中,在功能更为紧密的情况下,同样困难。我已经标记为答案,但我仍然认为这些选项是一种黑客攻击。如果我想要纯函数式语言的运行时灵活性,那么从输入传递函数是唯一的方法吗?函数式语言中的组合模式有什么好的资源吗?在单元测试中这很好,但我这里说的是验收测试。