Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在具有外部依赖性的f#中测试函数_F# - Fatal编程技术网

如何在具有外部依赖性的f#中测试函数

如何在具有外部依赖性的f#中测试函数,f#,F#,我很难用外部依赖性对F#代码进行单元测试 在C#(我的背景)中,通常会有一个传入了依赖项的类,然后该类会被重用。为我的示例代码道歉,它很愚蠢,但我只是想说明我的观点 公共类Foo{ 独立性d; 公共Foo(IDependency d){this.d=d;} public int DoStuff(字符串条){返回d.DoSomethingToStuff(条);} 公共int-DoMoreStuff(字符串条){ int i=d.DoSomethingToStuff(巴); 返回d.DoSometh

我很难用外部依赖性对F#代码进行单元测试

在C#(我的背景)中,通常会有一个传入了依赖项的类,然后该类会被重用。为我的示例代码道歉,它很愚蠢,但我只是想说明我的观点

公共类Foo{
独立性d;
公共Foo(IDependency d){this.d=d;}
public int DoStuff(字符串条){返回d.DoSomethingToStuff(条);}
公共int-DoMoreStuff(字符串条){
int i=d.DoSomethingToStuff(巴);
返回d.DoSomethingElseToStuff(bar,i);
}
}
我试图在F#上保持务实,避免使用类和接口(除非我需要与其他.NET语言进行互操作)

所以在这个场景中,我的方法是让模块和一些依赖项作为函数传入的函数。我找到了这个技术

我在这段代码中遇到的两个问题是:

  • 我需要不断传递我的依赖关系。在C#中,它被传递到构造函数中并重新使用。您可以想象,如果在多个地方使用
    somethingFunc
    ,这种情况会多么快失控

  • 我如何进行单元测试以确定已执行依赖关系?同样在C#中,我会使用一个模拟框架并断言调用了某些方法

  • 在F#world中,我该如何处理这些问题?

    这并不太难——关键之一是要认识到存在一个问题

    在本例中,将有助于对函数参数进行重新排序,以便“依赖项”优先:

    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")
    
    这里,为了简单起见,我使用了,但是为了正确的测试,您应该定义一个正确的断言函数,或者使用您最喜欢的断言库


    通常情况下,我倾向于放松输入参数的验证,因为这可能会使测试与特定实现紧密耦合。另外,请记住,您应该—在本例中,只有查询,所以所有的测试双精度都是存根:尽管它们在调用输入时会验证输入,但测试根本不会验证它们是否被调用。

    人们可能不喜欢此注释,但是:了解副作用并查看。“我试图对F#保持务实态度,避免使用类和接口”相关:@MauricioScheffer我读过几篇博客文章,内容涉及将F#视为函数语言,并使用在数据结构上操作的函数,而不是封装数据和行为。无论如何,我不想把F#当作一种经典的OO语言来对待。@VincePanuccio OO与否没有多大关系。。。保持代码的可组合性,并尽可能避免副作用。@VincePanuccio问题是没有这样的限制。在脑海中分离这三个概念很有用:对象、命令、副作用。如何进行单元测试以确保调用函数?虽然通过这个例子很容易看出,但对于更复杂的逻辑,我想验证某些函数是否被调用。@VincePanuccio更新了我的答案。@VincePanuccio“我如何进行单元测试以确保函数被调用?”@MauricioScheffer这是最好的方法,但这要求您在设计时不进行变异,我相信你知道怎么做:)不过,F#不是一种“纯粹”的函数式语言,有时候,验证命令是否被调用是有帮助的——这就是我在文章中解释的。TBC:在纯函数程序中,没有命令,因此不需要模拟,所以我并不反对。在一个不纯的函数程序中,您可能有一些命令,因此也有一些模拟。
    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")