• jmockit使用总结-MockUp重点介绍


      公司对开发人员的单元测试要求比较高,要求分支覆盖率、行覆盖率等要达到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.模拟私有属性(实例属性和类属性),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静态代码块
     @Mock
     void $clinit(Invocation invocation){
    
     }

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

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

    6.模拟接口,MockUp不支持,采用@Capturing

    /**
     * 被模拟的接口
     */
    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里即可解决,原因未知。

    3.在测试方法里的MockUp,在方法结束前,调用一下mockUp.tearDown。

  • 相关阅读:
    VSPD虚拟串口工具
    Go 普通指针类型、unsafe.Pointer、uintptr之间的关系
    Go内存对齐
    自定义URL Protocol 协议
    C# 百钱买百鸡
    C++输出九九乘法表
    C#嵌入子窗体,判断子窗体是否打开了
    K3 WISE 开发插件《K3 WISE常用数据表整理》
    提取Jar2Exe源代码,JavaAgent监控法
    Linux密码重置
  • 原文地址:https://www.cnblogs.com/hdwang/p/11826778.html
Copyright © 2020-2023  润新知