使用JMockit编写java单元测试

之前《有效使用Mock编写java单元测试》一文中层介绍过使用EasyMock和PowerMock来编写java单元测试,今天介绍一个更加强大的工具——JMockit。

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

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

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

<span style="color:#000066;"><dependency>
			<groupId>com.googlecode.jmockit</groupId>
			<artifactId>jmockit</artifactId>
			<version>1.5</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>com.googlecode.jmockit</groupId>
			<artifactId>jmockit-coverage</artifactId>
			<version>0.999.24</version>
			<scope>test</scope>
		</dependency></span>


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

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



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

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

1.录制方法预期行为。

2.真实调用。

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

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

要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(null, "multiply3")
this.invoke(obj, "multiply3", 1);
         result = 4;        
  }    };    
String actual = obj.getTripleString(1);   
 assertEquals("4", actual);    
new Verifications() {        {           
 this.invoke(obj, "multiply3", 1); 
           times = 1;        
}    };
}














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

我们知道EasyMock和PowerMock的Mock对象是通过JDK/CGLIB动态代理实现的,本质上是类的继承或者接口的实现,但是在java面向对象编程中,基类对象中的私有属性是无法被子类继承的,所以如果被Mock对象的方法中使用到了其自身的私有属性,并且这些私有属性没有提供对象访问方法,则使用传统的Mock方法是无法进行测试的,JMockit提供了设置Mocked对象私有属性值的方法,代码如下:

被测试代码:

public class ClassMocked {
    private String name = "name_init";
    public String getName() {
        return name;
    }
    
    private static String className="Class3Mocked_init";
    
    public static String getClassName(){
        return className;
    }
}








使用JMockit设置私有属性:

@Test
public void testMockPrivateProperty() throws IOException {
    final ClassMocked obj = new ClassMocked();
    new NonStrictExpectations(obj) {
        {
            this.setField(obj, "name", "name has bean change!");
        }
    };
    assertEquals("name has bean change!", obj.getName());
}







使用JMockit设置静态私有属性:

@Test
public void testMockPrivateStaticProperty() throws IOException {
    new NonStrictExpectations(Class3Mocked.class) {
        {
            this.setField(ClassMocked.class, "className", "className has bean change!");
        }
    };
    assertEquals("className has bean change!", ClassMocked.getClassName());
}

(2).基于状态的Mock方式:

JMockit上面的基于行为Mock方式和传统的EasyMock和PowerMock流程基本类似,相当于把被模拟的方法当作黑盒来处理,而JMockit的基于状态的Mock可以直接改写被模拟方法的内部逻辑,更像是真正意义上的白盒测试,下面通过简单例子介绍JMockit基于状态的Mock。

被测试的代码如下:

public class StateMocked {
    
    public static int getDouble(int i){
        return i*2;
    }
    
    public int getTriple(int i){
        return i*3;
    }
}







改写普通方法内容:

@Test
public void testMockNormalMethodContent() throws IOException {
    final StateMocked obj = new StateMocked();
    new NonStrictExpectations(obj) {
        {
            new MockUp<StateMocked>() {//使用MockUp修改被测试方法内部逻辑
                @Mock
                public int getTriple(int i) {
                    return i * 30;
                }
            };
        }
    };
    assertEquals(30, obj.getTriple(1));
    assertEquals(60, obj.getTriple(2));
    Mockit.tearDownMocks();//注意:在JMockit1.5之后已经没有Mockit这个类,
使用MockUp代替,mockUp和tearDown方法在MockUp类中
}











修改静态方法的内容:

若要改写静态方法内容,则首先需要新建一个类,包含与被测试静态方法相同方法签名的方法,并且使用@Mock标注,代码如下:

public class StaticMocked {
    @Mock
    public static int getDouble(int i){
        return i*20;
    }
}





测试代码如下:

@Test
public void testDynamicMockStaticMethodContent() throws IOException {
    Mockit.setUpMock(StateMocked.class, StaticMocked.class);
    assertEquals(20, StateMocked.getDouble(1));
    assertEquals(40, StateMocked.getDouble(2));
    Mockit.tearDownMocks();
}

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:\userdata\administrator\.m2\repository\com\googlecode
\jmockit\jmockitcoverage\0.999.24\jmockit-coverage-0.999.24.jar

(2).Maven+Sonar方式:

如果直接使用mvn sonar:sonar命令时,发现任何单元测试无法执行,长时间卡住,强行结束后再次执行时maven工程target目录下surefire目录无法删除,只有重启机器后才能删除,解决方法如下:

在pom文件中添加如下的插件:

<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>2.12</version>
				<configuration>
					<argLine>-javaagent:"${settings.localRepository}"/
com/googlecode/jmockit/jmockit-coverage/0.999.24/jmockit-coverage-0.999.24.jar</argLine>
				</configuration>
			</plugin>







再次执行mvn sonar:sonar命令就可以正常统计出JMockit的单元测试覆盖率。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值