单元测试之关键问题解答

当你打开CSDN首页上的一位专家的最新博文,你以为你能学到什么东西,结果看完以后你觉得什么都没有学到,这种感觉是不是非常不爽?

这篇博文的重点:
  1. 单元测试的目的是什么;
  2. 单元测试测试什么;
  3. 在敏捷开发中,如何进行单元测试;
  4. 测试的分配与配合;
  5. 如何设计好的单元测试案例;
因为我的打字不快,外加国内对知识产权的轻视,我不准备花大篇幅写详细文章内容只是点到为止。你想学到更高深的知识,请在参加我开设的培训(我没有组织任何培训,也没有自己的培训机构,所以上一句话是说了白说)。

有人认为大多数程序员知道单元测试的重要性。事实上只有少数人知道单元测试的重要性,大多数程序员都懒得写单元测试,大多数公司都在使用流水性工程管理。XP,敏捷开发,或是精简敏捷开发都没有想像的那样广泛应用,甚至在很多地方XP,敏捷开发的应用都不正确。甚至在我现在的公司里,很多人都不知道如何运用JUnit/NUnit来进行单元测试。他们对JUnit/NUnit会有各种错误假设,然后设计出来的单元测试根本解决不了实际问题。更多的公司根本不懂自动化测试或单元测试,所以他们所有的测试多半是半自动半人为的测试。有一部分可能还只是人为测试,效率低下。

单元测试的目的
我也不重复很多地方已经重复过的什么是单元测试,单元测试的目的,我只想说说我的看法。单元测试的目的就是在最早的开发阶段进行测试。这么做的好处,最大的好处是,每次开发对一个个单元(也就是类)进行更改,再运行一下单元测试就知道自己有没有破坏单元希望达到的功能。TDD的目的是用测试来推动开发的进行。在单元没有成形之前,单元测试案例被设计出来模拟假想中的单元是如何被利用的,这时单元测试案例的运行肯定是不及格的,然后开发就开始设计单元来让单元测试案例的运行达到及格。以后的修改中,所有的单元测试要运行来保证单元的修改没有造成运行的错误

当然,并不是所有的开发团队都进行TDD,单元测试还是可以帮助这些小组,单元测试可以间接地测量一个单元的可测试性,要是一个单元要依靠很多其他单元才能完成一个操作,那么这个单元对其他部件的依赖性过大,那么可测性就很低。单元与单元的依赖可以依靠Mock对象来测试,但是过于复杂的依赖特别是实类和实类的依赖,在单元测试中可以马上检测出程序的可测性。

单元测试的目的,好处,并不是显而易见的。了解这些细节只能从详细阅读理解书籍和网上的资料,或者从我这样的人交流获得。现在很少人能够一字一句地将书或文章读透。所以最好的学习方法还是通过面对面的交流。

单元测试测试什么
单元测试的是一个源码中最小单元的代码是否正确处理它该处理的任务。源码中最小单元的代码可以是一个类中的类函数。也可以是类的本身(几个类函数一起合作处理类所应该进行的操作)。这里为什么有两种不同,因为单元测试设计者的风格不同,导致设计的不同,但是任何测试结合几个已开发的实单元,就不算单元测试。使用Mock单元来协助现有单元的测试是单元测试,因为Mock单元不是已开发的实单元,它是一个假的,辅助性的单元。所以使用Mock单元最终测试的也只是现有开发的单元。

任何测试,如业务功能,或是接口型测试都不算是单元测试。严格地说单元测试在面向对象程序设计里,就是对类为单元的测试。业务功能,接口测试已经是QA测试负责的业务。在XP,敏捷,精简敏捷开发里,业务功能,接口测试是接受性测试中的部分。QA在开发者开发的同时,必须对业务功能定义并设计接受性测试。只有通过了接受性测试,代码的初级接受性才能认可。单元测试是开发者自己的接受性测试,目的是保证自己开发的单元合乎自己的期望。QA的接受性测试往往是以业务功能定义和接口为目标的局部结合性测试,甚至有时是复杂的头到尾测试。单元测试和QA接受性测试都不能忘记边界情况,边界数据的处理等等案例。

单元测试的目标是,在最快的速度内让任何人能够设计一个测试案例,然后能够马上执行,案例与案例间不能有任何序列的依赖,每个单元测试都有自己的测试前设置,和测试後清除步骤(所以两个测试案例不用相互依赖)。

在敏捷开发中,如何进行单元测试
在敏捷开发中,自动化,经常性的单元测试是一个项目成功的关键。所有的自动化测试在每一次的构建後都要运行,任何错误都会造成构建失败,这样才能督促开发者或测试修改出错的地方。构建可以是每日构建(Daily Build),甚至是随时只要有变化就进行构建(Constant Continuous Build)。

