我有一个生成随机数的函数。在验收测试时,我想用生成已知数字的方法替换它。
在面向对象的语言中,我会使用依赖注入并在测试设置中简单地连接这个组件。我能看到在函数式语言中执行此操作的唯一方法是通过程序从根传递依赖项,直到它到达需要它的函数(“海龟一路向下”是正确的短语吗?) .
有没有更好的办法?
我有一个生成随机数的函数。在验收测试时,我想用生成已知数字的方法替换它。
在面向对象的语言中,我会使用依赖注入并在测试设置中简单地连接这个组件。我能看到在函数式语言中执行此操作的唯一方法是通过程序从根传递依赖项,直到它到达需要它的函数(“海龟一路向下”是正确的短语吗?) .
有没有更好的办法?
有很多选项——你可以传递函数,你可以使用依赖注入机制(许多标准的 .NET 库可以在 F# 中工作,但你也可以找到一些特定于 F# 的轻量级工具),或者你甚至可以使用 monads隐式传递状态。
但是,您是否需要在运行时在生产设置和测试设置之间做出选择?如果不是,您可以将要以不同方式实现以进行测试的功能移动到单独的文件中,并拥有两个不同的项目版本。
这可能不是纯粹的解决方案,但它非常简单(只需使用Random.Test.fs
代替Random.Runtime.fs
)并且功能很好 - 您只需提供单个(或多个)功能的不同实现,其余代码将使用它们。
使用纯函数式语言的唯一方法是将状态贯穿始终。F# 是不纯的,因此您可以按照在 OO 中执行的典型方式来执行此操作。但我不清楚你是在问语用学还是功能设计如何解决这个问题。
大概您有以下内容:
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
。
所以这里我们的解决方案是“传入依赖”,但它不必一直从根开始。