单元测试静态类型的功能代码


15

我想问大家,在这种情况下,对以Haskell,scala,ocaml,nemerle,f#或haXe编写的静态类型的功能代码进行单元测试是有意义的(最后一个是我真正感兴趣的,但是我想利用更大社区的知识)。

我问这是因为,根据我的理解:

  • 单元测试的一方面是使规范具有可运行的形式。但是,当采用声明性样式将规范化规范直接映射到语言语义时,实际上是否有可能以可运行的形式以独立方式表达规范,从而增加价值?

  • 单元测试最明显的方面是跟踪无法通过静态分析发现的错误。鉴于类型安全的功能代码是一种非常接近静态分析器所理解的代码的好工具,因此您似乎可以将很多安全性转移到静态分析上。但是,无法涵盖代码中使用x而不是y(都为坐标)之类的简单错误。OTOH在编写测试代码时也可能会出现这样的错误,因此我不确定是否值得这样做。

  • 单元测试确实引入了冗余,这意味着当需求改变时,实现它们的代码和涉及该代码的测试都必须被改变。当然,这种开销大约是恒定的,因此可以说,这并不重要。实际上,在像Ruby这样的语言中,它的确无法与优势相提并论,但是鉴于静态类型的函数式编程涵盖了许多基础单元测试的目的,因此感觉这是一个不变的开销,可以不付出任何代价就可以减少它。

由此推断,在这种编程风格中,单元测试已经过时了。当然,这样的主张只会导致宗教战争,所以让我将其归结为一个简单的问题:

当您使用这种编程风格时,您在什么程度上使用单元测试以及为什么使用(为什么希望代码获得什么质量)?或者反过来说:您是否有标准,可以用来限定静态分析器覆盖的静态类型功能代码单元,因此不需要单元测试范围?


4
顺便说一句,如果您没有尝试过QuickCheck,那么绝对可以。
乔恩·珀迪

scalacheck.org是Scala的等效
V-灯

Answers:


8

单元测试的一方面是使规范具有可运行的形式。但是,当采用声明式样式将规范化规范直接映射到语言语义时,实际上是否有可能以可运行的形式以独立方式表达规范,从而增加价值?

如果您有可以直接映射到函数声明的规范-很好。但是通常这些是两个完全不同的抽象级别。单元测试旨在测试单个代码,这些代码由从事该功能的同一位开发人员编写为白盒测试。规格通常看起来像是“当我在此处输入此值并按下此按钮时,应该会发生这种情况”。通常,这样的规范导致要开发和测试的功能不止一个。

但是,无法解决在代码中使用x代替y(都是坐标)这样的简单错误。但是,在编写测试代码时也可能会出现这样的错误,因此我不确定是否值得这样做。

您的误解是,单元测试实际上是第一手发现代码中的错误-至少这是不正确的,至少是部分正确的。这样做是为了防止您以后在代码演变时引入错误。因此,当您首先进行功能测试并且单元测试正常工作(正确放置“ x”和“ y”),然后在重构时使用x代替y时,单元测试将向您显示。

单元测试确实引入了冗余,这意味着当需求改变时,实现它们的代码和涉及该代码的测试都必须被改变。当然,这种开销大约是恒定的,因此可以说,这并不重要。实际上,在像Ruby这样的语言中,它的确无法与优势相提并论,但是鉴于静态类型的函数式编程涵盖了许多基础单元测试的目的,因此感觉这是一个不变的开销,可以不付出任何代价就可以减少它。

在工程中,大多数安全系统都依赖冗余。例如,在汽车上有两次休息,为跳伞者准备了多余的降落伞等。相同的想法适用于单元测试。当然,当需求改变时拥有更多的代码来改变可能是不利的。因此,尤其是在单元测试中,保持它们干燥是很重要的(遵循“不要重复自己”的原则)。在静态类型的语言中,与在弱类型的语言中相比,您可能需要编写更少的单元测试。特别是“正式”测试可能不是必需的-这是一件好事,因为它使您有更多的时间进行重要的单元测试,以测试实质性的事情。而且不要仅仅因为您是静态类型,就不需要单元测试,在重构时仍然有足够的空间引入错误。


5

单元测试的一方面是使规范具有可运行的形式。但是,当采用声明式样式将规范化规范直接映射到语言语义时,实际上是否有可能以可运行的形式以独立方式表达规范,从而增加价值?

您不可能完全将您的规范表示为类型约束。

当您使用这种编程风格时,您在什么程度上使用单元测试以及为什么使用(为什么希望代码获得什么质量)?

实际上,这种样式的一个主要好处是纯函数更易于进行单元测试:无需设置外部状态或在执行后对其进行检查。

通常,函数的规范(或其一部分)可以表示为将返回值与参数相关联的属性。在这种情况下使用的QuickCheck(Haskell的)或ScalaCheck(斯卡拉)可以让你写下这些属性作为语言表达,并检查其拥有的随机输入。


1
有关QuickCheck的更多详细信息:基本思想是编写“属性”(代码中的不变量)并指定如何生成潜在输入。然后,快速检查会创建大量随机输入,并确保您的不变式在每种情况下均成立。它比单元测试更彻底。
Tikhon Jelvis '12

1

您可以将单元测试视为如何使用代码的示例,并说明其价值所在。

这是一个例子,鲍勃叔叔很友善地和我一起演过约翰·康威的《人生游戏》。我认为这是对此类事情的绝佳练习。大多数测试都是全系统的,测试整个游戏,但是第一个测试仅测试一个功能-一个计算单元周围邻居的功能。您可以看到所有测试都是以声明性形式编写的,我们正在寻找的行为已明确阐明。

也可以模拟函数中使用的函数。通过将它们传递到函数中(等效于依赖项注入),或使用诸如Brian Marick的Midje之的框架


0

是的,单元测试已经对使用静态类型的功能代码有意义。一个简单的例子:

prop_encode a = (decode . encode $ a) == a

您可以强制prop_encode使用静态类型。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.