如何使用外部依赖项测试f#中的函数

Vin*_*cio 5 f#

我很难尝试使用外部依赖项对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);
  }
}
Run Code Online (Sandbox Code Playgroud)

我试图用F#实用,避免使用类和接口(除非我需要与其他.NET语言互操作).

所以我在这种情况下的方法是将模块和一些函数与依赖项作为函数传入.我在这里发现了这个技术

module Foo

  let doStuff bar somethingFunc =
    somethingFunc bar

  let doMoreStuff bar somethingFunc somethingElseFunc =
    let i = somethingFunc bar
    somethingElseFunc bar i
Run Code Online (Sandbox Code Playgroud)

我对这段代码的两个问题是:

  1. 我需要继续传递我的依赖关系.在C#中,它在构造函数中传递并重新使用.你可以想象如果somethingFunc在几个地方使用它会多快失控.

  2. 如何对依赖项执行进行单元测试?再次在C#中,我使用了一个模拟框架并断言某些方法被调用.

如何在F#世界中解决这些问题?

Mar*_*ann 8

将依赖注入等SOLID概念映射到功能样式F#并不太困难- 其中一个关键是要意识到对象和闭包之间存在很强的关系.

在本例中,它将有助于重新排序函数参数,以便"依赖"首先出现:

module Foo =    
  let doStuff somethingFunc bar =
    somethingFunc bar

  let doMoreStuff somethingFunc somethingElseFunc bar =
    let i = somethingFunc bar
    somethingElseFunc bar i
Run Code Online (Sandbox Code Playgroud)

这将使您能够使用部分函数应用程序组合函数:

let doStuff' = Foo.doStuff somethingImp
Run Code Online (Sandbox Code Playgroud)

现在,doStuff'是一个闭包,因为它关闭了具体的功能somethingImp.本质上,它捕获依赖项,因此它就像具有注入依赖项的对象一样工作,您仍然可以使用其余bar参数调用它:

let bar = 42
let actual = doStuff' bar
Run Code Online (Sandbox Code Playgroud)

测试

以下是使用本地函数作为存根的示例:

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")
Run Code Online (Sandbox Code Playgroud)

这里,为了简单起见,我使用了assert关键字,但是为了正确测试,你应该定义一个正确的断言函数,或者使用你最喜欢的断言库.

通常情况下,我倾向于放松对输入参数的验证,因为它可能会使测试双打与特定实现过于紧密耦合.另外,请记住,您应该使用Stubs for Queries和Mocks for Commands - 在这个例子中,只有Queries,所以所有的测试双打都是Stubs:尽管如果它们被调用它们确实验证了输入,那么测试不会验证它们是否完全被调用.

  • @MauricioScheffer这是最好的方法,但这需要你设计没有变异,我相信你*你知道该怎么做:)尽管如此,F#还不是一种"纯粹的"功能语言,有时,它可能会有所帮助验证调用了一个Command - 这就是我在[Mocks for Commands,Stubs for Queries]中解释的内容(http://blog.ploeh.dk/2013/10/23/mocks-for-commands-stubs-for-查询)文章.TBC:在一个纯粹的功能程序中,没有命令,因此,不需要Mocks,所以我并不反对.在一个不纯的功能程序中,你可能有一些命令,因此,有一些模拟. (3认同)
  • @VincePanuccio"如何进行单元测试以确保调用函数?" < - 不要.您的单元测试应该只是将一些数据传递给正在测试的函数,然后检查结果. (2认同)