zoukankan      html  css  js  c++  java
  • Jmockit使用

    引用单元测试中mock的使用及mock神器jmockit实践中的java单元测试中各种Mock框架对比,就能明白JMockit有多么强大:

    JMockit是基于JavaSE5中的java.lang.instrument包开发,内部使用ASM库来动态修改java的字节码,使得java这种静态语言可以想动态脚本语言一样动态设置被Mock对象私有属性,模拟静态、私有方法行为等等,对于手机开发,嵌入式开发等要求代码尽量简洁的情况下,或者对于被测试代码不想做任何修改的前提下,使用JMockit可以轻松搞定很多测试场景。

    通过如下方式在maven中添加JMockit的相关依赖:

    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. <dependency>  
    2.             <groupId>com.googlecode.jmockit</groupId>  
    3.             <artifactId>jmockit</artifactId>  
    4.             <version>1.5</version>  
    5.             <scope>test</scope>  
    6.         </dependency>  
    7.         <dependency>  
    8.             <groupId>com.googlecode.jmockit</groupId>  
    9.             <artifactId>jmockit-coverage</artifactId>  
    10.             <version>0.999.24</version>  
    11.             <scope>test</scope>  
    12.         </dependency>  

    JMockit有两种Mock方式:基于行为的Mock方式和基于状态的Mock方式:

    引用单元测试中mock的使用及mock神器jmockit实践中JMockit API和工具如下:

    (1).基于行为的Mock方式:

    非常类似与EasyMock和PowerMock的工作原理,基本步骤为:

    1.录制方法预期行为。

    2.真实调用。

    3.验证录制的行为被调用。

    通过一个简单的例子来介绍JMockit的基本流程:

    要Mock测试的方法如下:

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public class MyObject {  
    2.     public String hello(String name){  
    3.         return "Hello " + name;  
    4.     }  
    5. }  

    使用JMockit编写的单元测试如下:

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Mocked  //用@Mocked标注的对象,不需要赋值,jmockit自动mock  
    2. MyObject obj;  
    3.   
    4. @Test  
    5. public void testHello() {  
    6.     new NonStrictExpectations() {//录制预期模拟行为  
    7.         {  
    8.             obj.hello("Zhangsan");  
    9.             returns("Hello Zhangsan");  
    10.             //也可以使用:result = "Hello Zhangsan";  
    11.         }  
    12.     };  
    13.     assertEquals("Hello Zhangsan", obj.hello("Zhangsan"));//调用测试方法  
    14.     new Verifications() {//验证预期Mock行为被调用  
    15.         {  
    16.             obj.hello("Hello Zhangsan");  
    17.             times = 1;  
    18.         }  
    19.     };  
    20. }  

    JMockit也可以分类为非局部模拟与局部模拟,区分在于Expectations块是否有参数,有参数的是局部模拟,反之是非局部模拟。

    而Expectations块一般由Expectations类和NonStrictExpectations类定义,类似于EasyMock和PowerMock中的Strict Mock和一般性Mock。

    用Expectations类定义的,则mock对象在运行时只能按照 Expectations块中定义的顺序依次调用方法,不能多调用也不能少调用,所以可以省略掉Verifications块;

    而用NonStrictExpectations类定义的,则没有这些限制,所以如果需要验证,则要添加Verifications块。

    上述的例子使用了非局部模拟,下面我们使用局部模拟来改写上面的测试,代码如下:

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Test  
    2. public void testHello() {  
    3.     final MyObject obj = new MyObject();  
    4.     new NonStrictExpectations(obj) {//录制预期模拟行为  
    5.         {  
    6.             obj.hello("Zhangsan");  
    7.             returns("Hello Zhangsan");  
    8.             //也可以使用:result = "Hello Zhangsan";  
    9.         }  
    10.     };  
    11.     assertEquals("Hello Zhangsan", obj.hello("Zhangsan"));//调用测试方法  
    12.     new Verifications() {//验证预期Mock行为被调用  
    13.         {  
    14.             obj.hello("Hello Zhangsan");  
    15.             times = 1;  
    16.         }  
    17.     };  
    18. }  

    模拟静态方法:

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Test  
    2. public void testMockStaticMethod() {  
    3.     new NonStrictExpectations(ClassMocked.class) {  
    4.         {  
    5.             ClassMocked.getDouble(1);//也可以使用参数匹配:ClassMocked.getDouble(anyDouble);  
    6.             result = 3;  
    7.         }  
    8.     };  
    9.   
    10.     assertEquals(3, ClassMocked.getDouble(1));  
    11.   
    12.     new Verifications() {  
    13.         {  
    14.             ClassMocked.getDouble(1);  
    15.             times = 1;  
    16.         }  
    17.     };  
    18. }  

    模拟私有方法:

    如果ClassMocked类中的getTripleString(int)方法指定调用一个私有的multiply3(int)的方法,我们可以使用如下方式来Mock:

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Test  
    2. public void testMockPrivateMethod() throws Exception {  
    3.     final ClassMocked obj = new ClassMocked();  
    4.     new NonStrictExpectations(obj) {  
    5.         {  
    6.             this.invoke(obj, "multiply3", 1);//如果私有方法是静态的,可以使用:this.invoke(null, "multiply3")  
    7.             result = 4;  
    8.         }  
    9.     };  
    10.   
    11.     String actual = obj.getTripleString(1);  
    12.     assertEquals("4", actual);  
    13.   
    14.     new Verifications() {  
    15.         {  
    16.             this.invoke(obj, "multiply3", 1);  
    17.             times = 1;  
    18.         }  
    19.     };  
    20. }  

    设置Mock对象私有属性的值:

    我们知道EasyMock和PowerMock的Mock对象是通过JDK/CGLIB动态代理实现的,本质上是类的继承或者接口的实现,但是在java面向对象编程中,基类对象中的私有属性是无法被子类继承的,所以如果被Mock对象的方法中使用到了其自身的私有属性,并且这些私有属性没有提供对象访问方法,则使用传统的Mock方法是无法进行测试的,JMockit提供了设置Mocked对象私有属性值的方法,代码如下:
    被测试代码:
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public class ClassMocked {  
    2.     private String name = "name_init";  
    3.   
    4.     public String getName() {  
    5.         return name;  
    6.     }  
    7.       
    8.     private static String className="Class3Mocked_init";  
    9.       
    10.     public static String getClassName(){  
    11.         return className;  
    12.     }  
    13. }  
    使用JMockit设置私有属性:
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Test  
    2. public void testMockPrivateProperty() throws IOException {  
    3.     final ClassMocked obj = new ClassMocked();  
    4.     new NonStrictExpectations(obj) {  
    5.         {  
    6.             this.setField(obj, "name", "name has bean change!");  
    7.         }  
    8.     };  
    9.   
    10.     assertEquals("name has bean change!", obj.getName());  
    11. }  
    使用JMockit设置静态私有属性:
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Test  
    2. public void testMockPrivateStaticProperty() throws IOException {  
    3.     new NonStrictExpectations(Class3Mocked.class) {  
    4.         {  
    5.             this.setField(ClassMocked.class, "className", "className has bean change!");  
    6.         }  
    7.     };  
    8.   
    9.     assertEquals("className has bean change!", ClassMocked.getClassName());  
    10. }  
    (2).基于状态的Mock方式:
    JMockit上面的基于行为Mock方式和传统的EasyMock和PowerMock流程基本类似,相当于把被模拟的方法当作黑盒来处理,而JMockit的基于状态的Mock可以直接改写被模拟方法的内部逻辑,更像是真正意义上的白盒测试,下面通过简单例子介绍JMockit基于状态的Mock。
    被测试的代码如下:
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. public class StateMocked {  
    2.       
    3.     public static int getDouble(int i){  
    4.         return i*2;  
    5.     }  
    6.       
    7.     public int getTriple(int i){  
    8.         return i*3;  
    9.     }  
    10. }  
    改写普通方法内容:
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Test  
    2. public void testMockNormalMethodContent() throws IOException {  
    3.     StateMocked obj = new StateMocked();  
    4.     new MockUp<StateMocked>() {//使用MockUp修改被测试方法内部逻辑  
    5.         @Mock  
    6.       public int getTriple(int i) {  
    7.             return i * 30;  
    8.         }  
    9.     };  
    10.     assertEquals(30, obj.getTriple(1));  
    11.     assertEquals(60, obj.getTriple(2));  
    12.     Mockit.tearDownMocks();//注意:在JMockit1.5之后已经没有Mockit这个类,使用MockUp代替,mockUp和tearDown方法在MockUp类中  
    13. }  
    修改静态方法的内容:
    基于状态的JMockit改写静态/final方法内容和测试普通方法没有什么区别,需要注意的是在MockUp中的方法除了不包含static关键字以外,其他都和被Mock的方法签名相同,并且使用@Mock标注,测试代码如下:
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Test  
    2.     public void testGetTriple() {  
    3.         new MockUp<StateMocked>() {  
    4.             @Mock    
    5.             public int getDouble(int i){    
    6.                 return i*20;    
    7.             }  
    8.         };    
    9.         assertEquals(20, StateMocked.getDouble(1));    
    10.         assertEquals(40, StateMocked.getDouble(2));   
    11.     }  
    JMockit和PowerMock混用时不兼容问题:
    由于PowerMock需要在单元测试类上添加@RunWith(PowerMockRunner.class)注解,用于表面使用PowerMock来执行单元测试,而JMockit不需要指定@RunWith注解,因此当一个单元测试类中混合使用PowerMock和JMockit时,JMockit总是会报错初始化失败,因此我建议不要在同一个单元测试类中混用PowerMock和JMockit。
    另外,JMockit要求必须出现在JVM classpath的中Junit前面位置,因此在添加Maven依赖时记得要把JMockit放在Junit最前面,否则同样报错JMockit初始化失败。
     
    统计JMockit的单元测试覆盖率:
    由于JMockit使用JavaSE5中的java.lang.instrument包开发,因此一般的单元测试覆盖率统计插件和工具对其无法工作,必须要借助自带的JMockit coverage才行,对于使用Eclemma插件和maven+sonar方式的单元测试覆盖率统计,分别有如下方法:
    (1).Eclemma插件方式:
    如果直接使用Eclemma插件来统计单元测试覆盖率,会发现Eclemma长时间挂起阻塞,强行结束时会报错说找不到-javaagent等等,解决方法如下:
    在Eclipse中右键选择Coverage as ->Coverage Configurations,配置Junit的JVM参数如下:
    -javaagent:"${settings.localRepository}"/com/googlecode/jmockit/jmockit-coverage/0.999.24/jmockit-coverage-0.999.24.jar
    其中${settings.localRepository}是maven资源目录,例如:
    -javaagent:D:userdataadministrator.m2 epositorycomgooglecodejmockitjmockitcoverage.999.24jmockit-coverage-0.999.24.jar
    (2).Maven+Sonar方式:
    如果直接使用mvn sonar:sonar命令时,发现任何单元测试无法执行,长时间卡住,强行结束后再次执行时maven工程target目录下surefire目录无法删除,只有重启机器后才能删除,解决方法如下:
    在pom文件中添加如下的插件:
    [html] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. <plugin>  
    2.                 <groupId>org.apache.maven.plugins</groupId>  
    3.                 <artifactId>maven-surefire-plugin</artifactId>  
    4.                 <version>2.12</version>  
    5.                 <configuration>  
    6.                     <argLine>-javaagent:"${settings.localRepository}"/com/googlecode/jmockit/jmockit-coverage/0.999.24/jmockit-coverage-0.999.24.jar</argLine>  
    7.                 </configuration>  
    8.             </plugin>  
  • 相关阅读:
    Java Comparator和Comparabler的区别
    正则表达式全部符号解释
    Java使用reids,以及redis与shiro集成
    jQuery的select相关操作
    javascrit原生实现jquery的append()函数
    spring拦截器 实现应用之性能监控
    Gitlab完美安装【CentOS6.5安装gitlab-6.9.2】
    关于datepicker只显示年、月、日的设置
    spring aop 环绕通知around和其他通知的区别
    springMVC和spring各自扫描自己的注解不要相互混淆
  • 原文地址:https://www.cnblogs.com/E-star/p/4925308.html
Copyright © 2011-2022 走看看