【问题标题】:Unit testing higher order functions in F#在 F# 中对高阶函数进行单元测试
【发布时间】:2017-03-19 21:10:18
【问题描述】:

以下面的 F# 为例:

let parse mapDate mapLevel mapMessge (groups : string list) = 
    {
        DateTime = 
            mapDate(
                groups.[2] |> Int32.Parse, 
                groups.[0] |> Int32.Parse, 
                groups.[1] |> Int32.Parse)
        Level = mapLevel groups.[3]
        Message = mapMessge  groups.[4]
    }

我可以独立地对 map 函数进行单元测试,但这没关系,但是我如何对这个函数正确调用作为参数传入的函数进行单元测试呢?

在 C# 中,我会使用模拟并验证对它们的调用。我最近观看了一个复数视觉视频,该视频讨论了函数式语言如何倾向于使用存根而不是模拟。在这里,我可以传入一个函数,如果它没有得到预期的参数,则抛出该函数,但我并没有真正接受这种方法。

我只是想知道函数式编程中是否有任何模式可以用于单元测试这样的高阶函数?

【问题讨论】:

  • 如果parse 为给定的输入返回正确的输出,那么事实上它正确地调用了它的参数函数,不是吗?
  • 确实如此,但我不是紧密耦合实现吗?我明白你在说什么,尽管我需要停止考虑验证函数是否被调用,并在给定一些存根的情况下纯粹断言函数的输出。
  • 事实上,我认为检查某些函数是否按某种顺序调用会引入更紧密的耦合;它说'实施必须完全像这样'。所以我认为你已经掌握了窍门,开始考虑纯函数及其输入和输出。
  • 不使用mapMessge是故意的吗?
  • @RobertNielsen 这是一个错字,我现在改正它。

标签: unit-testing f# functional-programming higher-order-functions


【解决方案1】:

好吧,让我不同意给出的答案。实际上,有一种很好的方法可以测试高阶函数,甚至无需考虑它们可能采用的具体类型(我认为典型的 HOF 是完全通用的,但是没有区别:我建议的方法将正确地使用更严格的 HFO)。

让我们来看一些非常简单的东西,每个人都熟悉的东西。 ['t] -> ['t] 函数怎么样?它接受一个参数 - whatever 类型的列表并返回相同类型的列表。传统的 OOP 方法在这里行不通:需要对't 进行限制并测试该类型的某些特定参数; 唯一让作者对其实现更有信心的方法是增加单元测试数量。

在数学中有非常棒的东西叫做“范畴论”。这是一个相对较新的数学领域,从外部而不是内部研究事物。为了能够“从外部”描述事物,您需要获取您感兴趣的事物并强制它与您已经足够深入了解的事物进行交互。因此,范畴论教导要根据它们与其他事物的相互关系来描述事物。我们不能在这里做同样的事情吗?..

确实,我们可以。这实际上很简单:我们已经得到了f : ['t] -> ['t],但是还有什么可以让我们既可以交互又可以定义common的东西——无论任何其他因素如何,每次交互都适用的东西?让我们采取 any g: 't -> 'y。现在我们可以声明:g (List.head (f ...) = List.head (List.map g (f ...))。我假设['t] 类型的某个参数来替换...。请注意:给定属性是通用的:它适用于任何由指定签名组成的纯函数,无论它们的实现如何。另请注意通用但显而易见:只有两个不同的“对象”通过“组合”相互交互,也可以用标准 F# 的 (|>), (<|) 运算符重写。 p>

现在的事实是,对于任何高阶(纯)函数都存在这种普遍属性;大多数情况下,有几十个。因此,一个人能够根据组成(对于 FP 来说是常规的)来指定它们的属性,并保持在通用级别。以显式形式拥有这样的属性提供了自动生成数百个测试的机会,这些测试基于输入的不同,不仅因为它们的值(通常由单元测试完成,除了它们很少自动生成的事实),而且还根据类型。

【讨论】:

    【解决方案2】:

    纯函数更容易,因为您只需测试parse 函数的输出。您永远不需要像在命令式编程中那样使用副作用进行测试。

    在编写大部分单元测试时,您通常使用最简单的函数参数,例如 identity 或类似的。然后,您将编写一个名为“mapLevel is applied to Fourth group”之类的测试,而您将mapLevel 设置为易于识别为已更改的内容,例如toUpper。这可以确保您不会意外地将mapLevel 复制/粘贴到多个输出。然后对mapMessge 进行类似的测试。

    【讨论】:

      猜你喜欢
      • 2013-09-29
      • 2023-03-18
      • 2013-05-22
      • 1970-01-01
      • 1970-01-01
      • 2019-05-19
      • 2019-05-16
      • 2021-01-03
      • 1970-01-01
      相关资源
      最近更新 更多