首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

敏捷思维 架构设计中的方法学(14) 代码验证-2

敏捷思维 架构设计中的方法学(14) 代码验证-2

测试驱动测试驱动的概念可能大家并不陌生。在RUP中的同样概念是测试优先设计(test-first design),而在XP中则表现为测试优先编程(test-first programming)。其实我们在日常的工作中已经不知不觉的在进行测试驱动的部分工作了,但是将测试驱动提高如此的高度则要归功于敏捷方法。测试驱动的基本思想是在对设计(或编码)之前先考虑好(或写好)测试代码,这样,测试工作就不仅仅是测试,而成为设计(或代码)的规范了。Martin Fowler则称之为"specification by example"
在敏捷测试领域。一种做法是将需求完全表述为测试代码的形式。这样,软件设计师的需求工作就不再是如何编写需求来捕获用户的需要,而是如何编写测试来捕获用户的需要了。这样做有一个很明显的好处。软件设计中的最致命的代码是在测试工作中发现代码不能够满足需求,发生这种情况有很多的原因,但是其结果是非常可怕的,它将导致大量的返工。而将需求整理为测试代码的形式,最后的代码只要能够经过测试,就一定能够满足需求。当然,这种肯定是有前提的,就是测试代码要能够完整、精确的描述需求。做到这一点可不容易。我们可以想象一下,在对用户进行需求分析的时候,基本上是没有什么代码的,甚至连设计图都没有。这时候,要写出测试代码,这是很难做到的。这要求设计师在编写测试代码的时候,系统的整体架构已经成竹在胸。因此这项技术虽然拥有美好的前景,但是目前还远远没有成熟。
虽然我们没有办法完全使用以上的技术,但是借用其中的思想是完全有可能的。
首先,测试代码取代需求的思想之所以好,是因为测试代码是没有歧义的,能够非常精确的描述需求(因为代码级别是最细的级别),并紧密结合架构。因此,从需求分析阶段,我们就应该尽可能的保持需求文档的可测试性。其中一个可能的方式是使用CRC技术。CRC技术能够帮助设计人员分析需求中存在的关键类,并找出类的职责和类之间的关系。在RUP中也有类似的技术。业务实体代表了领域中的一些实体类,定义业务实体的职责和关系,也能够有助于提高设计的可测试性。无论是哪一种方法,其思路都是运用分析技术,找出业务领域中的关键因素,并加以细化。
其次,测试驱动认为,测试已经不仅仅是测试了,更重要的是,测试已经成为一种契约。用于指导设计和测试。在这方面,Bertrand Meyer很早就提出了Design by Contract的概念。从软件设计的最小的单元来看,这种契约实际上是定义了类的制造者和类的消费者之间的接口。
最后,软件开发团队中的所有相关人员如果都能够清楚架构测试代码,那么对于架构的设计、实现、改进来说都是有帮助的。这里有一个关于测试人员的职责的问题。一般来说,我们认为测试人员的主要职责是找出错误,问题在于,测试人员大量的时间都花费在了找出一些开发人员不应该犯的错误上面。对于现代化的软件来说,测试无疑是非常重要的一块,但是如果测试人员的日常工作被大量原本可以避免的错误所充斥的话,那么软件的质量和成本两个方面则会有所欠缺。一个优秀的测试人员,应该把精力集中在软件的可用性上,包括是否满足需求,是否符合规范、设计是否有缺陷、性能是不是足够好。除了发现缺陷(注意,我们这里用的是缺陷,而不是错误),测试人员还应该找出缺陷的原因,并给出改正意见。
因此,比较好的做法是要求开发人员对软件进行代码级别的测试。因此,给出架构的测试代码,并要求实现代码通过测试是提高软件质量的有效手段。在了解了测试驱动的思路之后,我们来回答上一节结束时候的问题。可验证架构的最大的好处是通过自动化测试,能够建立一个不断改进的架构。在重构模式中,我们了解了重构对架构的意义,而保证架构的可测试性,并为其建立起测试网(下一节中讨论),则是架构能够得以顺利重构的基本保证。我们知道,重构的基本含义是在不影响代码或架构外部行为的前提条件下对内部结构进行调整。但是,一旦对代码进行了调整,要想保证其外部行为的不变性就很难了。因此,利用测试驱动的思路实现自动化测试,自动化测试是架构外部行为的等价物,不论架构如何演化,只要测试能够通过,说明架构的外部行为就没有发生变化。
针对接口的测试和前文一样,这里接口的概念仍然是广义上的接口。我们希望架构在重构的时候能够保持外部行为的稳定。但要做到这一点可不容易。发布的接口要保证稳定,设计师需要有丰富的设计经验和领域经验。前文提到的最小接口原则,其中的一个含义就是如此,发布的接口越多,今后带来的麻烦就越多。因此,我们在设计架构,设计类的时候,应该从设计它们的接口入手,而不是一上手就思考具体的实现。这是面向对象思想和面向过程思想的一大差别。
这里,我们需要回顾在稳定化这一模式中提到的从变化中寻找不变因素的方法。稳定化模式中介绍的方法同样适用于本模式。只有接口稳定了,测试脚本才能够稳定,测试自动化才可以顺利进行。将变化的因素封装起来,是保持测试脚本稳定的主要思路。变化的因素和需要封装的程度根据环境的不同而不同。对一个项目来说,数据库一般是固定的,那么数据访问的代码只要能够集中在固定的位置就已经能够满足变化的需要了。但是对于一个产品来说,需要将数据访问封装为数据访问层(或是OR映射层),针对不同的数据库设计能够动态替换的Connection。
测试网本章的最后一个概念是测试网的概念。如果严格的按照测试优先的思路进行软件开发的话。软件完成的同时还会产生一张由大量的测试脚本组成的测试网。为什么说是测试网呢?测试脚本将软件包裹起来,软件任何一个地方的异动,测试网都会立刻反映出来。这就像是蜘蛛网一样,能够对需求、设计的变更进行快速、有效的管理。
测试网的脚本主要是由单元测试构成的。因此开发人员的工作除了编写程序之外,还需要编织和修补这张网。编织的含义是在编写代码之前先编写测试代码,修补的含义是在由于软件变更而导致接口变更的时候,需要同步对测试脚本进行修改。额外的工作看起来似乎是加大了开发人员的工作量。但在我们的日常实践中,我们发现事实正好相反,一开始开发人员虽然会因为构建测试网而导致开发速度下降,但是到了开发过程的中期,测试网为软件变动节约的成本很快就能够抵消初始的投入。而且,随着对测试优先方法的熟悉和认同,构建测试网的成本将会不断的下降,而起优势将会越来越明显:
  • 能够很容易的检测出出错的代码,为开发人员扫除了后顾之忧,使其能够不断的开发新功能,此外,它还是代码日创建的基础。
  • 为测试人员节省大量的时间,使得测试人员能够将精力集中在更有效益的地方。
此外,构成测试网还有一个额外的成本,如果开发团队不熟悉面向对象语言,那么由于接口不稳定导致的测试网的变动会增大其构建成本。
总结从以上的讨论可以看出,架构和代码是分不开的,架构脱离了代码就不能够称得上是一个好的架构。这是架构的目标所决定的,架构的最终目标就是成为可执行的代码,而架构则为代码提供了结构性的指导。因此,用代码来验证架构是一种有效的做法。而要实现这个做法并不是一件容易的事情,我们需要考虑代码级别的架构相关知识(我们讨论的知识虽然局限在面向对象语言,但是在其它的语言中同样可以找到类似的思想),并利用它们为架构设计服务。
返回列表