美文网首页Java 杂谈
Java测试框架之JMockit

Java测试框架之JMockit

作者: NoobYPP | 来源:发表于2019-01-20 15:46 被阅读30次

    JMockIt

    maven依赖:

        <dependency>
            <groupId>org.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>1.8</version>
            <scope>test</scope>
        </dependency>
    
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    

    这里的依赖是成对的并且有先后顺序耦合


    @Mocked

    当@Mocked修饰一个具体类时,会mock该具体类所有成员属性,若是基本类型,返回原始0,若是String则返回null,若是其他依赖的引用类型,则继续mock它使其不为空引用,但递归地,其内部的对象引用任然像上面那样继续递归mock。

     //@Mocked注解用途
    public class MockedClassTest {
        // 加上了JMockit的API @Mocked, JMockit会帮我们实例化这个对象,不用担心它为null
        @Mocked
        Locale locale;
     
        // 当@Mocked作用于class
        @Test
        public void testMockedClass() {
            // 静态方法不起作用了,返回了null
            Assert.assertTrue(Locale.getDefault() == null);
            // 非静态方法(返回类型为String)也不起作用了,返回了null
            Assert.assertTrue(locale.getCountry() == null);
            // 自已new一个,也同样如此,方法都被mock了
            Locale chinaLocale = new Locale("zh", "CN");
            Assert.assertTrue(chinaLocale.getCountry() == null);
        }
      
    }
    
    //@Mocked注解用途
    public class MockedInterfaceTest {
     
        // 加上了JMockit的API @Mocked, JMockit会帮我们实例化这个对象,尽管这个对象的类型是一个接口,不用担心它为null
        @Mocked
        HttpSession session;
     
        // 当@Mocked作用于interface
        @Test
        public void testMockedInterface() {
            // (返回类型为String)也不起作用了,返回了null
            Assert.assertTrue(session.getId() == null);
            // (返回类型为原始类型)也不起作用了,返回了0
            Assert.assertTrue(session.getCreationTime() == 0L);
            // (返回类型为原非始类型,非String,返回的对象不为空,这个对象也是JMockit帮你实例化的,同样这个实例化的对象也是一个Mocked对象)
            Assert.assertTrue(session.getServletContext() != null);
            // Mocked对象返回的Mocked对象,(返回类型为String)的方法也不起作用了,返回了null
            Assert.assertTrue(session.getServletContext().getContextPath() == null);
        }
    }
    

    @Injected @Tested

    @Mocked对mock的类所有实例进行mock。在特定场景下,只需要对依赖的实例进行mock,搭配使用@Injected @Tested来实现这种功能:

    简单demo演示@Injected @Mocked区别

    
    public class TestJMockitTest {
    
        @Injectable
        private TestJMockit testJMockit;
    
        @Test
        public void printStringInConsole() {
            System.out.println("start ========");
            new TestJMockit().printStringInConsole(); // 会打印
            System.out.println("end ========");
        }
    
    }
    
    
    public class TestJMockitTest {
    
        @Mocked
        private TestJMockit testJMockit;
    
        @Test
        public void printStringInConsole() {
            System.out.println("start ========");
            new TestJMockit().printStringInConsole(); // 不会打印
            System.out.println("end ========");
        }
    
    }
    
    
    public class TestJMockit {
        public void printStringInConsole() {
            System.out.println("Im Stephen Curry 3 Points!!!");
        }
    }
    

    可见@Mocked针对类型mock,@Injected针对类实例mock


    @Capturing

    @Capturing 意为捕捉,JMockit中,当知道基类或者接口时,想要控制其所有子类的实现,则使用@Capturing,就像 “捕捉” 本身的意义一样。

    public interface BasketballPlayer {
    
        /**
         * 篮球运动员得分技能
         */
        int getScore(String name);
    
    }
    
    @SpringBootTest
    @RunWith(SpringRunner.class)
    public class BasketballPlayerTest {
    
        private BasketballPlayer basketballPlayer1 = name -> {
            if (name.equals("Kobe")) {
                return 81;
            }
            return 0;
        };
    
        private BasketballPlayer wonderfulGiftPlayer =
                (BasketballPlayer) Proxy.newProxyInstance(BasketballPlayer.class.getClassLoader(),
                        new Class[]{BasketballPlayer.class},
                        (proxy, method, args) -> {
                            if (args[0].equals("Kobe")) {
                                return 81;
                            }
                            return 0;
                        });
    
        // 上面是JDK动态代理生成的BasketballPlayer实例
        // 如果是科比则得分81
    
        @Test
        public void getScore(@Capturing BasketballPlayer basketballPlayer) {
    
            // @Capturing 会捕捉BasketballPlayer所有实例,即使是运行时生成的动态实例。
            new Expectations() {
                {
                    basketballPlayer.getScore(anyString);
                    result = 81;
                }
            };
    
            Assert.assertEquals(81, basketballPlayer1.getScore("couldBeAnyString"));
    //        Assert.assertEquals(81, wonderfulGiftPlayer.getScore("couldBeAnyString")); // 不能测试成功,JDK动态代理
    
        }
    }
    

    上面的JDK动态代理的mock失败了,但是文档上貌似显示是支持的,暂未找到原因...
    可见,@Mocked 和 @Capturing 的区别,前者mock不能影响子类和实现类。因此,在一些第三方API需要mock时,就使用@Capturing去捕捉这种关系,但是其实若是单一的第三方接口,直接@Mocked出一个匿名内部类也可以实现,@Capturing可以运用于一些比较特定的场合,到时候找不到解决方式时就会想到还有一个@Capturing,一般情况下,是不怎么使用到@Capturing的。

    mockup 和 @mock的搭配使用
    不建议使用此方式,因为这种方式是new一个匿名内部类,在其中对想要mock的方法一个一个添加@Mock去mock,看似增加了定制化,但是实际上每个需要被mock的方法都要手动实现一遍:一是不够优雅,二是比如mock一个HTTPSession这种,需要大量的@Mock手动实现,而此时@Mocked一行就可以解决。功能只需要在expection中去record即可。

    Expectations

    所谓的 record-replay (录制-回放)功能,在此种方式下,可录制所有想要被mock的方法和它的返回值,代码比较优雅。
    需要注意的是在new expectations内部中录制过程中,要再手动添加一对大括号{}。

     new Expectations() {
            // 需要用一对大括包住录制过程
                {
                    basketballPlayer.getScore(anyString);
                    result = 81;
                }
            };
    

    @Verification

    new Verifications() {
        // 这是一个Verifications匿名内部类
        {
              // 这个是内部类的初始化代码块,我们在这里写验证脚本,脚本的格式要遵循下面的约定
            //方法调用(可是类的静态方法调用,也可以是对象的非静态方法调用)
            //times/minTimes/maxTimes 表示调用次数的限定要求。赋值要紧跟在方法调用后面,也可以不写(表示只要调用过就行,不限次数)
            //...其它准备验证脚本的代码
            //方法调用
            //times/minTimes/maxTimes 赋值
        }
    };
      
    还可以再写new一个Verifications,只要出现在重放阶段之后均有效。
    new Verifications() {
           
        {
             //...验证脚本
        }
    };
    

    整个验证过程大致分为
    {录制}
    回放
    验证
    这里的@Verification就是用来验证,比如一个方法调用几次,或者是调用次数的上下限,都可以检验。不满足即抛出错误。使用较少。

    以上,大概就是JMockit使用基础。

    零配置启动mock

    package cn.emitor.spring4d.utils;
    
    import mockit.Expectations;
    import mockit.Injectable;
    import mockit.Tested;
    import org.junit.Assert;
    import org.junit.Test;
    
    /**
     * @author Emitor
     * on 2018/12/26.
     */
    public class MyJMockitTestWorkTest {
        @Injectable
        MyMockItDependencyObject dependencyObject;
    
        @Tested
        MyJMockitTestWork work;
    
        @Test
        public void sdOutPrint() {
    
            new Expectations(){
                {
                    dependencyObject.getMySayHelloANumber();
                    result = 1;
                }
            };
    
            Assert.assertEquals("oh it's not my expectation~", work.sdOutPrint(1), dependencyObject.getMySayHelloANumber());
    
            new Expectations() {
                {
                    dependencyObject.getMyA();
                    result = 1;
                }
    
            };
            Assert.assertEquals("oh, it's not my expectation!", work.sdOutPrint(1), dependencyObject.getMyA());
    
        }
    }
    

    依赖的两个类:

    public class MyJMockitTestWork {
        public Integer sdOutPrint(Integer a) {
            return a;
        }
    }
    
    
    @Component
    public class MyMockItDependencyObject {
        public final void a() {
    
        }
        public static void b() {
    
        }
    
        public static final String e = "1";
        private static void c() {
    
        }
    
        private Integer a = 1;
        public Integer getMySayHelloANumber() {
            return new Random().nextInt(10);
        }
    
        public Integer getMyA() {
            return a;
        }
    
    }
    

    就可以实现 record-replay 功能的测试,解决功能依赖性问题。

    简单注解配置实现拥有Spring上下文的测试环境

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class CanIBeAutowiredssTest {
        @Autowired
        private CanIBeAutowired canIBeAutowired;
        @Autowired
        DoShitController doShitController;
    
        @Test
        public void getAString() {
            String xMas = canIBeAutowired.getAString();
            Assert.assertNotNull("oh, no xMas", xMas);
            Assert.assertEquals(xMas, "merry Xmas");
            Assert.assertEquals(doShitController.doShitLikeAlways(""), "doShitLikeAlwaysBe: ");
        }
    }
    

    上面即实现了自动注入,即这里已经出现Spring上下文。
    其中, @RunWith(SpringRunner.class)@SpringBootTest 缺一不可。前者缺失导致无法注入值,即@AutoWired下面的为null。而缺失后者导致bean无法创建错误。

    简单配置实现controller测试

    package cn.emitor.spring4d.controllers;
    
    import mockit.Injectable;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.context.web.WebAppConfiguration;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.test.web.servlet.ResultActions;
    import org.springframework.test.web.servlet.setup.MockMvcBuilders;
    import org.springframework.web.context.WebApplicationContext;
    
    import static org.junit.Assert.assertEquals;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    
    /**
     * @author Emitor
     * on 2018/12/26.
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    @WebAppConfiguration
    public class DoControllerTest {
        @Autowired
        protected WebApplicationContext wac;
        @Injectable
        MockMvc mockMvc;
    
        @Before()  //这个方法在每个方法执行之前都会执行一遍
        public void setup() {
            mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();  //初始化MockMvc对象
        }
    
    
        @Test
        public void doLikeAlways() throws Exception {
            ResultActions resultActions = mockMvc.perform(get("/api").param("ApiName", "this is a api name"));
            String returnString = resultActions.andExpect(status().isOk())
                    .andDo(print())
                    .andReturn().getResponse().getContentAsString();
    
            System.out.println(returnString);
    
        }
    }
    

    这里产生spring运行content以便mock出想要的网络请求和自动注入效果。

    JMockit大概是现在Java测试框架中,功能最强大的之一了。它具有上手容易,代码优雅等有点,使用得当,在开发中写测试用例时比较得力,很好的解决了非传统意义上单元测试中的依赖问题。

    相关文章

      网友评论

        本文标题:Java测试框架之JMockit

        本文链接:https://www.haomeiwen.com/subject/pnbxjqtx.html