在F#中对高阶函数进行单元测试

9

请看以下 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]
    }

我可以独立单元测试地图函数,但是如何单元测试该函数正确调用传递的函数参数?在C#中,我会使用模拟对象(mocks)并验证对它们的调用。最近我看了一个 pluralsight 视频,讲述了函数式语言通常使用存根(stubs)而不是模拟对象(mocks)。在这里,我可以传入一个函数,如果没有得到预期的参数,则会抛出异常,但我并不太确定这种方法是否可行。我只是想知道有没有任何一般的函数式编程模式来单元测试这种高阶函数?

3
如果parse针对给定的输入返回了正确的输出,那么实际上它就已经正确地调用了它的参数函数,是吗? - Yawar
这是正确的,但我不是在紧密耦合实现吗?不过我明白你的意思了,我需要停止考虑验证函数是否被调用,而是纯粹断言给定一些存根的函数输出。 - Connel
4
实际上,我认为检查某些函数是否按特定顺序调用会导致更紧密的耦合,这意味着“实现必须完全像这样”。因此,我认为你正在逐渐掌握其中的要点,开始以纯函数及其输入和输出为思考方式。 - Yawar
mapMessage 没有被使用是故意的吗? - Robert Nielsen
@RobertNielsen 这是一个打字错误,我现在会更正。 - Connel
2个回答

1

好的,让我不同意给出的答案。实际上,有一种很好的方法可以测试高阶函数,而不必担心它们可能采取的具体类型(我认为典型的HOF是完全通用的,但是没有区别:我建议的方法将适用于更严格的HFO)。

让我们来看看真正简单的东西,每个人都熟悉。如何使用['t] -> ['t]函数?它接受一个参数-任何类型的列表,并返回相同类型的列表。传统的OOP方法在这里行不通:需要对't进行限制并测试该类型的某些特定参数;使作者感到更自信的唯一方法是增加单元测试数量。

数学中有一个名为“范畴论”的非常棒的东西。它是比较新的数学领域,从外部而不是内部研究事物。为了能够“从外部”描述事物,您需要将您感兴趣的事物与您已经深入了解的某些东西强制交互。因此,范畴论教导我们用其他事物的相互关系的术语来描述事物。难道我们不能在这里做同样的事情吗?..

确实,我们可以做到。这其实很容易:我们已经有一个 f : ['t] -> ['t],但是否还有其他东西可以使它们互相作用并定义出一些共同的内容——无论其他因素如何,都适用于每个交互?让我们取任意的g: 't -> 'y。现在我们能够陈述: g (List.head (f ...) = List.head (List.map g (f ...))。我假设某个类型为['t]的参数来代替...。请注意:给定的属性是通用的:它将适用于指定签名的任何纯函数组合,而不考虑它们的实现方式。还要注意多么通用但显而易见:只有两个不同的“对象”通过“组合”相互作用,这也可以用标准F#的(|>),(<|)运算符重写。

现实情况是,对于任何高阶(纯)函数,都存在这种通用属性;大多数情况下,有数十个这样的属性。因此,一个人能够在通用层面上通过组合来指定它们的属性(这对于FP来说很正常)。将这些属性以明确的形式呈现出来,可以为自动生成数百个测试提供机会,这些测试基于不同类型的输入而非仅仅是值(这通常由单元测试完成,除了它们很少被自动生成)。

-1

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接