【问题标题】:Faking Return value with F# and FakeItEasy使用 F# 和 FakeItEasy 伪造返回值
【发布时间】:2018-05-06 10:52:30
【问题描述】:

我正在尝试使用 FakeItEasy 模拟 C# 中定义的接口

public interface IMyInterface
{
    int HeartbeatInterval { get; set; }
}

在我做的 F# 测试中

let myFake = A.Fake<IMyInterface>()

A.CallTo(fun () -> ((!myFake).HeartbeatInterval)).Returns(10) |> ignore

在测试运行器中运行它会导致

System.ArgumentException Expression of type 'Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,System.Int32]' cannot be used for return type 'System.Int32'

事实上,它似乎对任何返回类型都执行此操作,例如如果 HeartbeatInterval 返回的类型为 foo,则抛出的异常将是类型 foo 而不是 System.Int32

是我做错了还是 F# 和 FakeItEasy 之间存在一些不兼容?

我突然想到使用对象表达式可能是一种更简单的方法。

【问题讨论】:

    标签: unit-testing f# fakeiteasy


    【解决方案1】:

    我敢假设“FakeItEasy”中的“Easy”代表“Easy Over Simple”。库它“简单”,如果您从 C# 中使用它,这可能是正确的。因为它显然是为与该语言一起使用而设计的。但它远非“简单”,因为它使用了特定于 C# 的语法技巧,这些技巧在视图中隐藏并且在 F# 中不能很好地工作。

    您现在遇到的具体问题是两件事的结合:(1) F# 函数与 C# Func&lt;T,R&gt; 不同,(2) F# 重载解析规则与 C# 不同。

    CallTo 有三个重载 - 其中两个采用 Expression&lt;_&gt;,第三个是“catch-all”,采用 object。在 C# 中,如果您使用 lambda 表达式作为参数调用此方法,编译器将尽力将 lambda 表达式转换为 Expression&lt;_&gt; 并调用其中一种专用方法。然而,F# 并没有做出这样的努力:F# 对 C# 样式 Expression&lt;_&gt; 的支持非常有限,主要集中在与 LINQ 的兼容性上,并且只有在没有替代方案时才会启动。所以在这种情况下,F# 选择调用CallTo(object) 重载。

    接下来,论点是什么? F# 是一种非常严格和一致的语言。除了一些特殊的互操作情况外,大多数 F# 表达式都有一个明确的类型,无论它们出现在什么上下文中。具体来说,fun() -&gt; x 形式的表达式将具有unit -&gt; 'a 类型,其中'ax 的类型。换句话说,它是一个 F# 函数。

    在运行时,F# 函数由 FSharpFunc&lt;T,R&gt; 类型表示,因此编译器将传递给 CallTo(object) 方法,它会查看它,但无法理解它到底是什么,抛出一个例外。

    要修复它,您可以为自己设置一个特殊版本的 CallTo(我们称之为 FsCallTo),这将强制 F# 编译器将您的 fun() -&gt; x 表达式转换为 Expression&lt;_&gt;,然后改用该方法CallTo:

    // WARNING: unverified code. Should work, but I haven't checked.
    type A with
        // This is how you declare extension methods in F#
        static member FsCallTo( e: System.Linq.Expressions.Expression<System.Func<_,_>> ) = A.CallTo( e )
    
    let myFake = A.Fake<IMyInterface>()
    
    // Calling FsCallTo will force F# to generate an `Expression<_>`, 
    // because that's what the method expects:
    A.FsCallTo(fun () -> ((!myFake).HeartbeatInterval)).Returns(10) |> ignore
    

    但是,正如您完全正确地观察到的那样,这对于模拟接口来说太麻烦了,因为 F# 已经具有完美的静态可验证、无运行时成本、语法良好以对象表达式的形式替代:

    let myFake = { new IMyInterface with 
                     member this.HeartbeatInterval = 10
                     member this.HeartbeatInterval with set _ = ()
                 }
    

    我完全建议和他们一起去。

    【讨论】:

    • 当然可以选择实际使用采用对象的重载,完全避免表达式。 A.CallTo(someObject).WhenArguments...
    • @Patrik 在这种情况下someObject 会是什么?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-02
    • 1970-01-01
    • 1970-01-01
    • 2012-07-16
    • 1970-01-01
    • 2022-10-24
    • 1970-01-01
    相关资源
    最近更新 更多