English 中文(简体)
自动化单元测试能否取代静态类型检查?
原标题:
  • 时间:2009-01-06 04:12:30
  •  标签:

我已经开始研究整个单元测试/测试驱动开发的概念,而我越来越想,它似乎填补了静态类型检查的类似角色。两种技术都提供编译时的快速响应,检查程序中特定类型的错误。然而,如果我说错了,请纠正我,但似乎拥有完整覆盖的单元测试套件会测试所有静态类型检查所检测到的东西,以及更多其他的东西。换句话说,静态类型检查只能花很少的力气去“证明”你的程序是正确的,而单元测试则可以让你“证明”更多(在一定程度上)。

那么,如果你同时使用单元测试,是否有使用静态类型检查语言的理由?一个类似的问题被问到了这里,但我想更深入地探讨。静态类型检查相对于单元测试有哪些具体优势(如果有)?一些问题比如编译器优化和智能感知已经出现在我们的脑海中,但是否有其他解决这些问题的办法?还有其他的优缺点我没有想到的吗?

最佳回答

我认为自动化单元测试对于动态类型语言将会非常重要,但这并不意味着它会替代静态类型检查在你应用的上下文中。事实上,一些使用动态类型的人可能实际上是因为他们不想要经常进行类型安全检查的麻烦。

动态类型语言相比静态类型语言提供的优势远不止是测试,类型安全仅仅是其中一个方面。动态类型语言和静态类型语言在编程风格和设计差异上也差别很大。

此外,过于严格地执行类型安全的单元测试意味着该软件终究不应该是动态类型的,或者应该用静态类型语言而不是动态类型语言编写应用的设计。

问题回答

关于软件质量有一个不变的事实。

如果它不能编译,它就不能上市。

在这个规则中,静态类型语言将胜过动态类型语言。

好的,是的,这条规则并不是不变的。Web应用程序可以在不编译的情况下发布(我部署了很多没有编译的测试Web应用程序)。但是基本真理是什么,这是最重要的。

你越早发现错误,修复成本就越低。

静态类型语言可以在软件开发周期的最早期防止真正的错误发生。 动态语言则不能。 如果你非常彻底地进行单元测试,可以替代静态类型语言。

然而,为什么要费心呢?世界上有许多非常聪明的人为您编写了一个完整的错误检查系统,即编译器。如果您担心更早地获取错误,请使用静态类型语言。

请不要把这篇文章视为对动态语言的抨击。我每天都使用动态语言,非常喜欢它们。它们非常具有表现力和灵活性,可以编写出非常有趣的程序。然而,在早期错误报告方面,它们确实输给了静态类型的语言。

针对任何规模合理的项目,仅通过单元测试无法考虑到所有情况。

所以我的答案是“不”,即使你设法考虑到所有情况,你也已经打败了使用动态语言的初衷。

如果你想要编写类型安全的程序,最好使用一种类型安全的语言。

拥有100%的代码覆盖率并不意味着您已经完全测试了您的应用程序。请考虑以下代码:

if (qty > 3)
{
    applyShippingDiscount();
}
else
{
    chargeFullAmountForShipping();
}

如果我输入qty = 1和qty = 4的值,我可以获得100%的代码覆盖率。

现在想象一下我的业务条件是"......对于三个或更多物品的订单,我需要对运费进行折扣。" 那么我需要编写适用于边界的测试。因此,我会设计测试,其中qty为2、3和4。我仍然有100%的覆盖率,但更重要的是我找到了逻辑上的一个错误。

这就是我对仅专注于代码覆盖率的问题所在。我的观点是,你最多只能得到这样一种情况:开发者基于业务规则创建一些最初测试,并且为了提高覆盖率指标,引用他们的代码来设计新的测试用例。

显示类型(我想你是指)是一种规范形式,单元测试要弱得多,因为它只提供示例。重要的区别是,规范声明在任何情况下必须保持什么,而测试只涵盖示例。您永远无法确定您的测试覆盖所有边界条件。