敏捷开发的目标是,在开发中出现任何变化,整个团队都能敏捷地转换自己的开发方向来顺应变化的要求。TDD所要达到的目的是用需求来引导单元的业务功能,用测试先模拟业务功能的实现,在引导开发者实现单元的的业务功能。开发者必须对单元进行分割和重组,利用OO最佳设计结构和设计模式(Design Pattern)来实现OCP(Open for extension,Close for modification Principle)。利用OCP达到的目的是让整个系统变得更易延伸,更易维护。TDD也会驱使开发者对程序系统的构架进行层次分化,将系统分割成用户界面,商业逻辑,和后台存储的三层结构。三层结构是具有测试性的, 用户界面是用手动测试,和专业自动化测试工具进行的。商业逻辑和后台存储这两层都能用单元测试来实施自动话和单元化测试。 具体如何做到这些,必须通过实际案例的剖析来理解。

敏捷开发另一目标是,快速为客户提供期望的价值,所以,在最短时间内,一个完整的简单的软件系统必须形成,然后不同的部件在独立开发测试后,马上能够和现有的产品(也就是一个完整的简单的软件系统)集成。这就需要开发者,QA合作进行测试。开发者至少要做到用单元测试来保证部件质量。QA必须在集成的基础上开发结合性测试,头到尾测试,以整个系统为整体的方式进行测试。
具体的测试方法应该有实际情况出发,就像独孤九剑那样,随机应变。任何流程只要适应团队的发展,就是好的流程。

测试的分配与配合
理想情况下,单元测试是由开发者自己进行,或是在QA的指导下设计。现实中,开发者的测试能力有限,有时因为懒或不得不进行的赶工,他们不写或进行很少的单元测试,所以,测试帮助开发者进行单元测试,是一种必要。开发者和测试的合作可以是一起设计或是分工。仅仅让开发者搞单元测试,或把所有的单元测试扔给测试做都是比较极端的做法。我认为平衡点是,测试从黑箱的角度进行单元测试,程序从最基本的行为正确的目的用白箱的角度进行单元测试。两者的努力可以重叠。

在这个话题上,各个公司的运作都是不一样的,有些公司所有的测试都是设计完成后的测试(结果就是整个系统根本没有测试性,所有的东西都结在一起了),有些从最早就开始搞单元测试,有些把单元测试全部推给QA。也有很多是开发者和QA重复了许多同样的工作,等于是过多地重复地进行了许多同样的单元测试。

如何平衡开法者和QA的单元测试,这里没有一个简单的回答。这是开发团队和QA团队必须相互磨合最后达到的一种高效率的平衡。软件开发本身就是人与人的互动。所以没有相互交流相互沟通的开发肯定会把产品推向错误的发展方向。
如何设计好的单元测试案例
这是一个测试战术的问题,各个公司的在战术运用上都是不一样的。可能大的框架是一样的,但是在执行的过程中,具体细节都是不同的。不管变化是什么,只要坚持了正确的原则,测试的结果一定会很好,首先,了解整个团队期望的目标,然后熟悉使用的工具,最后是遵循业界公认的正确准则。我所知道的 工具是NUnit和JUnit,cxxtest,和几种不同的构建系统(微软的构建系统,Maven 1/2,FireFox的构建系统我只知道一点点,还有一些杂毛系统)。NUnit/JUnit的特性是任何单元测试都能在任何顺序下运行,单元测试本身就是一个独立的程序,它不需要依赖其他单元测试。它有自己的设置函数和任何情况下都能执行的清除 函数。知道这些我就知道如何设计一个独立的,不依赖其他单元测试的测试案例。cxxtest是有一定的不同,但是也是多少差不多。单元测试的目的是测试一个最基本的单元代码,当然对于QA,NUnit/JUnit都可以用于更复杂的测试案例的设计和执行。对于开法者来说,最简单最直接的就是测试一个最小单元的代码。更复杂的测试案例就超出了单元测试的范围。

所有的测试都应该在构建后运行,构建应该越频繁越好。现实中,所有的测试都运行可能会造成构建速度太慢。所以平衡测试数量和分割测试案例运行都要团队根据实际情况解决。而且,不被经常运行的测试案例如何在其他形式中运行,也要根据 团队根据实际情况来决定。测试案例太多而导致无法经常运行是一个很大的问题。找到平衡甚至能够超越平衡而达到频繁运行所有测试案例,是衡量测试团队敏捷处事的指标之一。能够频繁地运行大量耗时的测试案例,是一个团队所能达到的一个了不起的成就。

