F# 函数式程序员如何测试返回单元的函数?
函数式程序员如何测试返回单元的函数? 在我的例子中,我认为我需要对该函数的接口进行单元测试:F# 函数式程序员如何测试返回单元的函数?,f#,F#,函数式程序员如何测试返回单元的函数? 在我的例子中,我认为我需要对该函数的接口进行单元测试: let logToFile (filePath:string) (formatf : 'data -> string) data = use file = new System.IO.StreamWriter(filePath) file.WriteLine(formatf data) data 使用I/O对函数进行单元测试时,建议采用什么方法? 在面向对象编程中,我相信
let logToFile (filePath:string) (formatf : 'data -> string) data =
use file = new System.IO.StreamWriter(filePath)
file.WriteLine(formatf data)
data
使用I/O对函数进行单元测试时,建议采用什么方法?
在面向对象编程中,我相信可以利用
测试间谍模式是否转化为函数式编程?
我的客户看起来像这样:
[<Test>]
let ``log purchase``() =
[OneDollarBill] |> select Pepsi
|> logToFile "myFile.txt" (sprintf "%A")
|> should equal ??? // IDK
显然,您可以像在命令式代码中一样使用模拟,只要代码单元将其依赖项作为参数 但是,从另一个角度来看,我发现这个演讲非常有趣。就我所记得的,一般的观点是,您应该避免使用mock,让所有函数尽可能保持纯净,使它们成为数据输入数据输出。在你的程序的边缘,你会有一些非常简单的功能来执行重要的副作用。它们非常简单,甚至不需要测试 您提供的函数非常简单,可以归入该类别。用模拟或类似的方法测试它只需要确保调用了某些方法,而不是发生了副作用。这样的测试没有意义,也不会给代码本身增加任何价值,同时还会增加维护负担。最好使用集成测试或端到端测试来测试副作用部分,该测试实际查看编写的文件
关于这个主题的另一个很好的讨论是讨论了功能核心、命令外壳的概念。不要把这看作是一个权威性的答案,因为我不是测试专家,但我对这个问题的回答是,在一个完美的世界中,您不能也不需要测试返回函数的
单元
理想情况下,您可以对代码进行结构化,使其由读取数据的一些IO、编码所有逻辑的转换和保存数据的一些IO组成:
read
|> someLogic
|> someMoreLogic
|> write
其思想是,您所有的重要内容都在someLogic
和someMoreLogic
中,read
和write
是非常简单的,它们将文件作为字符串或行序列读取。这非常简单,您不需要测试它(现在,您可以通过再次读取文件来测试实际的文件写入,但此时您需要测试文件IO,而不是您编写的任何逻辑)
这是在OO中使用模拟的地方,但由于您有一个很好的功能结构,现在可以编写:
testData
|> someLogic
|> someMoreLogic
|> shouldEqual expectedResult
现在,在现实中,这个世界并不总是那么美好,像spy
这样的操作最终会很有用——也许是因为你正在与一个不完全实用的世界进行交互操作
Phil Trelford有一个可以让你记录对函数的调用,并检查它是否被预期的输入调用了——这是我多次发现的有用的东西(它非常简单,你不需要一个框架)。这与我自己写的答案非常接近。在单元测试中,我们通常使用,这是一种已经耗尽了逻辑的实现代码,您不需要对其进行测试-这就是您放置I/O的地方。我也会在F#和Haskell中这样做。如果您无法耗尽有效的逻辑功能,返回单元的一个功能替代方案是使用State或Writer monad。函数式编程风格的函数没有副作用。因为调用单元返回函数而不是忽略函数的唯一可能的原因是为了产生副作用,所以问什么样的函数方式来测试一种不符合函数风格的函数是没有意义的。@JoelMueller如果你仔细想想,Haskell也一样,没有副作用,效果在类型系统中是显式的。仍然没有直接的方法来测试它们,建议仍然是将IO限制在代码的一小部分。也许IO monad最有用的一个方面就是,如果您的所有函数都在IO
中结束,这表明您可能做错了什么!
testData
|> someLogic
|> someMoreLogic
|> shouldEqual expectedResult