jmockit总结.docx
- 文档编号:4097233
- 上传时间:2023-05-06
- 格式:DOCX
- 页数:22
- 大小:26KB
jmockit总结.docx
《jmockit总结.docx》由会员分享,可在线阅读,更多相关《jmockit总结.docx(22页珍藏版)》请在冰点文库上搜索。
jmockit总结
jmockit
行为和状态的测试:
基于行为的mock是站在目标测试代码外面的角度的.通常主要模拟行为。
而基于状态的是站在目标测试代码内部的。
我们可以对传入的参数进行检查、匹配,才返回某些结果。
Mockup用于statebased测试。
一定要理解,单元测试测什么:
单元测试的原则是哪怕你测试的方法中有一行代码,也是有必要写测试方法的。
所以不要担心,你测试的业务逻辑最后只剩下边界值测试。
你可以根据一些MOCK的返回值比如空,非空,个数等来测试你的业务逻辑是否正确。
如果业务方法依赖于第三方类库,缓存,消息队列,DAO层的方法都是可以MOCK的。
以前的思路:
Service方法依赖于SDK,针对这些方法的单元测试需要与微博交互返回正确的AccessToken,这样才能Service做有意义的单元测试。
这是典型的依赖于第三方或远程调用的场景。
正确的思路:
新浪的API已经由新浪团队测试过。
我假设它完全正确,只需要模拟它的返回值来测试我的“业务逻辑”在mock指定值下的反应。
就象有些Service方法依赖于DAO层,如果DAO层的单元测试充分(比如通过DBUNIT等工具模拟数据),那么在业务层的单元测试中,只需要mockDAO层即可。
Mock的原因:
1)一些依赖单元本身已经(或者将来会有,只是目前还没有实现而已)拥有自己的单元测试。
2)由于一些特殊原因,在测试环境中,一些并不是很容易快速的执行的单元(因为它们可能会写数据库或者发送邮件等等)。
Dependency是什么?
通过使用mocked声明,可以在指定的测试代码对一些特殊的依赖(比如新浪微博的API)进行mock模拟,也就是说,一个mocked类型,应该是单元测试中的一个依赖类型,这些类型可以是引用、接口、抽象类、具体的类、final类等等。
@mocked是去修饰Dependency的。
二种指定要mocked的Dependency:
1.传统的方式:
在Expectations里面。
@Test
publicvoiddoBusinessOperationXyz()
{
...
newExpectations()//an"expectationblock"
{
DependencymockInstance;//"Dependency"isourmockedtypeforthistest
...
{
...
//"mockInstance"isamockedinstanceautomaticallyprovidedforuseinthetest
mockInstance.mockedMethod(...);
...
}
};
...
}
2.但大多数情况下,例如变量都是通过一些特定的注解及注解本身的属性来指定mock,例如@Mocked,@NonStrict,@Injectable等等。
这些注解标记可以在实例的域或者测试方法的参数中使用。
注:
不管哪种方式,默认情况下,被mock的类型的所有方法(无论方法或者构造函数的修饰符是否是private,stati,final,native等,这些方法和构造函数都会被mock掉)在测试期间都被mock实现。
如果一个mock类型被声明为类,那么除了java.lang.Object之外,该类的父类将被递归mock。
Expectation:
Anexpectationisasetofinvocationstoanspecificmockedmethod/constructorthatisrelevantforagiventest.
为单元测试服务的一组invocations定义。
单元测试的replay阶段会invoke这些invocations中的某个matchinvocation。
当单元测试中的方法的签名,运行时的参数(比如invocation的参数值,调用次数)都能match的Expectation中定义的某个invocation时候,该invocation所模拟的行为才会被触发。
Invocation:
单元测试replay阶段invoke一个invocation。
如果这个定义在Expectation中的invocation被match到的话。
Record-Replay-Verify模型(jmockit就是遵循这套模型)
任何一个测试至少可以划分三个相互独立的阶段,这些阶段将按顺序执行,一次执行一个阶段
1. @Test
2. public void someTestMethod()
3. {
4. // 1. 准备阶段:
测试执行之前所需要的所有东西都可以编写在这里
5. ...
6. // 2. 单元测试代码在这里执行,通常是通过调用public方法来执行
7. ...
8. // 3. 验证阶段:
验证上面所执行的单元测试代码真正执行了其业务逻辑
9. ...
10. }
首先,我们有一个准备阶段,单元测试代码所需要的对象和数据都可以在这里创建或者从其他地方加载进来。
之后,测试的代码被执行。
最后,测试结果和期望结果进行比较。
(1)Record(记录)阶段:
在这里将记录下哪些调用会被期望执行,这些都是发生在测试的准备阶段,而且在真正测试代码执行调用之前。
(2)Replay(重播)阶段:
我们感兴趣的mock调用将在这里有机会被执行,就好像真正的单元测试代码被执行一样。
这些在Record阶段记录下来的mock方法/构造函数调用将在这里重播(执行),尽管这些mock调用在record和replay阶段通常不是一对一对应的。
(3)Verify(验证)阶段:
所有的真实调用应该在这里和期望值进行校验,这些动作发生在测试验证阶段
JMockit工具来编写基于行为的测试代码,通常符合下面的经典模板:
importmockit.*;
...otherimports...
publicclassSomeTest
{
//零个或者更多的mock属性,这些属性对于整个类的所有测试方法来说是通用的。
@MockedCollaboratormockCollaborator;
@NonStrictAnotherDependencyanotherDependency;
...
@Test
publicvoidtestWithRecordAndReplayOnly(mockparameters)
{
//如果这里需要测试前的准备,可以在这里执行,但对于Jmockit来说,对此没特别要求。
当然这里也可以为空。
newExpectations()//一个期望块.
{
//零个或者多个局部mock属性域
{
//一个或者多个mock对象(类型)的调用,这些调用会被Expectations记录(Recorded)下来
//一些没有被mock的方法、对象类型等同样可以在这个期望块里面调用
}
};
//单元测试代码真正业务逻辑在此执行
//如果需要,可以在这里进行验证代码编写,当然可以利用JUnit/TestNG断言
}
@Test
publicvoidtestWithReplayAndVerifyOnly(mockparameters)
{
//如果这里需要测试前的准备,可以在这里执行,但对于Jmockit来说,对此没特别要求。
当然这里也可以为空。
//单元测试代码真正业务逻辑在此执行
newVerifications(){{//一个验证块
//一个或者多个mock对象(类型)的调用,这些调用用于验证结果是否正确
//一些没有被mock的方法、对象类型等同样可以在这个验证块里面调用
}};
//如果需求,这里可以添加其他额外的验证代码,
//当然,这些验证可以编写在这里,也可以在Verifications块之前
}
@Test
publicvoidtestWithBothRecordAndVerify(mockparameters)
{
//如果这里需要测试前的准备,可以在这里执行,但对于Jmockit来说,对此没特别要求。
当然这里也可以为空。
newNonStrictExpectations(){//同样是一个期望块
//零个或者多个局部mock属性域
{
//一个或者多个mock对象(类型)的调用,这些调用会被Expectations记录(Recorded)下来
}
};
//单元测试代码真正业务逻辑在此执行
newVerificationsInOrder(){{//同样是一个验证块
//一个或者多个mock对象(类型)的调用,这些调用将期望按照特定的顺序进行比较。
}};
//如果需求,这里可以添加其他额外的验证代码,
//当然,这些验证可以编写在这里,也可以在Verifications块之前
}
期望块与验证块的位置及重复:
一个测试方法可以包含任意数量(含零个)的期望块,验证块也是一样。
如何声明和使用mock类型:
1)字段,期望块外的字段与期望块内的局部属性字段使用@Mocked来声明Mock类型。
2)参数,方法的参数声明来引入一个Mock类型。
第一种情况,属性字段是属于测试类或者一个mockit.Expectations子类(一个expectation期望块的内部的局部属性字段)。
第二种情况,参数必须是属于某个测试方法。
在所有情况,一个mock属性字段或者参数声明,都可以通过使用@Mocked声明。
对于方法mock的参数或者在expectation期望块中定义的mock属性字段来说,该注解是可选的。
可选的原因:
注解@Mocked(或者其他mock的注解类型,例如@NonStrict)只是对于定义在测试类中的属性字段域才是必须的,这是为了防止和该测试类的其他不需要mock的字段属性产生冲突而已。
publicinterfaceDependency//anarbitrarycustominterface
{
StringdoSomething(booleanb);
}
publicfinalclassMultiMocksTest
{
@MockedMultiMockmultiMock;
@Test
publicvoidmockFieldWithTwoInterfaces()
{
newNonStrictExpectations(){{
multiMock.doSomething(false);result="test";
}};
multiMock.run();
assertEquals("test",multiMock.doSomething(false));
newVerifications(){{multiMock.run();}};
}
@Test
public
{
newExpectations(){{
mock.doSomething(true);result=""
}};
assertEquals("",mock.doSomething(true));
}
}
对于一个返回值不为void类型的方法,Expectation中如何模拟方法返回值:
1)其返回值可以通过Expectations#result属性域
2)Expectations#returns(Object)方法来进行记录(Recorded)。
如果这个测试需要获取一个异常(exception)或者错误(error)时,_result属性域同样可以使用。
很简单,此时只需要将一个throwable实例赋值给它就可以了。
*Expectations中模拟方法返回多个可能的示例:
场景:
下面的代码是要测试的业务方法。
该业务方法依赖了第三方的DependencyAbc类。
通过mock来分别模拟
(1)
(2)(3)。
publicclassUnitUnderTest
{
(1)privatefinalDependencyAbcabc=newDependencyAbc();
publicvoiddoSomething()
{
(2)intn=abc.intReturningMethod();
for(inti=0;i Strings; try{ (3)s=abc.stringReturningMethod(); } catch(SomeCheckedExceptione){ //处理异常 } //这里可以处理其他逻辑 } } } @Test publicvoiddoSomethingHandlesSomeCheckedException()throwsException { newExpectations(){ DependencyAbcabc; { (1)newDependencyAbc();//构造方法的模拟。 (2)abc.intReturningMethod();result=3; (3)abc.stringReturningMethod(); returns("str1","str2"); result=newSomeCheckedException();//注意,mock异常只能通过result属性。 } }; newUnitUnderTest().doSomething(); } 第一个(其实就是DependencyAbc()的构造函数调用)实际上会在测试代码中通过一个无参的构造函数来初始化这些依赖,对于这种调用是不需要任何返回值的,除非在构造函数里面抛出一个异常或者错误(其实构造函数是没有返回值的,所以对它来说记录返回值是没什么意义可说)。 第二个期望指定调用intReturningMethod()后将返回值3。 第三个期望就是,调用stringReturningMethod()方法后将按顺序返回3个连续的期望值。 默认mock与默认返回值: 1.对于一个返回值不是void的mock方法,无论是否匹配上在Record阶段定义的调用期望,都应该对该方法提供默认的返回值。 Jmockit总会根据定义返回值的类型返回一个值: 对于整型缺省返回0,boolean类型默认为false,collection或者array会默认为empty对象,而对于引用类型,则默认为null(包括String类型和JDK原始的包装类)。 2.mocked的构造函数和返回值为void的mocked方法,也提供一个"缺省值",只不过就是简单的return而已,当然没有抛出异常或者错误。 ! Expectationinvocation与NonStrictExpectationsinvocation的使用: Expectationinvocation(严格期望 or 严格invocation): 在期望块newExpectations(){...}中,默认所有被记录下来的期望都是严格的。 这意味着,这些期望的调用[必须]在重播阶段被执行,而且需要按照声明的期望指定的[执行顺序执行]。 而且,[也只允许这些调用被执行]。 [任何一个没有被记录下来的非期望调用]都会造成测试用例失败。 NonStrictExpectationsinvocation(非严格期望or非严格invocation): 在一个非严格的期望块中,所有的被mock的类型的所有调用都可以在重播阶段执行,即使不是在期望中声明的,比如默认mocked的invocation。 这在Expectation中完全不可以的。 也就是说,默认情况下,在重播阶段是否执行mock类型的调用是不会造成测试用例失败的。 同样,这种不严格的期望是不要求调用的执行顺序的。 缺省情况下,一个严格的期望会精确匹配重播阶段的一个调用。 换而言之,这类型的期望是存在一个隐式的调用次数约束1,就好像它后面紧跟着times=1这个约束。 而另一方面,对于一个非严格的期望,默认是可以匹配重播阶段调用的任意次数的.注意,默认总是可以被显示指定复盖。 比如指定times/minTimes/maxTimes属性字段。 对于一个严格的期望,所有在重播阶段被期望所匹配的调用,都会隐式被校验(Verify)通过的。 剩余的调用则被认为不符合期望(即造成测试失败,哪怕这些调用是针对@Mocked形成的默认mocked的方法。 ),除非,这个mock的类型被关联到一个非严格的期望上。 所以,使用隐式验证的严格期望需要排除verification块的使用,这个块是用于调用的显式校验的。 实际上,在newVerifications(){...}代码块中,只有那些匹配非严格期望的调用才被允许使用。 从严格到非严格: 1. 除了使用针对期望块的NonStrictExpectations外,还可以使用(@NonStrict注解类属性+Expectation块)做到非严格效果,注意@NonStrict它是针对类的属性非严格,类的属性适用于类中的所有测试方法。 这个非严格的范围比非严格期望块的作用范围大很多。 一旦其它的测试方法还想在该属性的基础上使用Expectation块,是不可能的了。 如果是这种情况,就需要这样使用(@Mocked注解类属性+NonStrictExpectations块)。 2.@NonStrict可以避免需要记录调用构造函数,或任何不感兴趣的方法。 3.一旦使用了@NonStrict,Expectation中的invocation就变成了非严格的invocation。 可以在replay调用或不调用。 总结三种不同严格性的方式: 1)在一个给定的严格期望块中,如果需要指定某一个期望是非严格的,可以调用notStrict()注解方法。 2)对于一个特殊的mock类型/实例,其所有期望都需要是完全非严格的,则可以通过注解@NonStrict将其声明为一个mock属性字段或者参数。 3)如果在一个期望块中,需要所有的期望都是非严格的,则可以使用NonStrictExpectations类。 注意,即使使用NonStrictExpectations,它里面的invocation还是必须在replay阶段执行。 非严格的含义在于replay阶段调用非期望的方法是否失败。 示例: publicclassIntroductionTest{ //不管使用下面的二种注解之一,NonStrictExpectations块中的invocation还是必须在replay的时候调用。 只不过比起Expectation块,replay阶段还可以invocate非严格的invocation //getWinportUrlThrowException而不会失败。 //@NonStrict @Mocked privateWinportUrlServicewinportUrlService=null; @Test publicvoidtestNoExpectations(){ finalStringmemberId="test2009"; Assert.assertEquals(false,winportUrlService.hasWinport(memberId)); } @Test publicvoidtestWithExpectations(){ finalStringmemberId="test2009"; newNonStrictExpectations(){ { //下面的invocation必须出现在replay阶段。 winportUrlService.hasWinport(memberId); result=false;//也可以是returns(false); //总共可以调用的次数 times=2; } }; //步骤二、replay //如果注了下面二句,肯定失败。 并且只invocate一次也不行。 Assert.assertEquals(false,winportUrlService.hasWinport(memberId)); Assert.assertEquals(false,winportUrlService.hasWinport(memberId)); winportUrlService.getWinportUrlThrowException(memberId); } } publicclassIntroductionTest{ @NonStrict//使用@NonStrict才可以在replay阶段invocategetWinportUrlThrowException。 privateWinportUrlServicewinportUrlService=null; @Test publicvoidtestNoExpectations(){ finalStringmemberId="test2009"; Assert.assertEquals(false,winportUrlService.hasWinport(memberId)); } @Test publicvoidtestWithExpectations(){ finalStringmemberId="test2009"; newExpectations(){
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- jmockit 总结