为什么不在语法级别测试语言是受支持的功能?


37

您可以找到无穷无尽的博客,文章和网站列表,这些列表可以宣传对单元代码进行单元测试的好处。几乎可以保证,为Java,C ++,C#和其他类型的语言编写编译器的开发人员使用单元测试来验证其工作。

那么,为什么在这些语言的语法中,尽管它很流行,却为什么缺少测试呢?

微软在C#中引入了LINQ,那么为什么他们也不能添加测试呢?

我并不是要预测这些语言的变化,而是要阐明为什么它们一开始就没有。

例如:我们知道您可以编写一个for没有该语句语法的循环for。您可以使用whileif/ goto语句。有人认为for声明更有效,并将其引入语言。

为什么测试没有遵循编程语言的相同演变?


24
通过添加语法使语言规范更复杂,而不是通过简单的规则设计语言,而可以轻松地以通用方式扩展语言,这真的更好吗?
KChaloux 2014年

6
“语法级别”是什么意思?您是否有关于如何实施的详细信息/想法?
SJuan76

21
您能否给出一个测试的伪代码示例作为语法支持的功能?我很想知道您的想法。
FrustratedWithFormsDesigner 2014年

12
@FrustratedWithFormsDesigner 有关示例,请参见D语言
2014年

4
Rust具有内置单元测试功能的另一种语言。
塞巴斯蒂安·雷德尔2014年

Answers:


36

与许多事情一样,最好在库级别而不是语言级别支持单元测试。特别是,C#具有许多可用的单元测试库,以及.NET Framework本身的东西Microsoft.VisualStudio.TestTools.UnitTesting

每个单元测试库都有一些不同的测试原理和语法。在所有条件平等的情况下,选择多于少。如果将单元测试引入到语言中,则您要么会陷入语言设计者的选择之中,要么会使用...一个库,而完全避免使用语言测试功能。

例子

  • Nunit-通用的,惯用的单元测试框架,充分利用了C#的语言功能。

  • Moq-模拟框架,可充分利用lambda表达式和表达式树,而无需记录/播放隐喻。

还有许多其他选择。像Microsoft Fakes这样的库可以创建“垫片...”模拟,这些模拟不需要您使用接口或虚拟方法编写类。

Linq不是语言功能(尽管它是名称)

Linq是一个库功能。我们免费获得了C#语言本身的许多新功能,例如lambda表达式和扩展方法,但是Linq的实际实现是在.NET Framework中。

在C#中添加了一些语法糖,以使linq语句更整洁,但是使用linq不需要该糖。


1
我确实同意LINQ不是语言功能的观点,但是要使LINQ成为现实,就需要发生一些基础语言功能-特别是泛型和动态类型。
Wyatt Barnett 2014年

@WyattBarnett:是的,对于简单的单元测试也是如此,例如反射和属性。
布朗

8
LINQ需要lambda和表达式树。泛型已经在C#中了(通常在.Net中,它们已经存在于CLR中),而动力学与LINQ无关。您可以在没有动态的情况下执行LINQ。
Arthur van Leeuwen 2014年

Perl的DBIx :: Class是类似LINQ的功能的示例,在使用该语言实现所需的所有功能之后已有10多年了。
slebetman 2014年

2
@ Carson63000我认为您是指与var关键字结合在一起的匿名类型:)
M.Mimpen 2014年

21

原因很多。埃里克·利珀特(Eric Lippert)多次指出,feature X不在C#中的原因是因为这不在他们的预算范围内。语言设计人员没有无限的时间或金钱来实现事物,并且每个新功能都具有与之相关的维护成本。保持尽可能小的语言不仅对语言设计人员来说容易,而且对编写替代实现和工具(例如IDE)的人也更容易。免费的可移植性。如果单元测试是作为库实现的,则只需编写一次即可,并且可以在该语言的任何符合要求的实现中使用。

值得注意的是D 确实对单元测试提供了语法级别的支持。我不知道为什么他们决定把它扔进去,但是值得注意的是D意味着它是一种“高级系统编程语言”。设计人员希望它对于传统上使用C ++的一种不安全的低级代码是可行的,并且不安全的代码中的错误的代价是难以置信的高成本-未定义的行为。因此,我认为对他们来说,在可以帮助您验证某些不安全代码有效的任何事情上付出更多的努力是有意义的。例如,您可以强制只有某些受信任的模块才能执行不安全的操作,例如未经检查的数组访问或指针算术。

快速开发也是他们的首要任务,以至于他们将D代码作为足够快的编译速度以使其可用作脚本语言的设计目标。烘焙单元测试直接融入了语言中,因此您只需向编译器传递一个额外的标志就可以运行测试。

但是,我认为一个出色的单元测试库所要做的不只是找到一些方法并运行它们。以Haskell的QuickCheck为例,它可以测试诸如“对于所有x和y f (x, y) == f (y, x)”之类的东西。QuickCheck被更好地描述为一个单元测试生成器,它使您可以在比“对于此输入,我期望此输出”更高的级别上进行测试。QuickCheck和Linq并没有什么不同-它们都是特定于域的语言。因此,为什么不增加对语言的单元测试支持,为什么不添加使DSL实用化所需的功能呢?您将不仅获得单元测试的结果,还获得了更好的语言。


