如何在具有外部依赖项的f#中测试函数 [英] How to test functions in f# with external dependencies
问题描述
我很难尝试对具有外部依赖项的F#代码进行单元测试.
I am having a hard time trying to unit test F# code with external dependencies.
在C#(我的背景)中,您通常会拥有一个传入了依赖项的类,然后可以重新使用它.抱歉,我的示例代码很愚蠢,但我只是想说明我的观点.
In C# (my background) you would typically have a class with a dependency passed in, which is then re-used. Apologies for my sample code, it's dumb but I'm just trying to illustrate my point.
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语言互操作).
I'm trying to be pragmatic with F# and avoid using classes and interfaces (unless I need to interop with other .NET languages).
因此,在这种情况下,我的方法是让模块和某些函数的依赖项作为函数传递.我在此处
So my approach in this scenario is to have module and some functions with the dependencies passed in as functions. I found this tecnique here
module Foo
let doStuff bar somethingFunc =
somethingFunc bar
let doMoreStuff bar somethingFunc somethingElseFunc =
let i = somethingFunc bar
somethingElseFunc bar i
此代码存在两个问题:
-
我需要不断传递我的依赖关系.在C#中,它在构造函数中传递并重新使用.您可以想象如果在多个地方使用
somethingFunc
会很快失去控制.
如何对已执行依赖项进行单元测试?再次在C#中,我将使用模拟框架并断言某些方法已被调用.
How do I unit test that dependencies have been executed? Again in C# I'd use a mocking framework and assert that certain methods were called.
在F#世界中如何解决这些问题?
How do I approach these problems in the F# world?
推荐答案
将诸如依赖项注入等SOLID概念映射到Functional-风格F#-关键之一就是要意识到对象与闭包之间的牢固关系.
It's not too difficult mapping SOLID concepts like Dependency Injection to Functional-style F# - one of the keys is to realize that there's a strong relationship between objects and closures.
在当前情况下,将有助于对函数参数进行重新排序,以使依赖项"首先出现:
In the present case, it would help to reorder the function arguments so that the 'dependencies' go first:
module Foo =
let doStuff somethingFunc bar =
somethingFunc bar
let doMoreStuff somethingFunc somethingElseFunc bar =
let i = somethingFunc bar
somethingElseFunc bar i
这将使您能够使用部分功能应用程序 组成功能:
This will enable you to compose functions using partial function application:
let doStuff' = Foo.doStuff somethingImp
现在,doStuff'
是一个 closure ,因为它关闭了具体功能somethingImp
.从本质上讲,它捕获了依赖关系,因此它的工作方式与具有注入的依赖关系的对象一样,您仍然可以使用其余的bar
参数调用它:
Now, doStuff'
is a closure, because it closes over the concrete function somethingImp
. Essentially, it captures the dependency, so it works just like an object with an injected dependency, and you can still invoke it with the remaining bar
argument:
let bar = 42
let actual = doStuff' bar
测试
以下是将局部函数用作存根的示例:
Here's an example of using local functions as stubs:
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关键字,但是为了进行适当的测试,您应该定义适当的断言函数,或使用自己喜欢的断言库.
Here, for the sake of simplicity, I've used the assert keyword, but for proper tests, you should define a proper assertion function, or use your favourite assertion library.
通常,我倾向于放宽对输入参数的验证,因为这可能会使测试双打与特定实现过于紧密地结合在一起.另外,请记住,您应该使用存根对于查询,对于命令来说是假的-在此示例中,只有查询,因此所有的测试双打都是存根:尽管它们确实验证了输入 if 的调用,但测试并未验证它们是否完全被调用.
Normally, I would tend to loosen the verification of input arguments, as it may make the Test Doubles too tightly coupled to a particular implementation. Also, keep in mind that you should use Stubs for Queries, and Mocks for Commands - in this example, there are only Queries, so all the Test Doubles are Stubs: although they do verify input if they are invoked, the test doesn't verify that they are invoked at all.
这篇关于如何在具有外部依赖项的f#中测试函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!