a、单元测试原理归纳
优良的单元测试具有以下的特点:简称为 a-trip。
自动性(automatic)
完备性(thorough)
可重复性(repeatable)
独立性(independent)
专业性(professional)
下面让我们逐一理解它们的含义。
自动性
单元测试是自动执行的,这里的自动指两个方面:1执行测试,2检查测试结果
执行测试应该是足够简单的,这样,我们就可以随时随地的进行测试。因此,执行相应测试就应该像在ide中点击一个按钮,或者在命令行中打一个命令那么简单。一些ide甚至会在后台连续的进行测试。
维护这个环境很关键,不要引入哪些会破坏自动测试模型,需要手工进行干预的单元测试。如果测试需要一些环境(网络,数据库,等等),那就把它做为测试的一部分。mock对象可以有效的隔离对外部的依赖。
运行测试的不光你一个人,还应该有一台机器对所有提交的代码持续的运行所有的测试。这种自动、无人职守的检查的作用就像一个定位杆,这种安全机制保证所有提交的东西不会破坏任何测试。理想情况下并不必须这样子,因为你可以依靠每一个开发人员都会自行运行所有必需的测试。回到现实,可能某个家伙再某个遥远的地方并没有执行必需的测试。也许在他的机器上有一些代码能够保证一切没问题,可他们却没有提交代码。这样虽然在本地可以执行,其他地方就会出问题。
最后,自动性的含义还有测试必须能够自行判断成功还是失败。让一个人(你或者其他倒霉蛋)通过检查测试产生的数据结果来判断代码的正确性,这是让项目失败不错的方式。测试的自检查是一致回归的重要特性。人类并不擅长这种重复性的工作,另外,在项目里,我们还有很多更重要的事情去做。
测试的自动执行和自动检查是非常关键的,这意味着你不用花太多的心思再这上面,它已经成为了项目的一部分。测试是项目安全保护网的重要组成部分。它在你掉下来时接住你,且不会挡道,这样你就可以集中精力走"钢丝"了。
彻底性
良好的测试应该是彻底的,所有可能会出错的地方都应该测到。如何能够做到这一点呢?一种极端情况,测试每一行代码、每一个分支、每一个抛出的异常,诸如此类;另一个极端,只测试最可能的情况:边界条件、数据丢失、数据格式无效,等等。这就需要根据项目情况进行区分了。
如果追求较高的测试覆盖度,那就需要寻求代码覆盖工具来帮忙了(例如:免费的nounit,quilt,商用的clover),用这些工具可以知道到底有多少代码是被测试覆盖的。
有一个事实需要注意,bug在代码中的分布情况是不均匀的,而是喜欢聚集在有问题的地方(很多昆虫都这样,例如:苍蝇)。
这种现象引出了一段非常著名的呼声“别打补丁,重写”。通常,从头重写一段有一堆问题的代码的代价和痛苦程度要低得多。当然,有了单元测试,从头重写代码也安全多了,单元测试可以保证新代码能够按照预定的执行。
可重复性
测试用例之间是独立的同时,也应该是独立于环境的。目标就是保持每个测试能够以任意顺序、不断的重复执行,且得到同样的结果。这意味着测试不能依赖于不可控的任何外部环境。
使用mock对象来隔离测试,保证测试不依赖于外部环境。如果必须依赖一些条件(例如:数据库),那就要保证这个条件不受其他开发人员的干扰。每个开发人员都应该有自己的sandbox,不同的数据库实例啦,web服务器啦。
没有可重复性保证,可能会在最糟糕的时侯遇到一些让你奇怪的问题,更糟糕的是,这些奇怪的问题通常都是虚幻的,并不是一个真正的bug,只是测试自身的问题。真不值得为这些虚幻的问题浪费时间。
每个测试用例,每次执行都应该得到同样的结果。如果测试结果是失败,那就说明是在代码里面存在bug,而不是有其他问题引起的错误。
独立性
测试代码必须保持精简、整洁,这就要求测试是专注的、与环境和其他测试隔离的(要记住,别的开发人员可能同时在运行这些测试)。
在书写测试时,保证每个测试只做一件事情。这不是说在一个测试方法里只写一个assert,而是说在一个测试方法里应该只专注于一个、或者几个共同提供某些功能的方法产品代码。有些时候一个测试方法只能够测试了一个复杂的产品代码方法的一部分,就需要一套测试方法来完整的测试产品代码的方法了。
理想情况下,我们希望在测试代码和潜在的bug之间建立起关联。也就是说,当一个测试方法fail的时候,能够定位到对应代码中的bug。独立性也意味着test之间没有相互依赖。每一个test都是可以独立运行的,而不需要必须在其他test之后才能运行。每一个test应该是一个孤岛。
专业性
为单元测试所写的代码是货真价实的,甚至有些人会争辩说,比提交个客户的源代码还要货真价实。这意味着,测试代码的书写和维护应该保持和生产代码一样的专业水准。产品代码中必须遵循的常用的设计准则,如:数据封装、dry原则、高内聚、低耦合等等,在测试代码中也一样要遵守。
测试代码很容易就会写成linear的样式,代码里面充次着同样的内容,不断的重复同样的代码,却鲜见方法和对象。这样很糟糕,测试代码必须和生产代码按同等对待来书写。这就要把哪些常用的、重复部分的代码抽取出来成为一个方法,这样就可以在多处调用。
这样就会慢慢积累出一些测试的方法,可以封装在一个类里面了。别争论什么,就算这个类只是用来测试的,就创建一个好了。这样做不进没问题,而且是应该得到鼓励的:测试代码是货真价实的代码。某些情况下,我们也许需要创建一个更大的框架,或者创建一个数据驱动的测试工具。
不要浪费时间测试那些不必要的方面,我们不是为了测试而创建test。测试的完整性要求测试方法的每一个可能产生bug的方面。如果没有产生bug的可能,就不要做测试。比如get set方法,就没必要做测试了。但如果这些get set方法中包含了业务逻辑,哪就有必要进行测试了。
最后,测试代码应该和生产代码是同一规模量级的。是的,你绝对没有看错。如果产品代码有20000行,那么测试代码至少也应该是20000行,甚至更多。测试代码的量也很大,就必须保持整洁、精简,有良好的设计,结构良好的,同生产代码一样的专业。
[摘自]http://www.iteye.com/topic/30932
--------------------------------------------------------------------------------
b、单元测试目的
(1)单元测试目的:
首先保证代码质量。
其次保证代码的可维护。
再此保证代码的可扩展。
(2)单元测试的优点
1、它是一种验证行为。
程序中的每一项功能都是测试来验证它的正确性。它为以后的开发提供支缓。就算是开发后期,我们也可以轻松的增加功能或更改程序结构,而不用担心这个过程中会破坏重要的东西。而且它为代码的重构提供了保障。这样,我们就可以更自由的对程序进行改进。
2、它是一种设计行为。
编写单元测试将使我们从调用者观察、思考。特别是先写测试(test-first),迫使我们把程序设计成易于调用和可测试的,即迫使我们解除软件中的耦合。
3、它是一种编写文档的行为。
单元测试是一种无价的文档,它是展示函数或类如何使用的最佳文档。这份文档是可编译、可运行的,并且它保持最新,永远与代码同步。
4、它具有回归性。
自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地的快速运行测试。
(3)单元测试的范畴
如果要给单元测试定义一个明确的范畴,指出哪些功能是属于单元测试,这似乎很难。但下面讨论的四个问题,基本上可以说明单元测试的范畴,单元测试所要做的工作。
1、 它的行为和我期望的一致吗?
这是单元测试最根本的目的,我们就是用单元测试的代码来证明它所做的就是我们所期望的。
2、 它的行为一直和我期望的一致吗?
编写单元测试,如果只测试代码的一条正确路径,让它正确走一遍,并不算是真正的完成。软件开发是一个项复杂的工程,在测试某段代码的行为是否和你的期望一 致时,你需要确认:在任何情况下,这段代码是否都和你的期望一致;譬如参数很可疑、硬盘没有剩余空间、缓冲区溢出、网络掉线的时候。
3、 我可以依赖单元测试吗?
不能依赖的代码是没有多大用处的。既然单元测试是用来保证代码的正确性,那么单元测试也一定要值得依赖。
4、 单元测试说明我的意图了吗?
单元测试能够帮我们充分了解代码的用法,从效果上而言,单元测试就像是能执行的文档,说明了在你用各种条件调用代码时,你所能期望这段代码完成的功能。
[摘自]http://www.iteye.com/topic/38205
我想说的就是,在整个软件行业来说,不管是程序员也好,老板也好,客户也好,都漠视了一个基本的事实:单元测试代码是软件产品的一个必须组成部分!不提供测试代码的软件产品就是偷工减料,以次充好的奸商行为!
[摘自]http://www.iteye.com/topic/14021
c、基于mock对象和junit框架简化spring web组件单元测试
对于java组件开发者来说,他们都盼望拥有一组能够对组件开发提供全面测试功能的好用的单元测试。一直以来,与测试独立的java对象相比,测试传统型j2ee web组件是一项更为困难的任务,因为web组件必须运行在某种服务器平台上并且它们还要与基于http的web交互细节相联系。
易测性(在框架中测试每个组件而不管其具体种类)是spring框架所提倡的关键原则之一。从这一角度看,spring是对核心j2ee模型的一个重大改进—在以前情况下,在容器外进行组件测试是很难实现的,而且即使是容器内测试也往往要求复杂的安装过程。
本文正是想集中探讨spring的易测性特征—它能使得对web组件进行单元测试就象测试普通java对象(pojo)一样容易。
一、spring mock类简介
mock对象是一个术语,原来主要流行于extreme程序员和junit小组中。在单元测试上下文中,一个mock对象是指这样的一个对象——它能够用一些“虚构的占位符”功能来“模拟”实现一些对象接口。在测试过程中,这些虚构的占位符对象可用简单方式来模仿对于一个组件的期望的行为和结果,从而让你专注于组件本身的彻底测试而不用担心其它依赖性问题。
spring从j2ee的web端为每个关键接口提供了一个mock实现:
mockhttpservletrequest—几乎每个单元测试中都要使用这个类,它是j2ee web应用程序最常用的接口httpservletrequest的mock实现。
mockhttpservletresponse—此对象用于httpservletresponse接口的mock实现。
mockhttpsession—这是另外一个经常使用的mock对象(后文将讨论此类在会话绑定处理中的应用)。
delegatingservletinputstream—这个对象用于servletinputstream接口的mock实现。
delegatingservletoutputstream—这个对象将代理servletoutputstream实现。在需要拦截和分析写向一个输出流的内容时,你可以使用它。
总之,在实现你自己的测试控制器时,上面这些对象是最为有用的。然而,spring也提供了下列相应于其它不太常用的组件的mock实现(如果你是一个底层api开发者,那么你可能会找到其各自的相应用法):
mockexpressionevaluator—这个mock对象主要应用于你想开发并测试你自己的基于jstl的标签库时。
mockfilterconfig—这是filterconfig接口的一个mock实现。
mockpagecontext—这是jsp pagecontext接口的一个mock实现。你会发现这个对象的使用有利于测试预编译的jsp。
mockrequestdispatcher—requestdispatcher接口的一个mock实现,你主要在其它mock对象内使用它。
mockservletconfig—这是servletconfig接口的一个mock实现。在单元测试某种web组件(例如struts框架所提供的web组件)时,要求你设置由mockservletcontext所实现的servletconfig和servletcontext接口。
那么,我们该如何使用这些mock对象呢?我们知道,httpservletrequest是一个持有描述http参数的固定值的组件,而正是这些参数驱动web组件的功能。mockhttpservletrequest,作为httpservletrequest接口的一个实现,允许你设置这些不可改变的参数。在典型的web组件测试情形下,你可以实例化这个对象并按如下方式设置其中的任何参数:
//指定表单方法和表单行为
同样地,你可以实例化并全面地控制和分析httpresponse和httpsession对象。接下来,让我们简要观察spring所提供的特定的junit框架扩展。
二、junit框架扩展
spring提供了下列一些特定的junit框架扩展:
abstractdependencyinjectionspringcontexttests—这是一个针对所有测试的超类,其具体使用依赖于spring上下文。
abstractspringcontexttests—这是一个针对所有的junit测试情形的超类。它使用一个spring上下文。并且,一般在测试中不是直接使用它,而是使用abstractdependencyinjectionspringcontexttests或者abstracttransactionalspringcontexttests这样的派生类。
abstracttransactionalspringcontexttests—这是一个针对所有测试的超类,我们一般把它应用在事务相关的测试中。注意,一旦完成每个测试它就会正常地回滚事务;而且你需要重载onsetupintransaction和onteardownintransaction方法以便手工开始并提交事务。
abstracttransactionaldatasourcespringcontexttests—这是abstracttransactionalspringcontexttests的一个子类,它使用了spring的基于jdbc的jdbctemplate工具类。
所有上面这些扩展将极大程度地简化在测试时对于相关操作的依赖性注入和事务管理。
五、事务性单元测试
到目前为止,你已看到了相对简单的junit测试—它仅发生在用mock对象支持的一个控制器的上下文中。但是,如果测试一个web组件只有在一个事务性上下文(例如,通过依赖性注入与hibernate集成到一起)中才有意义的情况又会怎么样呢?不必担心,spring mvc为junit框架提供了一个体面的扩展集合—它能准确地提供依赖性注入和事务安全测试(也就是,任何更新在测试完成后都将被回滚)。
测试步骤:
让我们看一种假想的情形—你要实现一个组件(例如mytransactionalcontroller)测试,该组件运行在一个事务性的上下文中(也即,其方法调用的结果发生在一个事务内并且它应该在测试运行完后被回滚):
1.创建一个定制的junit类(mytransactionalcontrollertest),它扩展了spring的junit扩展类
2.为了实现从spring内置的单元测试中发现spring管理的bean,你需要重载getconfiglocations()方法并且返回上下文文件位置的string数组,请看如下:
3.拥有该类的一个测试属性及其相关联的getter和setter。由于abstracttransactionalspringcontexttests利用了auto-wiring(这是spring框架的一个特性—能够根据类属性的名字识别类依赖性并且用spring bean填入相匹配的名字或id)技术而且在测试时它将自动地解决类的依赖性问题,所以在spring上下文文件中该类属性具有与spring管理的bean一样的名字并且在测试时每个属性都有一个适当命名的setter:
4.就象你通常操作“普通的”junit测试一样实现测试方法:
注意,你是在调用可能会更新数据库的方法submitpayment。spring的junit扩展(abstracttransactionalspringcontexttests)将在这个测试方法结束后实现自动回滚。
5.如果你需要执行任何安装或清除任务,则可以重载abstracttransactionalspringcontexttests的onsetupbeforetransaction()或onsetupintransaction()方法。abstracttransactionalspringcontexttests将重载从testcase继承来的setup()和teardown()方法并且使其成为final类型。