问题
I am having a hard time trying to unit test F# code with external dependencies.
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);
}
}
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
The two problems I have with this code is:
I need to keep passing my dependencies around. In C#, it's passed in the constructor and re-used. You can imagine how quickly this gets out of control if
somethingFunc
is used in several places.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.
How do I approach these problems in the F# world?
回答1:
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
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
Testing
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")
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.
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.
来源:https://stackoverflow.com/questions/25195865/how-to-test-functions-in-f-with-external-dependencies