第4章 测试
编写单元测试是进行验证,更是进行设计。同样,它更是在编写文档。编写单元测试终结了许多反馈循环,尤其是功能验证方面的反馈循环。
4.1 测试驱动开发
假设我们遵循如下3条简单规则:
(1)除非编写了一个不能通过的单元测试,否则不编写任何产品代码。
(2)只要编写正好导致测试不通过或者编译失败的单元测试就够了,无需再多。
(3)只要编写能够正好使失败的单元测试通过的商品代码就够了,无需再多。
如果遵循这些规则,我们就是以非常短的迭代周期进行工作。我们仅仅编写刚好不能通过的单元测试,接着编写正好能使得该单元测试通过的产品代码。我们以1min、2min的节奏在这些步骤间交替。
第一个也是非常明显的效果,是程序中的每一项功能都有测试来验证它的操作正确性。这个测试套件可以给以后的开发提供支援。无论如何,我们因疏忽破坏了某些已有的功能,它就会告示我们。我们可以像程序中增加功能,或者更改程序结构,而不用担心这个过程中会破坏重要的东西。测试告诉我们程序仍然具有正确的行为。这样,我们就可以更自由地对程序进行改进。
还有一个更重要但不那么明显的效果,是首先编写测试可以迫使我们用不同的观测点。我们必须从程序调用者的有利视角去观察我们将编写的程序。这样,我们就会在关注程序功能的时候,直接关注它的接口。通过首先编写测试,我们可以设计出便于调用的软件。
此外,通过编写测试,我们迫使自己把程序设计为可测试的。把程序设计为易于调用和可测试是非常重要的。为了成为易于调用和可测试,程序必须和它的周边环境解耦。这样,首先编写测试迫使我们解除软件中的耦合。
首先编写测试的另一个重要效果,是测试可以作为一种非常有价值的文档。如果想知道如何调用一个函数或者创建一个对象,会有一个测试展示给你看。测试就像一套范例,它帮助其他程序员了解如何使用代码。这份文档是可编译、可运行的。它保存最新。它不会撒谎。
4.2 验收测试
作为验证工具来说,单元测试是必要的,但是不够充分。单元测试验证了系统中小的组成单元按照所期望的方式工作,但是它们没有验证系统作为一个整体是工作的正确性。单元测试是用来验证系统中单个机制的白盒测试(white-box test)。验收测试是用来验证系统能够满足客户需求的黑盒测试(black-box test)。
验收测试由不了解系统内部机制的人编写。这些测试可由客户直接编写,或者由业务分析师、测试人员及质量保证专家编写。验收测试是自动执行的,通常用一种特定的规格描述语言编写,这种语言比较适合非技术人员进行阅读和使用。
验收测试是关于一项特性的最终文档。一旦客户编写完成了验证一项特性的验收测试,程序员就可以阅读那些验收测试来真正地理解这项特性。所以,验收测试是有关系统特性的可编译、可执行的文档。简而言之,验收测试是真正的需求文档。
此外,首先编写验收测试对于系统的架构具有深远的影响。为了使系统具有可测试性,就必须要在高的系统架构层面对系统进行解耦合。例如,为了使验收测试无需通过用户界面(UI)就能够获得对于业务规则的访问,就必须要以满足这个目的的方式来解除用户界面和业务规则之间的耦合。
在项目迭代初期,会受到手工的方式进行验收测试的诱惑。但是,这样做使得迭代的初期就丧失了由自动化验收测试的需要带来的对系统进行解耦合的促进力,所以是不明智的。在开始第一次迭代时,如果非常清楚地知道必须要自动化验收测试,就会做出非常不同的系统架构方面的权衡。并且,正如单元测试可以促使你在小方面做出优良的设计决策一样,验收测试可以促使你在大的方面做出优良的系统架构决策。
我们还没有编写任何代码,也没有进行任何设计。这是开始考虑验收测试的最好时机。基于意图编程再一次称为有用的工具。我们应该以我们认为验收测试应该有的样子去编写它们,然后可以据此来设计系统。
我们想使验收测试便于编写并且易于改变,我想把它们放置在一个协作工具中,并且可以通过内部网络进行访问,这样可以随时运行。可以使用开源的FitNesse工具。在FitNesse中,可以以简单的Web页面的形式编写每个验收测试,并从Web浏览器来访问和运行。更多关于FitNesse的内容这里就不再介绍。
结论
测试套件运行起来越简单,就会越频繁地运行它们。测试运行得越多,就会越快地发现和那些测试的任何背离。如果能够一天多次地运行所有测试,那么系统失效的时间就绝不会超过几分钟。这是一个合理的目标。我们绝不允许系统倒退。一旦它工作在一个特定的级别上,就绝不能让它倒退到一个稍低的级别。
然而,验证仅仅是编写测试的好处之一。单元测试和验收测试都是一种文档形式。这样的文档是可以编译和执行的。因此,它是准确和可靠的。此外,编写测试所用的语言是明确的,并且非常易于其观看者阅读。程序员能够阅读单元测试,因为单元测试是使用程序员编程的语言编写的。客户能够阅读验收测试,因为验收测试是使用简单的表格式语言编写的。
测试最重要的好处就是它对于架构和设计的影响。为了使一个模块或者应用程序具有可测试性,必须对它进行解耦合。越是具有可测试性,耦合关系就越弱。全面地考虑验收测试和单元测试对于软件的结构具有深远的正面影响。