公司对开发人员的单元测试要求比较高,要求分支覆盖率、行覆盖率等要达到60%以上等等。项目中已经集成了jmockit这个功能强大的mock框架,学会使用这个框架势在必行。从第一次写一点不会,到完全可以应付工作要求,期间踩了好多坑,学到了不少东西。下面简单总结一下jmockit这个框架的使用,重点介绍MockUp的使用,因为项目中都采用此种方式模拟方法。
一、框架集成
添加maven依赖
org.jmockit
jmockit
1.16
test
junit
junit
4.12
二、@Mocked模拟方式介绍
@Mocked模拟,由录制、回放、验证三步骤完成,是对某个类的所有实例的所有方法进行完整的模拟方式。
/*** 被测试类*/
public classApp {publicString say() {return "Hello World";
}publicString say2(){return "Hello World 2";
}public staticString staticSay() {return "Still hello world";
}
}
/*** 测试类*/
public classAppTest {/*** 针对类及所有实例的的整体模拟,未写录制的方法默认返回0,null等*/@Mocked
App app;
@Testpublic voidtestSay() {//录制,定义被模拟的方法的返回值,可以录制多个行为,写在一个大括号里也可以,多个大括号隔开也可以
newExpectations() {{
app.say();
result= "say";
}};//回放,调用模拟的方法
System.out.println(app.say()); //say
System.out.println(new App().say()); //say
System.out.println(App.staticSay()); //null//验证
newVerifications() {{//验证say模拟方法被调用,且调用了2次
app.say();
times= 2;//验证staticSay模拟方法被调用,且调用了1次
App.staticSay();
times= 1;
}};
}
}
三、@Injectable模拟方式介绍
@Injectable和@Mocked的方式很像,区别是@Injectable仅仅对当前实例进行模拟。
/*** 测试类*/
public classAppTest001 {/*** 仅针对当前实例的整体模拟*/@Injectable
App app;
@Testpublic voidtestSay() {//录制
newExpectations() {{
app.say();
result= "say";
}};//回放
System.out.println(app.say()); //say,模拟值
System.out.println(app.say2()); //null,模拟默认值
final App appNew = newApp();
System.out.println(appNew.say());//Hello World,未被模拟,方法实际值
System.out.println(App.staticSay()); //Still hello world,未被模拟,方法实际值//验证
newVerifications() {
{//验证say模拟方法被调用
app.say();
times= 1;
}
{
appNew.say();
times= 1;
}
{//验证staticSay模拟方法被调用,且调用了1次
App.staticSay();
times= 1;
}
};
}
}
四、Expectations传参,局部模拟
/*** 测试类*/
public classAppTest002 {
@Testpublic voidtestSay() {final App app = newApp();//录制,带参数表示局部模拟【针对所有实例的局部模拟,具体到某个方法的模拟,其它方法不模拟】
new Expectations(App.class) {{
app.say();
result= "say";
}};//回放
System.out.println(app.say()); //say,模拟值
System.out.println(app.say2()); //Hello World 2 ,未被模拟,方法实际值
System.out.println(new App().say()); //say,模拟值
System.out.println(App.staticSay()); //Still hello world,未被模拟,方法实际值
}
}
五、MockUp局部模拟,可重写原有方法的逻辑,比较灵活,推荐使用
/*** 测试类*/
public classAppTest003 {
@Testpublic voidtestSay() {//局部模拟【针对所有实例的局部模拟,具体到某个方法的模拟,其它方法不模拟】
new MockUp(App.class){
@Mock
String say(){return "say";
}
};//回放
System.out.println(new App().say()); //say,模拟值
System.out.println(new App().say2()); //Hello World 2,未被模拟,方法实际值
System.out.println(App.staticSay()); //Still hello world,未被模拟,方法实际值
}
}
六、MockUp如何模拟私有方法、静态方法、静态块、构造函数等
1.模拟私有属性(实例属性和类属性),MockUp不支持,采用如下方式
//模拟实例的字段
Deencapsulation.setField(Object objectWithField, String fieldName, Object fieldValue)//模拟类的静态字段
Deencapsulation.setField(Class> classWithStaticField, String fieldName, Object fieldValue)
2.模拟私有方法,MockUp不支持,采用如下方式
//模拟实例方法,注意参数不能为null,如果要传null请使用带参数类型的另一个重载方法
Deencapsulation.invoke(Object objectWithMethod, String methodName, Object... nonNullArgs)//模拟类方法
Deencapsulation.invoke(Class> classWithStaticMethod, String methodName, Object... nonNullArgs)
3.模拟静态方法
和模拟实例方法一样,去掉static即可
4.模拟静态块
//mock静态代码块
@Mockvoid$clinit(Invocation invocation){
}
5.模拟实例块和构造函数
//mock代码块和构造函数
@Mockvoid$init(Invocation invocation) {
}
6.模拟接口,MockUp不支持,采用@Capturing
/*** 被模拟的接口*/
public interfaceIUserService {
String getUserName( );
}
public class UserServiceImpl implementsIUserService {
@OverridepublicString getUserName() {return "Bob";
}
}
/*** 接口模拟测试*/
public classIUserServiceTest {/*** MockUp不能mock接口方法,可以用来生成接口实例*/@Testpublic voidgetUserNameTest001(){
MockUp mockUp = new MockUp(){
@Mock
String getUserName( ){return "Jack";
}
};
IUserService obj= newUserServiceImpl();
System.out.println(obj.getUserName());//Bob,mock失败
obj=mockUp.getMockInstance();
System.out.println(obj.getUserName());//Jack,mockUp生成的实例,和自己写一个接口实现一样
obj= newUserServiceImpl();
System.out.println(obj.getUserName());//Bob,mock失败
}/*** @Capturing 注解可以实现mock接口,所有实现类的实例均被mock
*@parambase*/@Testpublic void getUserNameTest002(@Capturing finalIUserService base){
IUserService obj= newUserServiceImpl();
System.out.println(obj.getUserName());//mock成功,返回模拟默认值null//录制
newExpectations(){
{
base.getUserName();
result= "Jack";
}
};
System.out.println(obj.getUserName());//Jack
obj= newIUserService() {
@OverridepublicString getUserName() {return "Alice";
}
};
System.out.println(obj.getUserName());//Jack
}
}
七、MockUp模拟方法中调用原方法
/*** 模拟方法调用原方法逻辑测试*/
public classJSONObjectTest {
@Testpublic voidgetTest(){
JSONObject jsonObject= newJSONObject();
jsonObject.put("a","A");
jsonObject.put("b","B");
jsonObject.put("c","C");
System.out.println(jsonObject.get("a")); //A
System.out.println(jsonObject.get("b")); //B
System.out.println(jsonObject.get("c")); //C
new MockUp(){
@Mock
Object get(Invocation invocation,Object key){if("a".equals(key)){return "aa";
}else{//调用原逻辑
returninvocation.proceed(key);
}
}
};
System.out.println(jsonObject.get("a")); //aa
System.out.println(jsonObject.get("b")); //B
System.out.println(jsonObject.get("c")); //C
}
}
八、MockUp单元测试用例单个跑正常,批量跑失败可能的原因
1.测试方法使用了共享变量,相互影响。
2.在一个测试方法里多次MockUp同一个类,将某个类需要mock的方法均写在一个new MockUp里即可解决,原因未知。
3.在测试方法里的MockUp,在方法结束前,调用一下mockUp.tearDown。