How to test functions in f# with external dependencies

。_饼干妹妹 提交于 2019-12-21 17:01:41

问题


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:

  1. 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.

  2. 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!