公司对开发人员的单元测试要求比较高,要求分支覆盖率、行覆盖率等要达到60%以上等等。项目中已经集成了jmockit这个功能强大的mock框架,学会使用这个框架势在必行。从第一次写一点不会,到完全可以应付工作要求,期间踩了好多坑,学到了不少东西。下面简单总结一下jmockit这个框架的使用,重点介绍MockUp的使用,因为项目中都采用此种方式模拟方法。

一、框架集成

添加maven依赖

 <dependencies>
        <!-- jmockit必须写在junit之前 -->
        <dependency>
            <groupId>org.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>1.16</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

    </dependencies>

二、@Mocked模拟方式介绍

@Mocked模拟,由录制、回放、验证三步骤完成,是对某个类的所有实例的所有方法进行完整的模拟方式。

/**
 * 被测试类
 */
public class App {

    public String say() {
        return "Hello World";
    }

    public String say2(){
        return "Hello World 2";
    }

    public static String staticSay() {
        return "Still hello world";
    }
}
/**
 * 测试类
 */
public class AppTest {

    /**
     * 针对类及所有实例的的整体模拟,未写录制的方法默认返回0,null等
     */
    @Mocked
    App app;

    @Test
    public void testSay() {

        //录制,定义被模拟的方法的返回值,可以录制多个行为,写在一个大括号里也可以,多个大括号隔开也可以
        new Expectations() {{
            app.say();
            result = "say";
        }};

        //回放,调用模拟的方法
        System.out.println(app.say()); //say
        System.out.println(new App().say()); //say
        System.out.println(App.staticSay()); //null

        //验证
        new Verifications() {{
            //验证say模拟方法被调用,且调用了2次
            app.say();
            times = 2;

            //验证staticSay模拟方法被调用,且调用了1次
            App.staticSay();
            times = 1;
        }};

    }
}

三、@Injectable模拟方式介绍

@Injectable和@Mocked的方式很像,区别是@Injectable仅仅对当前实例进行模拟。 

/**
 * 测试类
 */
public class AppTest001 {

    /**
     * 仅针对当前实例的整体模拟
     */
    @Injectable
    App app;

    @Test
    public void testSay() {

        //录制
        new Expectations() {{
            app.say();
            result = "say";
        }};

        //回放
        System.out.println(app.say()); //say,模拟值
        System.out.println(app.say2()); //null,模拟默认值

        final App appNew = new App();
        System.out.println(appNew.say()); //Hello World,未被模拟,方法实际值
        System.out.println(App.staticSay()); //Still hello world,未被模拟,方法实际值

        //验证
        new Verifications() {
            {
                //验证say模拟方法被调用
                app.say();
                times = 1;
            }
            {
                appNew.say();
                times = 1;
            }
            {
                //验证staticSay模拟方法被调用,且调用了1次
                App.staticSay();
                times = 1;
            }
        };

    }
}

四、Expectations传参,局部模拟

/**
 * 测试类
 */
public class AppTest002 {

    @Test
    public void testSay() {

        final App app = new App();

        //录制,带参数表示局部模拟【针对所有实例的局部模拟,具体到某个方法的模拟,其它方法不模拟】
        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 class AppTest003 {

    @Test
    public void testSay() {
        //局部模拟【针对所有实例的局部模拟,具体到某个方法的模拟,其它方法不模拟】
       new MockUp<App>(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.模拟私有属性(实例属性和类属性)

//模拟实例的字段
Deencapsulation.setField(Object objectWithField, String fieldName, Object fieldValue)

//模拟类的静态字段
Deencapsulation.setField(Class<?> classWithStaticField, String fieldName, Object fieldValue)

2.模拟私有方法

//模拟实例方法,注意参数不能为null,如果要传null请使用带参数类型的另一个重载方法
Deencapsulation.invoke(Object objectWithMethod, String methodName, Object... nonNullArgs)

//模拟类方法
Deencapsulation.invoke(Class<?> classWithStaticMethod, String methodName, Object... nonNullArgs)

3.模拟静态方法

和模拟实例方法一样,去掉static即可

4.模拟静态块

 //mock静态代码块
 @Mock
 void $clinit(Invocation invocation){

 }

5.模拟实例块和构造函数

//mock代码块和构造函数
@Mock
void $init(Invocation invocation) {

}

6.模拟接口

/**
 * 被模拟的接口
 */
public interface IUserService {

    String getUserName( );
}
public class UserServiceImpl implements IUserService {

    @Override
    public String getUserName() {
        return "Bob";
    }
}
/**
 * 接口模拟测试
 */
public class IUserServiceTest {

    /**
     * MockUp不能mock接口方法,可以用来生成接口实例
     */
    @Test
    public void getUserNameTest001(){
        MockUp<IUserService> mockUp = new MockUp<IUserService>(){

            @Mock
            String getUserName( ){
                return "Jack";
            }
        };

        IUserService obj = new UserServiceImpl();
        System.out.println(obj.getUserName()); //Bob,mock失败

        obj = mockUp.getMockInstance();
        System.out.println(obj.getUserName()); //Jack,mockUp生成的实例,和自己写一个接口实现一样

        obj = new UserServiceImpl();
        System.out.println(obj.getUserName()); //Bob,mock失败
    }

    /**
     * @Capturing 注解可以实现mock接口,所有实现类的实例均被mock
     * @param base
     */
    @Test
    public void getUserNameTest002(@Capturing final IUserService base){
        IUserService obj = new UserServiceImpl();
        System.out.println(obj.getUserName()); //mock成功,返回模拟默认值null

        //录制
        new Expectations(){
            {
                base.getUserName();
                result  = "Jack";
            }
        };
        System.out.println(obj.getUserName()); //Jack

        obj = new IUserService() {
            @Override
            public String getUserName() {
                return "Alice";
            }
        };
        System.out.println(obj.getUserName()); //Jack
    }

}

七、MockUp模拟方法中调用原方法

/**
 * 模拟方法调用原方法逻辑测试
 */
public class JSONObjectTest {

    @Test
    public void getTest(){
        JSONObject jsonObject = new JSONObject();
        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<JSONObject>(){
            @Mock
            Object get(Invocation invocation,Object key){
                if("a".equals(key)){
                    return "aa";
                }else{
                    //调用原逻辑
                    return  invocation.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里即可解决,原因未知。

12-31 20:57