我很难尝试使用外部依赖项对F#代码进行单元测试。
在C#(我的背景)中,你通常会有一个传入依赖关系的类,然后重新使用它。对我的示例代码道歉,它很愚蠢,但我只想说明我的观点。
public class Foo {
IDependency d;
public Foo(IDependency d) { this.d = d; }
public int DoStuff(string bar) { return d.DoSomethingToStuff(bar); }
public int DoMoreStuff(string bar) {
int i = d.DoSomethingToStuff(bar);
return d.DoSomethingElseToStuff(bar, i);
}
}
我试图使用F#务实,避免使用类和接口(除非我需要与其他.NET语言互操作)。
所以我在这种情况下的方法是将模块和一些函数与依赖项作为函数传入。我找到了这个技术here
module Foo
let doStuff bar somethingFunc =
somethingFunc bar
let doMoreStuff bar somethingFunc somethingElseFunc =
let i = somethingFunc bar
somethingElseFunc bar i
我对此代码的两个问题是:
我需要继续传递我的依赖关系。在C#中,它在构造函数中传递并重新使用。如果在多个地方使用somethingFunc
,您可以想象这会失控的速度。
如何对依赖项执行进行单元测试?再次在C#中,我使用了一个模拟框架并断言某些方法被调用。
如何在F#世界中解决这些问题?
答案 0 :(得分:8)
它并不太难mapping SOLID concepts like Dependency Injection to Functional-style F# - 其中一个关键是要意识到strong relationship between objects and closures。
在目前的情况下,有助于重新排序函数参数,以便“依赖”'先走:
module Foo =
let doStuff somethingFunc bar =
somethingFunc bar
let doMoreStuff somethingFunc somethingElseFunc bar =
let i = somethingFunc bar
somethingElseFunc bar i
这将使您能够使用部分功能应用 撰写功能:
let doStuff' = Foo.doStuff somethingImp
现在,doStuff'
是闭包,因为它会关闭具体函数somethingImp
。本质上,它捕获依赖项,因此它就像具有注入依赖项的对象一样工作,您仍然可以使用剩余的bar
参数调用它:
let bar = 42
let actual = doStuff' bar
<强>测试强>
以下是将本地函数用作存根的示例:
module Tests =
let ``Data flows correctly through doMoreStuff`` () =
let somethingFunc bar =
assert (bar = 42)
1337
let somethingElseFunc bar i =
assert (bar = 42)
assert (i = 1337)
"Success"
let actual = Foo.doMoreStuff somethingFunc somethingElseFunc 42
assert (actual = "Success")
这里,为了简单起见,我使用了assert keyword,但是为了进行正确的测试,你应该定义一个正确的断言函数,或者使用你最喜欢的断言库。
通常情况下,我倾向于放松对输入参数的验证,因为它可能会使Test Doubles与特定实现紧密耦合。另外,请记住你应该use Stubs for Queries, and Mocks for Commands - 在这个例子中,只有查询,所以所有测试双打都是存根:虽然他们确实验证输入如果他们被调用,测试并不验证它们是否被调用。