本章内容 定义一个单元测试 对比单元测试与集成测试 探索一个简单的单元测试示例 理解测试驱动的开发 凡事总有第一次:第一次编写程序,第一次项目失败,第一次通过努力获得成功。你不会忘记自己的第一次,我希望你也不忘记第一个测试。也许你已经写过一些测试,在你的记忆里那些测试也许是差劲的、笨拙的、缓慢的,或者无法维护的。(大部分人都是如此。)乐观一点的话,也许你有过很棒的单元测试经历,想看看这本书里有什么你可能错过的知识。 本章先分析“传统的”单元测试定义,并和集成测试的概念进行比较。很多人对这二者的区别并不清楚。接着我们会列举单元测试和集成测试相比的优缺点,给“优秀的”单元测试下个更好的定义。最后我们会了解一下测试驱动开发的概念,因为测试驱动开发经常会和单元测试联系在一起。在这一章中,我还会提到一些概念,这些概念会在本书其他的章节做更详尽的解释。 让我们以单元测试的定义作为开始吧。 1.1 逐步定义单元测试 在软件开发领域,单元测试并不是一个新概念。从早期使用Smalltalk编程语言的20世纪70年代开始,单元测试就已经出现,并一次又一次被证明是开发人员提高代码质量,加深理解类或方法功能需求的最佳手段之一。 Kent Beck在Smalltalk中引入了单元测试的概念,这个概念又被带入许多其他编程语言,使单元测试成为软件编程中一项极为有用的实践。在深入讲解前,我需要更好地定义单元测试的概念。下面将给出维基百科中单元测试的传统定义。在本章中这个定义会慢慢演化,最后在1.4节给出最终的定义。 定义1.0 一个单元测试是一段代码(通常是一个方法),这段代码调用另一段代码,然后检验某些假设的正确性。如果这些假设是错误的,单元测试就失败了。一个单元可以是一个方法或函数。 你写代码测试的对象称为“被测试系统”(System Under Test,SUT)。 定义 SUT代表System Under Test,有的人喜欢用CUT(Class Under Test或Code Under Test)。在测试中,被测试的东西称为SUT。 我以前觉得(是的,觉得。本书中没有科学,只有艺术和感觉),单元测试的这个传统定义在技术上是正确的,但是在过去的几年中,我对于单元这个概念的想法改变了。我认为一个单元代表系统中的“功能单元”或者一个“用例”。 定 义 从调用系统的一个公共方法到产生一个测试可见的最终结果,其间这个系统发生的行为总称为一个工作单元。我们通过系统的公共API和行为就可以观察到一个可见的最终结果,无需查看系统的内部状态。 一个最终结果可以是以下任何一种形式。 被调用的公共方法返回一个值(一个返回值不为空的函数)。 在方法调用的前后,系统的状态或行为有可见的变化,这种变化无需查询私有状态即可判断。(例如:一个以前不存在的用户可以登入系统,或者一个状态机系统的属性发生变化。) 调用了一个不受测试控制的第三方系统,这个第三方系统不返回任何值,或者返回值都被忽略。(例如:调用一个第三方日志系统,这个系统不是你编写的,而且你也没有源代码。) 对于我来说,工作单元这个概念意味着一个单元既可以小到只包含一个方法,也可以大到包括实现某个功能的多个类和函数。 你也许觉得被测试的工作单元应该尽可能的小。我以前也这么认为,现在却不这么看。如果你创建的工作单元更大,而且它的最终结果对这个接口的用户可见度更高,那么我相信你的测试会更容易维护。如果你试图把工作单元缩到最小,最后会不得不伪造一堆东西,这些东西并不是使用公共API的真实最终结果,而是生成结果过程中的一些中间状态。我会在之后关于过度指定的部分(大部分内容在第8章)对此做更多的解释。 定义更新1.1 一个单元测试是一段代码,这段代码调用一个工作单元,并检验该工作单元的一个具体的最终结果。如果关于这个最终结果的假设是错误的,单元测试就失败了。一个单元测试的范围可以小到一个方法,大到多个类。 不管你使用哪种编程语言,要定义单元测试,最困难的就是定义“优秀的”单元测试。 1.1.1 编写优秀单元测试的重要性 要理解单元测试,能理解什么是工作单元还不够。 大部分尝试对自己的代码进行单元测试的人要么在某个阶段放弃了,要么并没有真正执行单元测试。他们或者依靠在产品生命周期后期执行的系统测试和集成测试来发现问题,或者改用手工方式,使用定制的测试应用程序或者通过最终产品调用代码来进行测试。 编写差劲的单元测试是没有意义的,除非你正在学习如何编写单元测试,这些差劲的测试是你初次练习的产物。如果你不加鉴别地胡乱编写单元测试,那还不如干脆不写,至少还能免除日后维护和时间安排的麻烦。通过定义什么是优秀的单元测试,你可以确保自己在开始编写单元测试时有正确的目标。 要理解什么是优秀的单元测试,你需要了解开发人员在测试的时候都做些什么。 那么,如何确保代码今天还能工作呢? 1.1.2 我们都写过(某种)单元测试 不要惊讶,你已经自己进行过某种程度的单元测试。你见过提交代码前不做测试的开发人员吗?是啊,我也没见过。 也许你是用一个控制台程序来调用一个类或组件的各种方法,也许是特意创建WinForm或者Web Form的用户界面来检查这个类或组件的功能,也许干脆是在实际应用程序的用户界面上手工执行各种操作。但最终的结果都是让你能在一定程度上确信代码工作正常,可以移交给其他人。 图1-1展示了大多数开发人员如何测试他们的代码。具体的用户界面可能会不同,但是模式通常是一样的:使用一个手工的外部工具反复进行检验,或者运行整个应用程序,手工验证它的行为。 图1-1 在传统测试中,开发人员使用一个图形用户界面(GUI)触发要测试的类的某个行为,然后检验结果 这些测试也许很有用,而且可能也很接近传统定义的单元测试,但是它们和我在本书中要定义的优秀的单元测试还有很大的区别。说到这里,我们又回到了一个开发人员在定义优秀的单元测试的特质时要面对的第一个问题,也是最重要的问题:什么是单元测试,什么不是单元测试?