【问题标题】:Checking function equality in a F# unit test在 F# 单元测试中检查函数相等性
【发布时间】:2012-01-03 18:08:36
【问题描述】:

我有一堆 F# 函数,它们为相同的输入实现不同的算法,有点像策略模式。为了选择正确的策略,我想对输入参数进行模式匹配并将函数作为值返回:

let equalStrategy points : seq<double> =
    ...

let multiplyStrategy factor (points: seq<double>) =
    ...

let getStrategy relationship = 
    match relationship with
        | "="  ->  equalStrategy
        | "*5" ->  multiplyStrategy 5.0
        | _    -> raise (new System.NotImplementedException(" relationship not handled"))

现在我想编写一些单元测试来确保我返回正确的策略,所以我在 nUnit 中尝试了类似的东西:

    [<TestCase("=")>]
    [<Test>]
    member self.getEqualstrategy( relationship:string ) =            
        let strategy = getStrategy relationship

        Assert.AreEqual( strategy, equalStrategy )

现在我认为代码是正确的,并且会做我想做的事,但断言失败,因为函数似乎没有定义相等操作。所以我的问题是:

(a) 有没有办法比较 2 个函数以查看它们是否相同,即 let isFoo bar = foo == bar,我可以在 nUnit 断言中使用它吗?

(b) 是否有另一个单元测试框架可以在 F# 中为我执行此断言?

【问题讨论】:

    标签: unit-testing f# nunit


    【解决方案1】:

    测试您的getStrategy 返回的 F# 函数是否与您定义的函数之一是相同的函数也基本上是不可能的。

    提供一些细节 - 当您将函数作为值返回时,F# 编译器会生成一个继承自 FSharpFunc 的类。更重要的是,它每次创建函数值时都会生成一个new类,所以你无法比较类的类型。

    生成的类的结构是这样的:

    class getStrategy@7 : FSharpFunc<IEnumerable<double>, IEnumerable<double>> {
      public override IEnumerable<double> Invoke(IEnumerable<double> points) {
        // Calls the function that you're returning from 'getStrategy'
        return Test.equalStrategy(points);
      }
    }
    
    // Later - in the body of 'getStrategy':
    return new getStrategy@7(); // Returns a new instance of the single-purpose class
    

    原则上,您可以使用反射来查看 Invoke 方法的内部并找出从那里调用的函数,但这不是一个可靠的解决方案。

    在实践中 - 我认为您可能应该使用其他一些更简单的测试来检查 getStrategy 函数是否返回了正确的算法。如果您在几个样本输入上运行返回的策略,这应该足以验证返回的算法是正确的,并且您不会依赖实现细节(例如 getStrategy 函数是否只返回一个命名的函数,或者它是否返回具有相同行为的新 lambda 函数。

    或者,您可以将函数包装在 Func&lt;_, _&gt; 委托中,并使用与 C# 相同的方法。但是,我认为检查 getStrategy 是否返回特定引用是一项过于详细的测试,只会限制您的实现。

    【讨论】:

    • 嗨,托马斯,谢谢。有趣的是,如果我从一个具有 Func 类型的 C# 单元测试中调用我的 getStrategy,我可以让这个测试工作。感谢详细的回复,谢谢。
    • 您可以通过编写let foo = System.Func&lt;_, _&gt;(fun a -&gt; a + a) 创建委托值,然后foo = foo 返回true。要适应 getStrategy,您必须返回一个委托 instance,然后将其与同一个委托 instance 进行比较(因此创建一个指向同一委托的新委托功能不起作用)。如果你在 C# 中工作,你能发布一个例子吗?
    • @Aidan 您对此有何解决方案?你的测试用 C# 工作了吗?
    【解决方案2】:

    F# 编译器很难正式证明两个函数总是具有相同的输出(给定相同的输入)。如果可能的话,您可以使用 F# 非常简单地证明数学定理。

    其次,对于纯函数,您可以验证两个函数对于足够大的不同输入样本是否具有相同的输出。 fscheck 之类的工具可以帮助您自动执行此类测试。我没用过,但我用过基于相同想法的scalacheck(两者都是来自Haskell's QuickCheck的端口)

    【讨论】:

    • 对不起,我不清楚。我不是在检查两个不同的函数是否给出相同的输出,而是在给定模式匹配的输入的情况下,尝试检查我的模式匹配返回的函数是否是我想要的函数。因此,在 C# 中我可以检查两个 Func<...> 对象是否相同,我正在寻找 F# 中的等价物。
    【解决方案3】:

    函数没有相等比较器:

    你会遇到错误:类型 '('a -> 'a)' 不支持 'equality' 约束,因为它是一个函数类型

    有个好帖子here

    【讨论】:

    • 对不起,我应该在我的问题中说这是我遇到的错误。我的断言失败了,因为函数没有相等比较器 - 我在问是否有办法绕过 tis。
    • 比较等式equalStrategy ([5.1] |> Seq.ofList()) 函数的结果更好,或者让您自己的自定义比较器通过反射进行比较
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-10
    • 2018-01-20
    • 1970-01-01
    • 2019-08-09
    相关资源
    最近更新 更多