在近的一个大型项目中,我们在早期定下了一个目标:不会在软件中使用大量QA人员专注于手工测试。通过手工测试发现bug极其耗时且成本高昂,这促使团队尝试尽可能的将质量内嵌到产品内部。但这并不意味着手工测试毫无价值,因为人们总能在怎样使用软件上给你一些特别的惊喜。
这是一个为期18个月左右,周期很长的项目,并且后续也会持续更新。 在项目初期,团队意识到项目成功的重中之重在于一个的测试策略,尤其是让我们的团队能够做到:1)随着项目时间的推移能够持续的提高团队的工作效率。2)不管面对的变更是大是小都能够具有足够的信心。
我们花费了很长时间才确定了一种有效的策略。这在很大程度上是因为我们不得不学习怎样让我们的程序在所有层上都具有可测性。虽然所有的项目团队成员都具有TDD(测试驱动开发)的经验,但仅仅这样并不足以建立有效的测试策略。
测试分层
给不同的测试分类是一件令人烦恼的事。有功能测试,集成测试,单元测试,验收测试,slow tests,fast tests,UI测试...等等等等。然后我们发现属于我们的测试主要有三种类型:
系统测试
皮下测试
单元测试
它们之间的区别主要在于被测试内容的范围。系统测试指的是通过应用的外部接口进行运作,无论对象是一个浏览器,文件下拉菜单,队列,window窗体应用或者其他的什么东西。
皮下测试运行在外部用户接口之下。如果测试的是Web应用,皮下测试在我们理解是指在真实的类
以及环境部署到位的情况下,通过命令处理器进行发送的表单对象。绕过UI层逻辑,直接到达domain层。发送表单对象,抛出”成功/失败”的执行结果。
对于底层而言,我们有单元测试。单元测试用于测试一个类,并且可以是fast test 或者 slow test中的一种。Fast Test 即是常规的TDD测试,用于增量的类设计。Test doubles曾被认为是必要的,但是除非系统交互非常值得关注,否则严格的 基于交互的测试 并不是那么有价值。我们同样也有slow 单元测试,其同样可被分类为 交互测试。当然它们同样可归类为 repository tests, persistence tests等。
单元:皮下:系统 测试在我们的测试工作中各自占的比重差不多是 10:2:1。 为了完成项目我们做了大约 5000 个单元测试,1000个皮下测试,500个使用 WaitN 以及 Gallio驱动浏览器的系统测试。6000个单元/皮下测试的执行时间大概是10分钟,而剩下的500个UI测试大概需要50分钟完成。
单元测试策略
单元测试是在严密的TDD模式下开发的。我们在功能实现之前编写单元测试,并用这些测试驱动代码设计。这些测试可以帮助发现设计问题、封装问题、代码异味等。
我们努力避免编写纯粹用于提供测试的代码。它们常常意味着我们有设计问题、责任错位或封装已被破坏。
随着我们越来越深入项目管道,我们对交互测试的重视越来越少。 如果你真的对交互感兴趣,那么通过mock进行的交互测试也仅仅是有趣而已。但更多的时候,我们更感兴趣的是附加作用,而交互只是一个实现细节。反之,我们经常做的是模拟(mock)出过慢或不可测的代码,比如存储库、基于外部服务的外观、配置类等等。否则,我们有限的模拟只是模拟了我们感兴趣的那些观察点。
在大型项目中的某些时间点,为了提取出能加快功能交付的理念,设计往往需要做大型的重构。在我们上一个项目中,我们发掘出了如下理念:
AutoMapper
将表单作为单独的命令消息处理
Input builders
有了以上这些,单元测试是重构时的保障。但只有我们依赖这些测试来捕获应用程序中所有有趣的行为时,才能有保障的作用。为了允许有效的大中型重构,我们需要增加额外的测试层级。
皮下测试策略
皮下测试,顾名思义,所有的测试都是在用户界面之下进行的。在MVC应用程序中,皮下测试是测试控制器下面的所有内容。对于Web service,一切测试都在终端下进行。皮下测试的思想是,应用程序的上层不执行任何实际的业务逻辑,而只是外部接口与底层服务之间的连接。
皮下测试的重要性体现在我们希望在抛开如用户接口和外部服务这类外部连接点的情况下,能够在整个系统运行的同时测试业务逻辑。相对于单元测试关注小模块的设计,皮下测试关注的不涉及设计,而是测试整个系统的基本输入和输出。