Java测试框架之JMockit

JMockIt

maven依赖:

    
        org.jmockit
        jmockit
        1.8
        test
    
    
        junit
        junit
        4.12
        test
    

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


@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)