3
为了留在.Net世界中,有FsCheck,它是QuickCheck到F#的端口,并带有一些C#使用的帮助程序。
Arthur van Leeuwen 2014年

留在.NET世界中?这个问题与.NET无关。
Miles Rout 2014年

@MilesRout C#是.NET的一部分。问答经常谈论C#,甚至问题也谈论LINQ。
Zachary Dow

该问题未标记为.NET或C#。
Miles Rout 2015年

@MilesRout可能是正确的,但是您不能合理地说它与它无关,只是因为它没有标签。:)
Zachary Dow

13

因为测试,尤其是测试驱动的开发是一种与直觉相反的现象。

几乎每个程序员都开始了他们的职业生涯,他们认为自己在管理复杂性方面比实际要好得多。即使是最伟大的程序员,除非使用大量回归测试,否则他们都无法编写大型且复杂的程序而没有严重的错误,这一事实令许多从业人员感到非常失望甚至可耻。因此,即使是对本应了解得更多的专业人员,也普遍反对常规测试。

我认为宗教测试正逐渐成为主流和人们期望的事实,主要是由于随着存储容量和计算能力的爆炸式增长,正在构建比以往更大的系统-极其庞大的系统特别容易出现复杂性崩溃,没有回归测试的安全网就无法管理。结果,即使是特别固执和陷入困境的开发人员现在也勉强承认他们需要测试并且将始终需要测试(如果这听起来像是在AA会议上的自白,那是非常故意的-从心理上讲,需要一个安全网很难让许多人接受个人)。

今天流行的大多数语言都是在这种态度转变之前发生的,因此它们对测试的内置支持很少:它们具有assert,但没有合同。我非常确定,如果这种趋势持续下去,未来的语言将在语言而不是库级别上提供更多支持。


3
您稍微夸大了您的案子。尽管单元测试无疑是最重要的,但以适当的方式构建程序,使不必要的复杂性最小化的方法同样重要,甚至不那么重要。诸如Haskell之类的语言具有可提高其中编写的程序的可靠性和可证明性的类型系统,并且编码和设计方面的最佳实践避免了许多未经测试的代码的陷阱,从而使单元测试的重要性不如其他方面。
罗伯特·哈维

1
@RobertHarve ...和单元测试是非常间接推动开发朝着这一好办法。在创建测试时而不是与其他代码一起使用时使用API​​,有助于将它们置于正确的思维方式,以限制或消除泄漏的抽象或奇怪的方法调用。我认为KillianFoth并没有夸大其词。
Izkata 2014年

@罗伯特:我相信他高估的唯一一件事是“宗教测试正在逐渐成为主流”。如果按照地理时间框架来缓慢地定义,他可能就在不远处..... :)
mattnz

+1在我目前的工作中,我对可用新的库和技术带来的发展态度的巨变感到震惊。这些家伙第一次在我的职业生涯中说服使用更复杂的开发过程,而不是让我感觉自己像是站在山顶上狂奔。我同意这些态度以母语语法表达只是时间问题。
罗布

1
抱歉,再想一想:罗伯特·哈维的评论。现在,类型系统是一个不错的检查,但是确实确实不足以定义类型的约束-它们解决了接口问题,但没有约束正确的行为。(即1 + 1 == 3将编译,但可能取决于+的实现,这可能是正确的,也可能不是正确的)。我想知道下一个趋势是否会看到更具表现力的类型系统,这些系统结合了我们现在认为是单元测试的内容。
罗布

6

许多语言都支持测试。C的断言是程序可能失败的测试。那就是大多数语言停止的地方,但是Eiffel和最近的Ada 2012具有先不变式(必须传递函数的参数)和后不变式(必须传递函数的输出),而Ada则具有在函数中引用初始参数的能力。后不变。Ada 2012还提供类型不变式,因此,每当在Ada类上调用方法时,都会在返回之前检查类型不变式。

好的测试框架不能为您提供全面测试的类型,但这是语言可以最好地支持的重要测试类型。


2
在Eiffel进行了一些工作并大量使用了断言之后,我的经验是,您可以做的任何事情都是合同性质的,因此可以发现许多错误。
Blrfl 2014年

2

一些强类型功能语言的支持者认为,这些语言功能减少或消除了对单元测试的需求。

二,恕我直言,这方面的一个很好的例子是在这里这里的 F#for Fun and Profit

就个人而言,我仍然相信单元测试的价值,但是有一些有效的观点。例如,如果非法状态无法用代码表示,那么不仅不需要为此编写单元测试,而且是不可能的。


2

我认为你错过了一些必要的功能的添加,因为它们并未在单元测试中被强调。

例如,C#中的单元测试主要是通过使用属性来驱动的。在自定义属性功能提供了丰富的扩展机制,允许像NUnit的框架,以迭代和竞争,与喜欢的东西为基础的理论参数测试。

这使我想到了第二个要点-我们对将其烘焙为语言的良好可测试性了解不足。测试中的创新步伐比其他语言构造要快得多,因此我们需要在语言中拥有灵活的机制来留出创新的自由。

我是用户,但对TDD并不狂热-在某些情况下(特别是对于改善您的设计思维)它非常有帮助。它对于较大的旧式系统不一定有用-具有良好数据和自动化的更高级别的测试可能会随着强大的代码检查文化而带来更多好处。

其他相关阅读:

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.