更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注我的的微信号,感谢!html
原文连接:http://www.cnblogs.com/zishi/p/6760272.htmljava
Mock工具Jmockit使用介绍git
在写单元测试的过程当中咱们会发现须要测试的类有不少依赖,这些依赖的类或者资源又会有依赖,致使在单元测试代码里没法完成构建,咱们应对的方法是Mock。简单的说就是模拟这些须要构建的类或者资源,提供给须要测试的对象使用。github
可用的Mock Toolkit有许多,比较常见的有EasyMock, Jmock和JMockit等等,到底选哪一个呢,Jmockit的官网上有个特性对比列表,很详细,他们的功能对好比下:web
Featurespring |
EasyMock数据库 |
jMockapache |
Mockito微信 |
Unitils Mockapp |
PowerMock: |
PowerMock: |
|
√ |
√ |
√ |
√ |
√ |
√ |
||
√ |
√ |
√ |
√ |
||||
√ |
√ |
√ |
√ |
||||
√ |
√ |
√ |
√ |
√ |
√ |
||
√ |
√ |
√ |
√ |
||||
N/A |
N/A |
N/A |
√ |
||||
No extra "prepare for test" code |
√ |
√ |
√ |
√ |
√ |
||
No need to use @RunWith annotation or base |
√ |
√ |
√ |
√ |
|||
Consistent syntax between void and non-void methods |
√ |
√ |
√ |
||||
Argument matchers for some parameters only, |
√ |
√ |
|||||
Easier argument matching based on properties |
√ |
√ |
√ |
√ |
√ |
√ |
|
√ |
√ |
√ |
√ |
||||
√ |
√ |
√ |
|||||
√ |
√ |
√ |
√ |
√ |
|||
√ |
√ |
||||||
Mocking of constructors and final/static/native/private methods |
√ |
√ |
√ |
||||
Declarative application of mocks/stubs to |
√ |
√ |
√ |
||||
√ |
√ |
√ |
√ |
||||
Mocking of "new-ed" objects |
√ |
√ |
√ |
||||
√ |
√ |
√ |
|||||
Declarative mocks for the test class (mock |
√ |
√ |
√ |
√ |
√ |
||
Declarative mocks for test methods |
√ |
||||||
√ |
|||||||
√ |
|||||||
Use of special fields to specify invocation |
√ |
||||||
√ |
|||||||
√ |
|||||||
√ |
|||||||
√ |
|||||||
√ |
|||||||
√ |
|||||||
Single jar file in the classpath is sufficient to |
√ |
N/A |
N/A |
√ |
|||
Total |
6/32 |
7/32 |
13/31 |
11/31 |
9/31 |
14/30 |
32/32 |
Total when ignoring JMockit-only features |
6/22 |
7/22 |
13/21 |
11/21 |
9/21 |
14/20 |
22/22 |
EasyMock 以及 Mockito 都由于能够极大地简化单元测试的书写过程而被许多人应用在本身的工做中,可是这两种 Mock 工具都不能够实现对静态函数、构造函数、私有函数、Final 函数以及系统函数的模拟,可是这些方法每每是咱们在大型系统中须要的功能。
关于更多Mockito2.0新特性,参考官方介绍文档,里边有关于为何不mock private的缘由,挺有意思的:
https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2
Mockito 的使用
###Maven###
经过Maven管理的,须要在项目的Pom.xml中增长以下的依赖:
<dependencies> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.9.0</version> <scope>test</scope> </dependency> </dependencies>
在程序中能够import org.mockito.Mockito,而后调用它的static方法。
建立 Mock 对象的语法为 mock(class or interface)。
经过 when(mock.someMethod()).thenReturn(value) 来设定 Mock 对象某个方法调用时的返回值。或者使用 when(mock.someMethod()).thenThrow(new RuntimeException) 的方式来设定当调用某个方法时抛出的异常。
Mock 对象一旦创建便会自动记录本身的交互行为,因此咱们能够有选择的对它的 交互行为进行验证。在 Mockito 中验证 Mock 对象交互行为的方法是 verify(mock).someMethod(…)。最后 Assert() 验证返回值是否和预期同样。
Mock 对象的建立
mock(Class classToMock);
mock(Class classToMock, String name)
mock(Class classToMock, Answer defaultAnswer)
mock(Class classToMock, MockSettings mockSettings)
mock(Class classToMock, ReturnValues returnValues)
能够对类和接口进行mock对象的建立,建立时能够为mock对象命名。对mock对象命名的好处是调试的时候容易辨认mock对象。
Mock对象的指望行为和返回值设定
假设咱们建立了LinkedList类的mock对象:
LinkedList mockedList = mock(LinkedList.class);
PowerMock 是在 EasyMock 以及 Mockito 基础上的扩展,经过定制类加载器等技术,PowerMock 实现了以前提到的全部模拟功能,使其成为大型系统上单元测试中的必备工具。缺点是缺乏文档。
mock是模拟对象,用于模拟真实对象的行为。
Powermock主要用于打桩。好比:方法A的参数须要传入实例B,方法A须要调用B的某个方法B.C()。方法C由于耗时长或者根本没有实现或者其余不方便在单元测试中实现等缘由,须要伪造返回,此时Powermock便可派上用场。
PowerMock扩展了EasyMock和Mockito框架,增长了对static和final方法mock支持等功能。这里主要基于PowerMock Mockito API进行介绍。
PowerMock支持JUnit和TestNG,这里基于JUnit。
安装
下载地址:https://github.com/jayway/powermock/wiki/Downloads。下载" Mockito and JUnit including dependencies"版本。当前版本为”powermock-mockito-junit-1.6.3.zip"。
###Mock###
所谓的mock,即模拟,模仿的意思。Mock 技术的主要做用是使用mock工具模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试目标与测试边界之外的对象隔离开。
###Stub###
Stub,桩。单元测试过程当中,对于在应用中不容易构造或者比较复杂的对象,用一个虚拟的对象来代替它。从类的实现方式上看,stub有一个显式的类实现,按照stub类的复用层次能够实现为普通类(被多个测试案例复用),内部类(被同一个测试案例的多个测试方法复用)乃至内部匿名类(只用于当前测试方法)。stub的方法也会有具体的实现,哪怕简单到只有一个简单的return语句。
###Stub 与 Mock 的区别###
Stub 是在单元测试过程当中去代替某些对象来提供所需的测试数据,适用于基于状态的(state-based)测试,关注的是输入和输出。而Mock适用于基于交互的(interaction-based)测试,关注的是交互过程,不仅是模拟状态,还可以模拟模块或对象的行为逻辑并能验证其正确性,Mock不须要类的显示实现,直接用工具模拟。
综合考量下来,因此咱们的mock工具也选择了jmockit(http://jmockit.org/index.html)
关于如何使用:
推荐:在Maven 的pom.xml文件中添加如下依赖节点:
<dependency> <groupId>org.jmockit</groupId> <artifactId>jmockit</artifactId> <version>1.30</version> <scope>test</scope> </dependency>
固然也能够在项目中直接引入jar包。
JMockit模拟API可用于JUnit 4(版本4.5或更高版本),JUnit 5或TestNG(版本6.2或更高版本)编写测试。 如今让咱们看看这个API是如何进行模拟的,为了便于理解,下面引用官网给出的范例代码,同时咱们将必要的类库进行引用。
在测试类中,声明一个你想要模拟的类型的mock字段,并用@Mocked,@Injectable或@Capturing注释。当模拟类时,@Injectable意味着只有被分配mock字段的实例将具备mock行为; 不然,被mock的类的全部实例将被mock。
import org.junit.*; import mockit.*; public class MyFirstJMockitTest { // Mocked实例(而不是常规的“mock对象”)将自动建立并分配到带注释的mock字段 @Mocked Collaborator mock1; //全部当前和将来的实例都会被mock @Injectable AnotherDependency anotherMock; //只有一个特定实例被mock @Test public void myFirstTestMethod() { //任何mock字段均可以在这里或者类的任何其余测试方法中使用 } @Test public void testMethodWithMockParameter(@Mocked YetAnotherDependency testSpecificMock) { ... } ... }
注意:上面的测试类显示了一些不一样的东西:第二个测试方法声明一个参数! 一般,JUnit / TestNG测试方法不容许有参数。 然而,当使用JMockit时,容许这样的模拟参数。 通常来讲,只有测试类中大多数或全部测试都须要Mock类型时,才使用测试类的Mock字段。 不然,Mock的范围最好仅限于单个测试的Mock参数。 JMockit将老是关注实例化Mock类型,而且当测试运行器调用测试方法时,将实例分配给mock字段(假设字段不是final)或将其做为参数传递。
要Mock测试的方法以下:
public class MyObject { public String hello(String name){ return "Hello " + name; } }
使用JMockit编写的单元测试以下:
@Mocked //用@Mocked标注的对象,不须要赋值,jmockit自动mock MyObject obj; @Test public void testHello() { new NonStrictExpectations() {//录制预期模拟行为 { obj.hello("Zhangsan"); returns("Hello Zhangsan"); //也可使用:result = "Hello Zhangsan"; } }; assertEquals("Hello Zhangsan", obj.hello("Zhangsan"));//调用测试方法 new Verifications() {//验证预期Mock行为被调用 { obj.hello("Hello Zhangsan"); times = 1; } }; }
代码完成后,运行单元测试,结果以下:
JMockit也能够分类为非局部模拟与局部模拟,区分在于Expectations块是否有参数,有参数的是局部模拟,反之是非局部模拟。
而Expectations块通常由Expectations类和NonStrictExpectations类定义,相似于EasyMock和PowerMock中的Strict Mock和通常性Mock。
用Expectations类定义的,则mock对象在运行时只能按照 Expectations块中定义的顺序依次调用方法,不能多调用也不能少调用,因此能够省略掉Verifications块;
而用NonStrictExpectations类定义的,则没有这些限制,因此若是须要验证,则要添加Verifications块。
上述的例子使用了非局部模拟,下面咱们使用局部模拟来改写上面的测试,代码以下:
@Test public void testHello() { final MyObject obj = new MyObject(); new NonStrictExpectations(obj) {//录制预期模拟行为 { obj.hello("Zhangsan"); returns("Hello Zhangsan"); //也可使用:result = "Hello Zhangsan"; } }; assertEquals("Hello Zhangsan", obj.hello("Zhangsan"));//调用测试方法 new Verifications() {//验证预期Mock行为被调用 { obj.hello("Hello Zhangsan"); times = 1; } }; }
模拟静态方法:
@Test public void testMockStaticMethod() { new NonStrictExpectations(ClassMocked.class) { { ClassMocked.getDouble(1);//也可使用参数匹配:ClassMocked.getDouble(anyDouble); result = 3; } }; assertEquals(3, ClassMocked.getDouble(1)); new Verifications() { { ClassMocked.getDouble(1); times = 1; } }; }
模拟私有方法:
若是ClassMocked类中的getTripleString(int)方法指定调用一个私有的multiply3(int)的方法,咱们可使用以下方式来Mock:
@Test public void testMockPrivateMethod() throws Exception { final ClassMocked obj = new ClassMocked(); new NonStrictExpectations(obj) { { this.invoke(obj, "multiply3", 1);//若是私有方法是静态的,可使用:this.invoke(null, "multiply3") result = 4; } }; String actual = obj.getTripleString(1); assertEquals("4", actual); new Verifications() { { this.invoke(obj, "multiply3", 1); times = 1; } }; }
接下来咱们用Jmockit实现一个具体的单元测试,首先下面是一段Controller的功能代码:
import com.odde.mail.model.Result; import com.odde.mail.service.MailService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.jackson.map.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import static java.lang.String.format; @Controller @RequestMapping("/mail") public class MailController { private static final Log log = LogFactory.getLog(MailController.class); private final ObjectMapper mapper = new ObjectMapper(); @Autowired private MailService mailService; @RequestMapping(value = "/send", method = RequestMethod.POST, produces = "text/plain;charset=UTF-8") public @ResponseBody String send(@RequestParam("recipients") String recipients, @RequestParam("subject") String subject, @RequestParam("content") String content) throws Exception { log.debug("mail controller send start"); log.debug(format("recipients:%s", recipients)); log.debug(format("subject:%s", subject)); log.debug(format("content:%s", content)); Result mailResult = mailService.send(recipients, subject, content); String result = mapper.writeValueAsString(mailResult); log.debug(format("result:%s", result)); log.debug("mail controller send finish"); return result; } }
接下来咱们看一下Jmockit实现的具体的单元测试代码:
import com.odde.mail.model.Result; import com.odde.mail.service.MailService; import mockit.Expectations; import mockit.Injectable; import mockit.Tested; import mockit.integration.junit4.JMockit; import org.junit.Test; import org.junit.runner.RunWith; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @RunWith(JMockit.class) public class MailControllerTest { @Tested MailController mailController; @Injectable private MailService mailService; @Test public void should_return_status_success_when_send_mail_success() throws Exception { new Expectations() { { mailService.send("test@test.com", "test", "test"); result = new Result("成功"); } }; String result = mailController.send("test@test.com", "test", "test"); assertThat(result, is("{\"status\":\"成功\"}")); } }
作完上面这些基本就能够了,后面的被测方法调用和验证都跟原来的同样。这样看起来是否是比原来的单元测试代码少了一些,也更简洁了一些,最重要的一点是这样的单元测试不依赖spring的bean定义文件,不须要启动web服务,执行起来速度很快。
首先仍然是先看一下Service的功能代码,代码也比较简单,就是调用Repository作一些增删改查的动做。
import com.odde.mail.model.Recipient; import com.odde.mail.model.Result; import com.odde.mail.repo.RecipientRepository; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class RecipientService { @Autowired private RecipientRepository recipientRepository; public Result add(String username, String email) { Recipient recipient = recipientRepository.findByEmail(email); Result result; if (recipient == null) { recipientRepository.save(new Recipient(username, email)); result = new Result("成功"); } else { result = new Result("失败"); } return result; } }
接着就是它的单元测试代码,咱们看一下Jmockit如何实现的mock代码:
import com.odde.mail.model.Recipient; import com.odde.mail.model.Result; import com.odde.mail.repo.RecipientRepository; import mockit.Injectable; import mockit.NonStrictExpectations; import mockit.Tested; import mockit.integration.junit4.JMockit; import org.junit.Test; import org.junit.runner.RunWith; import java.util.List; import static java.util.Arrays.asList; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; @RunWith(JMockit.class) public class RecipientServiceTest { @Tested private RecipientService recipientService; @Injectable private RecipientRepository recipientRepository; @Test public void should_return_success_when_add_recipient_not_exist() throws Exception { Result result = recipientService.add("Tom", "test@test.com"); assertThat(result.getStatus(), is("成功")); } }
相对Controller Test这里少了一步对recipientRepository对象findByEmail方法的mock,由于若是不经过Expectations进行方法mock的话,方法会默认返回null,而咱们要测试的场景正是须要findByEmail方法返回null,因此mock方法这一步咱们也省了。改写后的总体代码也比原来的少了不少,并且速度更快。
JMockit功能很是强大,不只能够轻松处理上面的这些测试场景,还能够对static,final,private等方法进行mock,可让你的单元测试毫无阻碍的进行。
可是若是过分的使用Mock框架,会让功能代码的真正问题被掩盖。原本单元测试的设计可让你发现功能代码上的一些设计是否合理,好比有没有紧耦合等,但使用JMockit可让你在设计不合理的代码上也能够轻松地进行单元测试,这样你就很难发现功能代码上的问题了。
因此建议JMockit等相似的mock框架仍是要谨慎使用,首先要保证功能代码设计合理,知足面向对象设计的要求,再来考虑提升单元测试效率的问题。
另外,Mock的函数是不计算到单元测试覆盖率里边的,以下图所示:
注意:咱们mock了hello方法,可是在EclEmma中显示覆盖率为0%。
在pom.xml文件中,注意依赖次序,JMockit必定要在JUnit以前,不然容易出现编译报错方法名找不到之类的奇葩问题:
引用的版本必须保持一致,能够在build path里查看是不是本身用的版本,不然会报错Jmockit初始化异常:
Junit使用最新版本是4.12。系统默认的junit版本过低了。会报异常以下:
java.lang.NoSuchMethodError: org.junit.runner.Request.classWithoutSuiteMethod(Ljava/lang/Class;)Lorg/junit/runner/Request;
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.createFilteredTest(JUnit4TestLoader.java:76)
at org.eclipse.jdt.internal.
改成最新版本,Junit运行正常:
<!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
调试过程当中报错:
xxx cannot be resolved to a type
解决方法,在发生错误的项目上单击鼠标右键-〉Properties,选中“Resource”,右侧Text file encoding选择“Other:UTF-8”,点击“Apply”按钮。
附录:参考文档一览
JMockit官网:http://jmockit.org/
使用JMockit编写java单元测试: http://blog.csdn.net/chjttony/article/details/17838693
感谢阅读,做者原创技术文章,转载请注明出处
其余推荐相关阅读:
单元测试系列之四:Sonar平台中项目主要指标以及代码坏味道详解
单元测试系列之七:Sonar 数据库表关系整理一(rule相关)