1

在 F#(和大多数函数式语言)中,一些代码非常短,如下所示:

let f = getNames
>> Observable.flatmap ObservableJson.jsonArrayToObservableObjects<string>

或者 :

let jsonArrayToObservableObjects<'t> =
    JsonConvert.DeserializeObject<'t[]> 
    >> Observable.ToObservable

我最终为后一个功能做的最简单的基于属性的测试是:

 testList "ObservableJson" [
        testProperty "Should convert an Observable of `json` array to Observable of single F# objects" <| fun _ -> 
            //--Arrange--
            let (array , json) = createAJsonArrayOfString stringArray

            //--Act--
            let actual = jsonArray
                         |> ObservableJson.jsonArrayToObservableObjects<string> 
                         |> Observable.ToArray 
                         |> Observable.Wait


            //--Assert--
            Expect.sequenceEqual actual sArray
    ]

不管排列部分,测试比被测函数多,所以比被测函数更难阅读!

  • 当它比生产代码更难阅读时,测试的价值是什么?

另一方面:

  • 我想知道由多个功能组合而成的功能是否可以安全地不被测试?
  • 是否应该在集成和接受级别进行测试?
  • 如果他们很短但执行复杂的操作怎么办?
4

2 回答 2

1

取决于您对“函数式编程”的定义。或者更准确地说——你想离函数式编程的起源有多近——数学具有广义和狭义的含义。

让我们拿一些与编程相关的东西。说,映射理论。您的问题可以这样翻译:从 A 到 B 的双射,从 B 到 C 的双射,我是否应该证明这两者的组合也是双射?答案是双重的:你绝对应该这样做,而且你只做一次:你的证明足够通用,可以涵盖所有可能的情况。

回到编程,这意味着管道内衬必须只测试(证明)一次——我猜这是在部署到生产之前。由于您作为程序员的工作是创建具有这种质量的函数(映射),即由管道运算符或其他任何东西组成,因此保留了所需的属性。再一次,最好坚持使用通用参数而不是编写大量类似的测试。

所以,最后,我们归结为一个更有价值的问题:如何保证某些操作保留某些属性?事实证明,承认这一事实的最简单方法是处理Monoid来自伟大的 Haskell 的类型:例如,Monoind是否可以将任何关联二元运算A -> A -> A与一些类型的标识元素一起表示A。拥有这样一个通用容器是非常有利可图的,并且是最知名的方式来明确您的代码的设计目的和方式。

于 2018-01-28T08:58:52.550 回答
0

我个人不会测试它。

事实上,对测试的需求较少,而是更多地依赖更严格的编译器规则、无副作用的函数、不变性等,这是我更喜欢 F# 而不是 C# 的主要原因之一。

当然我会继续(单元)测试“自定义逻辑”......例如算法代码

于 2018-01-23T19:49:56.820 回答