人们也往往忘记声明类型作为文档的价值。例如,如果一个Java方法返回一个List<String>,那么我立即知道我得到了什么,无需阅读文档、测试用例甚至方法代码本身。同样,对于参数:如果声明了类型,那么我知道方法期望什么。

在编写良好的代码中,声明本地变量类型的价值要低得多,因为变量存在的范围应该很小。 但是,您仍然可以使用静态类型:不必声明类型,让编译器推断出来。 Scala或甚至C#等语言允许您执行此操作。

一些测试风格更接近规范,例如QuickCheck或其Scala变体ScalaCheck,基于规范生成测试,试图猜测重要的边界。

如果你没有使用静态类型语言,那么你最好在计划使用这些代码进行任何"真实" 开发前仔细做好单元测试, 我希望用另一种表达方式来说明这个观点。

话虽如此,静态类型(或者更确切地说,显式类型)相对于单元测试具有一些显著的优点,这使我通常更加偏爱它。它创建了更易理解的API,并且允许以一种使用动态类型语言更加困难的方式快速查看应用程序的“骨架”(即每个模块或代码段的入口点)。

总之:在我看来,如果有实际、彻底的单元测试,动态类型语言和静态类型语言之间的选择主要取决于个人口味,有些人喜欢一种语言,有些人喜欢另一种语言。使用适合工作的工具。但这并不意味着它们完全相同——静态类型语言在某些方面始终具有优势,而动态类型语言在某些不同的方面始终具有优势。单元测试在很大程度上有助于将动态类型语言的缺点最小化,但它们并不能完全消除它们。

不。

但这不是最重要的问题,最重要的问题是:它不能做到有关紧要吗?

考虑静态类型检查的目的:避免一类代码缺陷(错误)。然而,这必须在所有代码缺陷的更大领域的背景下权衡。最重要的不是狭窄的比较,而是代码质量、编写正确代码的容易程度等方面的深度和广度比较。如果你能想出一种开发风格/流程,使你的团队能够更有效地生产更高质量的代码而不需要静态类型检查,那么这是值得的。即使你的测试中存在静态类型检查会捕捉到的漏洞,也是如此。

我想,如果你非常仔细的话,是可能的。但是为什么费这个劲呢?如果语言已经检查确保静态类型正确,那么测试它们就没有意义了(因为你已经得到了免费的检查)。

而且,如果您正在使用带有IDE的静态类型语言,IDE可以在编译测试之前提供错误和警告。我不确定是否有任何自动化单元测试应用程序可以做到这一点。

鉴于动态、延迟绑定语言的所有利益,我认为这是单元测试提供的价值之一。您仍需要仔细和有意识地编码,但这是任何编码的首要要求。能够编写清晰简单的测试有助于证明您的设计和实现的清晰度和简单性。它还为那些后来看到您的代码的人提供有用的线索。但是我不认为我依靠它来检测不匹配的类型。但实际上,我并不认为类型检查真的能捕捉到很多真正的错误。如果您首先具有清晰简单的编码风格,这种类型的错误在实际代码中并不会发生。

对于javascript,我期望jsLint将发现几乎所有的类型检查问题,主要是通过建议替代的编码风格来减少您的风险暴露。

类型检查有助于在系统内部的组件之间执行契约。单元测试(顾名思义)验证组件的内部逻辑。

对于单个代码单元,我认为单元测试真的可以使静态类型检查变得不必要。但在一个复杂的系统中,自动化测试无法验证系统不同组件可能相互交互的多种方式。为此,使用接口(在某种意义上是组件之间的一种“合同”)成为减少潜在错误的有用工具。而接口需要编译时类型检查。

我非常喜欢使用动态语言进行编程,所以我肯定不会反对动态类型。这只是最近发生在我身上的一个问题。不幸的是,我没有在大型和复杂的系统中使用动态语言的经验,因此我很想听听其他人的意见,看看这个问题是否是真实存在的,还是仅仅是理论上的。





相关问题
热门标签