结束语
这又是一篇废话连篇的总结性博文,我实在也写不出什么,很多东西必须用案例的运用来说明。很多情况下开发者的单元测试和QA的测试没有明显的界限,两者交际的区域是一种灰色地带,有时QA水平很低下,开发者就必须进行很多QA形式的测试,有时两者的程序设计能力都很强,但是交流不够,两者会设计很多相同重复的测试案例。有时开发者根本不进行单元测试或是简单至极的单元测试,很多没有涉及的方面都由QA进行覆盖。如何确立QA开发者的测试方向,如何建立有效的测试机制,每个公司的测试战略,测试流程的规划。具体事宜,还是需要实际情况和长期的协调来决定的。
我个人想要做到的是,公司个人可以向我咨询具体解决方法,我以自己经验对这些具体情况提供一些指导。请留言处留言。

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

相关推荐


迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图:提供一种方法顺序访问一个聚合对象中的每个元素,而又不想暴露该对象的内部表示。应用:STL标准库迭代器实现、Java集合类型迭代器等模式结构:心得:迭代器模式的目的是在不获知集合对象内部细节的同时能对集合元素进行遍历操作
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:(1)同步阻塞IO(BlockingIO):即传统的IO模型。(2)同步非阻塞IO(Non-blockingIO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的N
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定义一系列算法,把他们封装起来,并且使他们可以相互替换,使算法可以独立于使用它的客户而变化。应用:排序的比较方法、封装针对类的不同的算法、消除条件判断、寄存器分配算法等。模式结构:心得:对对象(Context)的处理操作可
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个作用于某对象结构中的各元素的操作,它使你在不改变各元素的类的前提下定义作用于这些元素的新操作。应用:作用于编译器语法树的语义分析算法。模式结构:心得:访问者模式是要解决对对象添加新的操作和功能时候,如何尽可能不修改对象的类的一种方
命令模式(Command)命令模式(Command)[Action/Transaction]意图:将一个请求封装为一个对象,从而可用不同的请求对客户参数化。对请求排队或记录请求日志,以及支持可撤消的操作。应用:用户操作日志、撤销恢复操作。模式结构:心得:命令对象的抽象接口(Command)提供的两个
生成器模式(Builder)生成器模式(Builder)意图:将一个对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。 应用:编译器词法分析器指导生成抽象语法树、构造迷宫等。模式结构:心得:和工厂模式不同的是,Builder模式需要详细的指导产品的生产。指导者(Director)使用C
设计模式学习心得《设计模式:可复用面向对象软件的基础》一书以更贴近读者思维的角度描述了GOF的23个设计模式。按照书中介绍的每个设计模式的内容,结合网上搜集的资料,我将对设计模式的学习心得总结出来。网络上关于设计模式的资料和文章汗牛充栋,有些文章对设计模式介绍生动形象。但是我相信“一千个读者,一千个
工厂方法模式(Factory Method)工厂方法模式(Factory Method)[Virtual Constructor]意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类,使一个类的实力化延迟到子类。应用:多文档应用管理不同类型的文档。模式结构:心得:面对同一继承体系(Produc
单例模式(Singleton)单例模式(Singleton)意图:保证一个类只有一个实例,并提供一个访问它的全局访问点。应用:Session或者控件的唯一示例等。模式结构:心得:单例模式应该是设计模式中最简单的结构了,它的目的很简单,就是保证自身的实例只有一份。实现这种目的的方式有很多,在Java中
装饰者模式(Decorator)装饰者模式(Decorator)[Wrapper]意图:动态的给一个对象添加一些额外的职责,就增加功能来说,比生成子类更为灵活。应用:给GUI组件添加功能等。模式结构:心得:装饰器(Decorator)和被装饰的对象(ConcreteComponent)拥有统一的接口
抽象工厂模式(Abstract Factory)抽象工厂模式(Abstract Factory)[Kit]意图:提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。应用:用户界面工具包。模式结构:心得:工厂方法把生产产品的方式封装起来了,但是一个工厂只能生产一类对象,当一个工厂需要生
桥接模式(Bridge)桥接模式(Bridge)[Handle/Body]意图:将抽象部分与它的实现部分分离,使他们都可以独立的变化。应用:不同系统平台的Windows界面。模式结构:心得:用户所见类体系结构(Window派生)提供了一系列用户的高层操作的接口,但是这些接口的实现是基于具体的底层实现
适配器模式(Adapter)适配器模式(Adapter)[Wrapper]意图:将类的一个接口转换成用户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。应用:将图形类接口适配到用户界面组件类中。模式结构:心得:适配器模式一般应用在具有相似接口可复用的条件下。目标接口(Targ
组合模式(Composition)组合模式(Composition)意图:将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。应用:组合图形、文件目录、GUI容器等。模式结构:心得: 用户(Client)通过抽象类(Component)提供的公用接口统一
原型模式(Prototype)原型模式(Prototype)意图:用原型实例制定创建对象的种类,并且通过拷贝这些原型创建新的对象。应用:Java/C#中的Clonable和IClonable接口等。模式结构:心得:原型模式本质上就是对象的拷贝,使用对象拷贝代替对象创建的原因有很多。比如对象的初始化构
什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。