测试观念谈

测试观念谈

邓 辉

到目前为止,测试仍然是一种公认的检验程序正确性最为有效的手段。详尽的测试可以大大地降低程序的缺陷率。虽然缺陷率是目前一种公认的检验程序质量的指标,但是它只是一个结果,要到达大家都满意的一个指标值是要付出一定的代价的。

糟糕的是,有很多项目在进行测试时,都只把注意力放在了这个缺陷率和覆盖率结果上,他们编写了非常详尽的测试用例文档,然后根据这个用例文档编写出对应的测试代码进行测试。这种做法在软件需求稳定的情况下也许能够工作的不错,但是需求不断变化这个大家都不愿意看到却又无法避免的现实,给这种测试方法带来了巨大的打击。功能的增加、修改和bug的修正都不可避免地导致程序结构的变化,而这种只关注覆盖率的测试用例文档和测试代码必然严重依赖于程序的结构。结果自然不难想象,他们不仅要去更改程序结构,还得去更改测试代码,即使在所要求的程序功能没有改变时也是如此。更加糟糕的是,他们还得去更改测试用例文档,以保持其和测试用例代码以及要测试的代码一致。否则的话,这份文档就会变得过时,此时非但不会具有什么用于交流的好处,反而会变得危险。

为什么会出现这种结果呢?难道说他们关注覆盖率错了吗?关注覆盖率本身没有错误,但是由于他们把覆盖率、缺陷率和软件其他特性(其中最为主要的一个就是封装性)的关系割裂开来,而这种割裂必然会带来高昂的测试代价。一个具有糟糕变化率封装特性的代码,很可能不论你付出多少代价,也根本无法达到高的测试覆盖率。这就是软件熵的惩罚。

所以我们要想在保证覆盖率和缺陷率的前提下,降低测试的代价,进行有效的测试,我们就必须把关注点放在软件的其他内在质量上面,我认为最为重要的几个指标是:封装性水平、变化率隔离水平、依赖关系处理水平以及是否满足Once and Only Once原则。这些指标会大大增强软件的灵活性和易更改性,并会大大提高软件本身的可测试性,直接降低到测试的代价和质量。

其实,为了能够和前面提到的需求易变特性相适应,很多探索者除了提出一些有助于达成上面所说的质量指标的技术(比如:OOAOPIOC以及语言的动态性等)外,还提出了很多在软件开发时可以遵循的一些过程方法,其中迭代法是目前得到普遍认可的一种方法。同样,我们所掌握的技能直接决定了我们想要实施的过程方法会带来的结果。一个没有掌握OO技能的团队,很可能会在实施迭代法时受到严重打击。因为和测试一样,他们缺乏保持软件“软性”所需要的足够技能。

说了这么多,我主要想表达的就是希望我们不要孤立地去谈测试,而是要认识到开发者本身的开发技能和测试有效性之间的关系。提高测试有效性(包括提高过程实施的有效性)的根本途径就是提高开发人员的技能。这个技能不是他们编写代码快慢的能力,而是他们对于好的代码和坏的代码的识别能力。这样才能非常经济地从根源上大幅提升软件的质量和开发生产力。我们在选择过程方法和实践时,要根据自己团队的情况,有针对性的选择那些对提升团队当前开发技能有帮助的部分。

那么团队应该如何开始呢?我觉得TDDTest-Driven Development)是一个很好的起点,通过TDD的实践,我们可以提高我们的分析、设计能力,改善编程的风格(注意不是编程规范),可以提高我们的快速迭代和节奏控制能力,可以提高我们对code smell的识别和重构能力,最为重要的是可以提高我们对需求的理解和快速验证能力(也就是我们做正确的事情的能力)。这些能力是我们能够更为有效地实施测试和迭代所必须的。敏捷方法中有很多实践存在争议,但是TDD是唯一一个得到广泛认可的实践。最近MSDN TV在对《Code Complete》(第2版)的作者Steve McConnell采访时,问到如果作者要对《Rapid Development》一书进行修订的话,会更改那些内容,Steve McConnell说,到目前为止唯一会更改的就是增加进TDD这项最佳实践。

TDD是一个很大的话题,我这里就不再赘述。但是有一点一定要记住:TDD is not about Testing。作为结果的测试套件,只是一个副产品。更为详细的信息可以参考Kent Beck的《Test-Driven Development》和Robert Martin的《Agile Software Development》。另外,关于测试认识方面更为详细的信息,可以参考我去年在IBM developerWorks上发表的《软件测试认识中的误区》一文。

谈到TDD,我不禁想起了前两天和两位朋友探讨覆盖率的必要性方面的问题。一位朋友最后举出了一个例子,Kent Beck在《Test-Driven Development》一书中说TDD实施的结果必然是100%(或者接近)的测试覆盖率。其实,在谈论TDD时,一般是不会讨论测试覆盖率的,Beck先生这样做只是为了和传统的理解有个可比性。为什么实施TDD后,会很容易地达到100%的覆盖率呢?原因是这样的:TDD使你关注于职责测试,在进行TDD时,你得不断重构你的代码使之符合好的原则,你得不断消除代码中的味道。最终的结果,你得到的是一个职责单一,没有重复,接口正交、紧凑,方法短小且不含(或者很少)分支的类(模块)。而你又是基于职责测试的,想不达到100%都难。是的,好的代码必然会很容易地带来足够高的测试覆盖率。

下面,我想谈一下对于想在公司范围内进行测试推广方面的一些建议,仅供参考:

l 在测试用例文档化方面,我持反对意见。我认为测试用例代码本身就是可以运行的文档,而且永远和实际的代码保持同步,并且最为直观、清晰且没有歧义。当然前提是我们必须要遵守TDD的纪律。如果真的需要书面化的文档(这种情况很少会发生),我们可以通过self-documented的代码来自动生成,比如在Java中可以使用JavaDoc工具。当然一些高层次的说明性文档除外。

l 可以把覆盖率作为一个指标,但是不应该把它作为唯一的指标。应该辅以其他更多纬度的度量标准。最重要的是要时刻关注高覆盖的代价,如果代价很高,那肯定是代码本身质量不高。

l 不应该以开发人员目前水平不高为由,就只实施一些看起来“安全”的实践。我们应该实施那些能够提升开发人员技能,能够使得他们变得更加优秀的实践。在我们拒绝实施一些推荐实践时,我们不应该只以“我们不希望你这样做,因为我们害怕你会偷懒”作为判断依据。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结