利用 Eclipse 进行单元测试.docx
- 文档编号:6178715
- 上传时间:2023-01-04
- 格式:DOCX
- 页数:9
- 大小:22.02KB
利用 Eclipse 进行单元测试.docx
《利用 Eclipse 进行单元测试.docx》由会员分享,可在线阅读,更多相关《利用 Eclipse 进行单元测试.docx(9页珍藏版)》请在冰豆网上搜索。
利用Eclipse进行单元测试
利用Eclipse进行单元测试
模拟对象将模仿出于指导代码执行的惟一目的而编写的类的行为,以便它在测试时符合代码执行要求。
最终,模拟对象数目可以随着应用程序类数目的增长而增长。
使用jMock、RMock甚至EasyMock等框架有助于消除对物理的独立存在的模拟对象集的需求。
EasyMock框架的一个主要缺点是不能模拟具体类——而只能模拟接口。
在本文中,我将向您展示怎样使用jMock框架来模拟具体类和接口,以及如何用RMock测试某些模糊的情况。
注Eclipse平台为使用jMock和RMock测试框架提供了一种易于使用的机制。
在EclipseIDE中配置jMock和RMock注:
有关JUnit、jMock和RMock的最新二进制文件,请参阅参考资料。
首先启动Eclipse集成开发环境(IDE)。
接下来,创建一个基本Java?
项目,稍后将把JUnit、jMock和RMockJavaArchive(JAR)库导入到该项目中。
将Java项目命名为TestingExample。
在JavaPerspective内,选择Project>Properties,然后单击Libraries选项卡,如下所示:
图1.在Eclipse中编辑TestingExample项目的属性当JAR文件位于Java类路径(即,已在Eclipse内配置的Java运行时环境(JavaRuntimeEnvironment,JRE))中时,请使用AddJARs按钮。
AddVariable按钮适用于文件系统(本地或远程)中的资源(包括JAR)所驻留的具体目录,并且通常可以引用此按钮。
在必须引用Eclipse中默认的那些特定资源或为特定的Eclipse工作区环境配置的那些特定资源时,请使用AddLibrary按钮。
单击AddClassFolder,从已经配置为项目一部分的一个现有项目文件夹中添加资源。
对于本示例,请单击AddExternalJARs并浏览到已下载的jMock和RMockJAR。
将其添加到项目中。
当显示图2中所示的属性窗口时,请单击OK。
图2.已添加到TestingExample项目中的jMock和RMockJAR回页首TestExample源代码对于TestExample项目,您将使用来自四个类的源代码:
ServiceClass.javaCollaborator.javaICollaborator.javaServiceClassTest.java待测试的类将是ServiceClass,该类包含了一个方法:
runService()。
服务方法将获取实现简单接口ICollaborator的Collaborator对象。
具体的Collaborator类中实现了一个方法:
executeJob()。
Collaborator是必须正确模拟的类。
第四个类是测试类:
ServiceClassTest(实现的性质已经被尽可能地简化)。
清单1将显示第四个类的代码。
清单1.服务类的样例代码publicclassServiceClass{
publicServiceClass(){
//no-argsconstructor
}
publicbooleanrunService(ICollaboratorcollaborator){
if("success".equals(collaborator.executeJob())){
returntrue;
}
else
{
returnfalse;
}
}
}在ServiceClass类中,if...else代码块是一个简单的逻辑分支,根据测试期望说明选取一条路经——而不是另一条路经——之后测试将失败(或通过)的原因。
下面显示了Collaborator类的源代码。
清单2.Collaborator类的样例代码publicclassCollaboratorimplementsICollaborator{
publicCollaborator(){
//no-argsconstructor
}
publicStringexecuteJob(){
return"success";
}
}Collaborator类也十分简单,它配有无参数的构造函数以及从executeJob()方法返回的简单String。
下面的代码显示了ICollaborator类的代码。
publicinterfaceICollaborator{
publicabstractStringexecuteJob();
}接口ICollaborator有一个必须在Collaborator类中实现的方法。
以上代码就绪后,让我们继续检验怎样在各种场景中成功地运行ServiceClass类的测试。
回页首场景1:
使用jMock模拟接口测试ServiceClass类中的服务方法十分简单。
假定测试要求为证明runService()方法并未运行——换言之,返回的布尔结果是false。
在这种情况下,传递给runService()方法的ICollaborator对象被模拟为期望调用executeJob()方法,并返回除了“success”以外的字符串。
通过这种方法,确保把布尔字符串false返回给测试。
下面所示的是包含测试逻辑的ServiceClassTest类代码。
清单3.场景1的ServiceClassTest类样例代码importorg.jmock.Mock;
importorg.jmock.cglib.MockObjectTestCase;
publicclassServiceClassTestextendsMockObjectTestCase{
privateServiceClassserviceClass;
privateMockmockCollaborator;
privateICollaboratorcollaborator;
publicvoidsetUp(){
serviceClass=newServiceClass();
mockCollaborator=newMock(ICollaborator.class);
}
publicvoidtestRunServiceAndReturnFalse(){
mockCollaborator.expects(once()).method("executeJob").will(returnValue("failure"));
collaborator=(ICollaborator)mockCollaborator.proxy();
booleanresult=serviceClass.runService(collaborator);
assertFalse(result);
}
}编写测试的时机用测试模拟框架运行您自己的测试的最佳方法是利用test-first灵活方法。
首先创建测试并设定期望。
仅在测试失败后才编写实现以修正测试。
当测试运行正常时,您将编写另一个测试以检查稍后添加到待测试的类中的功能。
如果将在各种测试用例中执行公共操作,则在测试中包括setUp()方法是一种很好的想法。
包括tearDown()方法也很不错,但不作严格要求,除非要运行集成测试。
另请注意,使用jMock和RMock,框架将在测试运行结束时或测试运行期间在所有模拟对象中检查所有期望。
并不实际需要为每个模拟期望包括verify()方法。
当作为JUnit测试运行时,测试将通过,如下所示:
图3.场景1测试通过ServiceTestClass类将扩展jMockCGLIB的org.jmock.cglib.MockObjectTestCase类。
mockCollaborator是一个十分简单的org.jmock.JMock类。
通常,用jMock生成模拟对象有两种方法:
要模拟接口,则使用newMock(Class.class)方法要模拟具体类,则使用mock(Class.class,"identifier")方法必须注意的是怎样将模拟代理传递给ServiceClass类中的runService()方法。
使用jMock,您可以从已创建的模拟对象(其中期望已经被设定)中提取代理实现。
这一点在本文稍后的场景中至关重要,尤其是在涉及RMock的场景中。
回页首场景2:
使用jMock模拟带有默认构造函数的具体类假定ServiceClass类中的runService()方法仅接受Collaborator类的具体实现。
jMock能够确保先前的测试通过而无需更改期望吗?
是的,只要您能够构造简单默认样式的Collaborator类。
更改ServiceClass类中的runService()方法使其反映以下代码。
清单4.经过编辑的场景2的ServiceClass类publicclassServiceClass{
publicServiceClass(){
//no-argsconstructor
}
publicbooleanrunService(Collaboratorcollaborator){
if("success".equals(collaborator.executeJob())){
returntrue;
}
else{
returnfalse;
}
}
}ServiceClass类的if...else逻辑分支保持不变(为了清晰起见)。
同时,无参数构造函数仍然适用。
注,并不总是需要有创造性逻辑,例如while...do子句或for循环来正确地测试类的方法。
只要有针对类使用的对象的方法执行,简单的模拟期望就足以测试那些执行。
您还必须更改ServiceClassTest类以匹配场景,如下所示:
清单5.经过编辑的场景2的ServiceClassTest类...
privateServiceClassserviceClass;
privateMockmockCollaborator;
privateCollaboratorcollaborator;
publicvoidsetUp(){
serviceClass=newServiceClass();
mockCollaborator=mock(Collaborator.class,"mockCollaborator");
}
publicvoidtestRunServiceAndReturnFalse(){
mockCollaborator.expects(once()).method("executeJob").will(returnValue("failure"));
collaborator=(Collaborator)mockCollaborator.proxy();
booleanresult=serviceClass.runService(collaborator);
assertFalse(result);
}
}这里有几点需要注意。
第一,runService()方法签名已经不同于以往。
它现在不接受ICollaborator接口,而接受具体类实现(Collaborator类)。
就测试框架而言,此更改非常重大(注,虽然在本质上反对多态,但是我们将使用传递具体类的示例(仅供举例之用)。
在实际的面向对象的场景中绝对不能这样做)。
第二,模拟Collaborator类的方式已经更改。
使用jMockCGLIB库可以模拟具体类实现。
提供给jMockCGLIB的mock()方法的附加String参数被用作创建的模拟对象的标识符。
使用jMock(当然,还有RMock)时,在单一测试用例内每个模拟对象设置都要求有惟一标识符。
这对于在公共的setUp()方法中或在实际测试方法内定义的模拟对象来说是正确的。
第三,测试方法的原始期望并未更改。
仍然要求有false证明才能使测试通过。
这是十分重要的,因为通过展示使用的测试框架足够灵活、可以适应各种输入带来的更改、同时仍然允许获得不变的测试结果,使它们在无法调节输入生成同样的结果时展示了其实际限制。
现在,重新运行作为JUnit测试的测试。
测试将通过,如下所示:
图4.场景2测试通过在下一个场景中,情况会变得略微复杂一些。
您将使用RMock框架来相对缓解一下这种困难的情形。
回页首场景3:
使用jMock和RMock模拟带有非默认构造函数的具体类首先像以前一样尝试使用jMock来模拟Collaborator对象——只是这一次,Collaborator没有默认的无参数构造函数。
注,保留布尔false结果的测试期望。
同时假定Collaborator对象要求使用字符串和原始的int作为传递给构造函数的参数。
清单6显示了对Collaborator对象所做的更改。
清单6.经过编辑的场景3的Collaborator类publicclassCollaborator{
privateStringcollaboratorString;
privateintcollaboratorInt;
publicCollaborator(Stringstring,intnumber){
collaboratorString=string;
collaboratorInt=number;
}
publicStringexecuteJob(){
return"success";
}
}Collaborator类构造函数仍然十分简单。
用传入参数设定类字段。
这里不必使用任何其他逻辑,并且其executeJob()函数保持不变。
重新运行测试,并且示例的所有其他组件保持不变。
结果是灾难性的测试失败,如下所示:
图5.场景3测试失败以上测试是作为简单的JUnit测试运行的,没有代码覆盖。
您可以用大多数代码覆盖工具(例如,Cobertura或EclEmma)来运行本文中列出的任何一个测试。
但是,用Eclipse内的代码覆盖工具运行RMock测试时会带来一些问题(参见表1)。
以下代码显示了实际堆栈跟踪的代码片段。
清单7.场景3中测试失败的堆栈跟踪...Superclasshasnonullconstructorsbutnoargumentsweregiven
atnet.sf.cglib.proxy.Enhancer.emitConstructors(Enhancer.java:
718)
atnet.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:
499)
atnet.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:
25)
atnet.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:
216)
atnet.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:
377)
atnet.sf.cglib.proxy.Enhancer.create(Enhancer.java:
285)
atnet.sf.cglib.proxy.Enhancer.create(Enhancer.java:
660)
.....
.....失败原因是jMock无法通过没有无参数构造函数的类定义创建可行的模拟对象。
实例化Collaborator对象的惟一方法是提供两个简单参数。
您现在必须找到一种方法把参数提供给模拟对象实例化过程以达到同样的效果,这就是使用RMock的原因。
用RMock测试框架更正失败的测试要更正测试,必须执行一些修改。
这些更改可能显得十分重要,但是实际上,它们是一种相对简单的解决方法,利用两种框架的强大功能来实现目的。
必需的第一项更改是使测试类成为RMockTestCase,而不是成为jMockCGLIBTestCase。
目的是在测试本身内启用属于RMock的那些模拟对象的较容易的配置并且——更重要的是——在最初设置期间启用这些配置。
经验证明,如果测试类扩展的整个TestCase对象属于RMock,则通过两个框架构造和使用模拟对象将更容易。
此外,乍看之下,快速确定模拟对象的流程将更容易一些(在这里,流程用于描述使用模拟对象作为参数甚或作为其他模拟对象的返回类型的情况)。
必需的第二项更改是(至少)构造一个保存传递给Collaborator类的构造函数的参数实际值的对象数组。
为了清晰起见,还可以包括构造函数接受的类类型的类型数组并可以传递该数组,以及刚刚描述为参数的对象数组以实例化模拟Collaborator对象。
第三项更改涉及用正确语法构造对RMock模拟对象的一个或多个期望。
而第四项也是最后一项必需的更改是使RMock模拟对象脱离记录状态转入就绪状态。
实现RMock更改清单9显示了对ServiceClassTest类的最终修改。
它还显示了RMock及其相关功能的引入。
清单9.修正场景3的ServiceClassTest类...
importcom.agical.rmock.extension.junit.RMockTestCase;
publicclassServiceClassTestextendsRMockTestCase{
privateServiceClassserviceClass;
privateCollaboratorcollaborator;
publicvoidsetUp(){
serviceClass=newServiceClass();
Object[]objectArray=newObject[]{"exampleString",5};
collaborator=(Collaborator)intercept(Collaborator.class,objectArray,"mockCollaborator");
}
publicvoidtestRunServiceAndReturnFalse(){
collaborator.executeJob();
modify().returnValue("failure");
startVerification();
booleanresult=serviceClass.runService(collaborator);
assertFalse(result);
}
}首先,需要注意测试的期望仍未改变。
RMockTestCase类的导入预示着引入RMock框架功能。
接下来,测试类现在将扩展RMockTestCase,而不是MockObjectTestCase。
稍后,我将向您展示在TestClass对象仍为RMockTestCase类型的对象的测试用例中重新引入MockObjectTestCase。
使用intercept()方法的备选方法通过RMock,您可以使用intercept()方法仅模拟具体类。
可以使用RMockmock()方法模拟具体类和接口。
仅当需要模拟那几个重要方法时,使用interface()方法。
此方法被视为经过改进的mock()方法。
在setUp()方法内,用Collaborator类的构造方法所需的实际值实例化对象数组。
该数组被立刻传递给RMock的intercept()方法来帮助实例化模拟对象。
方法的签名类似于jMockCGLIBmock()方法的签名,因为这两种方法将接纳惟一模拟对象标识符作为参数。
模拟对象到Collaborator类型的类强制转换十分有必要,因为intercept()方法将返回类型Object。
在测试方法本身testRunServiceAndReturnFalse()之内,您可以看到更多更改。
模拟Collaborator对象的executeJob()方法将被调用。
在此阶段,模拟对象处于记录状态——即简单地定义对象将一直期望的方法调用,因此,模拟将相应地记录期望。
下一行是对模拟对象的通知,用于确保当它遇到executeJob()方法时,它应当返回字符串failure。
因此,使用RMock,您只需通过调用方法而不调用模拟对象(并传递它可能需要的任何参数)来描述期望,然后修改该期望以相应地调整任何返回类型。
最后,调用RMock方法startVerification()把模拟Collaborator对象转为就绪状态。
模拟对象现已准备好在ServiceClass类中作为实际对象使用。
该方法非常重要并且必须调用它才能避免测试初始化失败。
测试更改再次重新运行ServiceClassTest以达到最终的肯定结果:
在模拟对象实例化期间提供的参数造成了所有差别。
图6显示JUnit测试已经通过。
图6.使用RMock的场景3测试成功assertFalse(result)代码行表示与场景1相同的测试期望,而RMock像jMock以前那样维持测试成功。
在许多方面,这都十分重要,但是这里更重要的特点可能是实践了修正失败测试的灵活原则而不更改测试期望。
惟一的差别是使用了另一个框架。
在下一个场景中,您将在一种特殊情况下使用jMock和RMock。
没有一个框架能够仅凭自身就实现正确结果,除非在测试内形成某种联合。
回页首场景4:
jMock和RMock之间的特定协作如前所述,我希望检验两个框架必须协同工作才能实现某个结果的情况。
否则,构建良好的测试每次都将失败。
在某些情况下,使用jMock还是RMock并不重要,例如,当需要模拟的接口或类存在于已经签名的JAR中时。
此类情况十分少见,但是当测试针对安全专有的产品(通常是这样或那样的一类现有软件)中的应用程序编程接口(API)编写代码时可能会出现此情况。
清单10显示了两个框架完成测试用例的示例。
清单10.场景4的测试示例点击查看代码清单在setUp()方法内,根据为扩展jMock-CGLIBMockObjectTestCase对象而创建的私有内部类实例化了新"testcase"。
使用任何jMock功能时,这个小解决方法对于确保整个测试类为RMockTestCase对象十分有必要。
例如,您将设定类似testCase.once()而不是类似once()的jMock期望,因为Tes
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 利用 Eclipse 进行单元测试 进行 单元测试