单元测试框架Mockito调研

最近系统需要引进单元测试Mock框架,也就是在进行单元测试的时候,跳过RPC调用,直接通过Mock方式返回。然后就进行一个简单的调研,通应用到系统中。

一.Mock框架调研

1.1.系统诉求

  • 提高单元测试质量
  • 提高单测行覆盖率
  • mock代替RPC调用

1.2.调研分析

通过调研和查阅文档,对于市面上几款单元测试框架进行了比对分析,其结果如下:

框架 优点 缺点 备注
小黄瓜 自动化测试佼佼者,可直接编写解析纯文本 上手难度大,系统额外侵入文件。
Junit 单元测试利器,使用方便 无法解决mock问题。
JMock mock接口的支持度较好 编写mock对象,需要显示指定,方法模拟较为复杂。
EasyMock 对于快速mock数据较为简便 行业推荐较少
Mockito 快速上手,代码简洁,并且异常信息更为清晰,相比api更简单,更加面向对象。 对于较为复杂的调用,mock起来比较复杂 推荐
PowerMock 兼EasyMock与Mockito的优点 可能存在jcoco,sonar不兼容问题。 推荐

1.3采用框架

和团队讨论决定:使用Mockito

二.Mock框架引入

2.1.Mockito介绍

一款优秀的单元测试框架,能够支持mock创建,验证结果,实现打桩(stubbing)
关于具体详细介绍不做赘述,详情参考官方文档:官网

2.2.项目使用

  1. 引入Maven依赖
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core --> select采用的版本
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-all</artifactId>
  <version>1.10.19</version>
</dependency>
  1. 创建Mock注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
@Documented
public @interface MockTest {//仅仅是作为mock接口的标记
}
  1. mock监听器
public class MockitoBeansTestExecutionListener extends DependencyInjectionTestExecutionListener {
 
    //声明mock实例容器
    private static Map<Class<?>, MockBeanWrapper> mockBeans = new ConcurrentHashMap<>();
    private static Map<Class<?>, List<Field>> injectMockBeans = new ConcurrentHashMap<>();
    private static boolean hasInitialized = false;
 
 
    public static Map<Class<?>, MockBeanWrapper> resolvedAllMockBeans() {
        Assert.isTrue(hasInitialized);
        return Collections.unmodifiableMap(mockBeans);
    }
 
    @Override
    public void beforeTestMethod(TestContext testContext) throws Exception {
        //获取对象实例
        Object testInstance = testContext.getTestInstance();
 
        //获取注入实例对象
        List<Field> fields = injectMockBeans.get(testContext.getTestClass());
 
        //设置访问权限,并添加mock对象
        if (CollectionUtils.isNotEmpty(fields)){
            for (Field field : fields){
                field.setAccessible(true);
                field.set(testInstance,mockBeans.get(field.getType()).getMockObject());
            }
        }
    }
 
    @Override
    public void beforeTestClass(TestContext testContext) throws Exception {
 
        //获取当前MockBean数据,如果父类中有MockBean则将其加入
        Field[] fields = testContext.getTestClass().getDeclaredFields();
        Field[] superFields = testContext.getTestClass().getSuperclass().getDeclaredFields();
 
        List<Field> mockFields = Lists.newArrayList(fields);
        if (superFields.length > 0){
            mockFields.addAll(Lists.newArrayList(superFields));
        }
 
        //将需要mock的对象创建出来
        for (Field field : mockFields) {
            MockTest mockTest = field.getAnnotation(MockTest.class);
            if (mockTest != null) {
                MockBeanWrapper wrapper = new MockBeanWrapper();
                Class<?> type = field.getType();
 
                wrapper.setMockObject(Mockito.mock(type));
                wrapper.setBeanType(type);
                wrapper.setBeanName(field.getName());
 
                mockBeans.putIfAbsent(wrapper.getBeanType(), wrapper);
                injectMockBeans.compute(testContext.getTestClass(), (targetClass, waitInjectFields) -> {
                    if (waitInjectFields == null) {
                        waitInjectFields = new ArrayList<>();
                    }
                    waitInjectFields.add(field);
                    return waitInjectFields;
                });
            }
        }
        hasInitialized = true;
    }
 
    @Data
    public class MockBeanWrapper {
        private String beanName;
        private Class<?> beanType;
        private Object mockObject;
    }
}
  1. 注册到Bean工厂
public class MockitoBeansPostProcessor implements BeanFactoryPostProcessor {
 
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Map<Class<?>, MockitoBeansTestExecutionListener.MockBeanWrapper> allMockBeans = MockitoBeansTestExecutionListener.resolvedAllMockBeans();
        for (Map.Entry<Class<?>, MockitoBeansTestExecutionListener.MockBeanWrapper> mockBeanWrapperEntry : allMockBeans.entrySet()) {
            beanFactory.registerResolvableDependency(mockBeanWrapperEntry.getKey(), mockBeanWrapperEntry.getValue().getMockObject());
            beanFactory.registerSingleton(mockBeanWrapperEntry.getValue().getBeanName(), mockBeanWrapperEntry.getValue().getMockObject());
        }
    }
}
  1. 实际使用案例
  • 启动类改造
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:context-test.xml"})
@TestExecutionListeners({MockitoBeansTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        SqlScriptsTestExecutionListener.class})
@Transactional(rollbackFor = Exception.class)
public class TestBase extends JUnit4SpringContextTests {
//公共处理逻辑
}
  • 单元测试使用
public class MaxBrandAdapterTest extends TestBase {
 
    @Resource
    private MaxBrandAdapter maxBrandAdapter;
 
    @MockTest
    MaxBrandApi maxBrandApi;
 
    @Test
    public void queryMaxBrandExceptionTest() {
        //Mock异常场景
        Mockito.when(maxBrandApi.queryHotBrand(Mockito.any(),Mockito.anyLong(),Mockito.anyString())).thenThrow(Exception.class);
        List<Long> list = maxBrandAdapter.queryMaxBrandByUserId("userId");
        if (CollectionUtils.isNotEmpty(list)) {
            for (Long id : list) {
                System.out.println(id);
            }
        }
        Assert.assertNull(list);
    }
 
    @Test
    public void queryHotBrandTest(){
        //mock正常场景
        Mockito.when(maxBrandApi.queryHotBrand(Mockito.any(),Mockito.anyLong(),Mockito.anyString()))
                .thenReturn(mockResultData());
 
        List<Long> list = maxBrandAdapter.queryMaxBrandByUserId("userId");
        if (CollectionUtils.isNotEmpty(list)) {
            for (Long id : list) {
                System.out.println(id);
            }
        }
        Assert.assertNotNull(list);
    }
     //mock结果数据
    private MaxResult<MaxBrandTO> mockResultData(){
        MaxBrandTO brandTO = new MaxBrandTO();
        brandTO.setBrandIds("123,345,123");
        MaxResult<HotBrandTO> result = new MaxResult();
        result.success();
        result.setData(brandTO);
        return result;
    }
}

参考1参考2参考3

上一篇:单元测试 - Mockito - powermock -实例函数调用


下一篇:Java 单元测试编写完全教程(TestNG + Mockito + Powermock)