`
lvyanglin
  • 浏览: 84239 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

真正意义上的spring环境中的单元测试方案spring-test与mokito完美结合

阅读更多
一.要解决的问题:
    spring环境中单元测试其实不是真正意义上的单元测试,真正意义上的单元测试应该是隔离所有的依赖,对当前业务实现业务逻辑测试;但是目前spring好像还没提供这样的解决方案,只能做依赖于环境的集成测试。比如:要测试A类,但是A类依赖B类和C类,这个时候我们必须保证B和C是完整的且是相对稳定的没太多bug的类.但是实际开发过程中,C类和B类可能是对数据库操作的Dao层或是对外接口层,这个时候我们在测试A类的时候业务B和C的环境或B或C都现在还没开发完成只是一个接口定义完成,这个时候就很难完成我们A类的测试了。



二.解决方案:
   为了解决这个问题我们必须在测试的时候忽略B和C类,换句话说就是假象B和C都是可以运行或按我们预期返回结果的运行。我们利用mockito来掩饰我们测试类的所有的依赖。这样我们需要做到两点1.我们可以让B和C可以控制返回预期;2.B和C必须注入到spring中替换我们的测试类的依赖.





/**
 * 类SayServiceTests.java的实现描述:Mock demo
 * 

 */
@ContextConfiguration(locations = { "/applicationContext.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({ org.springframework.test.context.support.DependencyInjectionAsMockitoTestExecutionListener.class })
public class SayServiceTest {

    @Mock
    public SayDao     sayDao;

    @Autowired
    public SayService sayService; // TODO 暂时用实现类

    @Test
    public void testSay() {
        // 1.设置预期行为 void

        when(sayDao.sayTo(null)).thenReturn("3");
        // 2.验证
        assertTrue(sayService.sayTo(null).equals("3"));

    }

    public SayDao getSayDao() {
        return sayDao;
    }

    public void setSayDao(SayDao sayDao) {
        this.sayDao = sayDao;
    }

    public SayService getSayService() {
        return sayService;
    }

    public void setSayService(SayService sayService) {
        this.sayService = sayService;
    }

    @Test
    public void testSayTo() {
        System.out.println("testSayTo...");

        // 1.设置预期行为
        when(sayDao.sayTo(any(Person.class))).thenAnswer(new Answer() {

            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                Object[] args = invocation.getArguments();
                // SayDao mock = (SayDao)invocation.getMock(); //Object mock = invocation.getMock();

                if (null != args && args.length > 0) {
                    Person person = (Person) args[0];
                    return person.getName();
                }
                return null;
            }

        });

        // 2.验证
        Person person = new Person();
        person.setId(11);
        person.setName("Leifeng");

        String s = sayService.sayTo(person);
        System.out.println(s);

        assertSame("Leifeng", s);
    }

    @Test
    public void testSaySomething() {
        System.out.println("testSaySomething...");
        // 1.设置预期行为
        when(sayDao.saySomething(anyString(), any(Person.class), anyString())).thenAnswer(new Answer<String>() {

            @Override
            public String answer(InvocationOnMock invocation) throws Throwable {
                Object[] args = invocation.getArguments();
                if (null != args && args.length > 0) {
                    String hello = (String) args[0];
                    Person person = (Person) args[1];
                    String msg = (String) args[2];

                    return hello + "," + person.getName() + "(" + person.getId() + ")" + ":" + msg;
                    // SayDao dao = new SayDaoImpl();
                    // return dao.saySomething(hello, person, msg);
                }

                return null;
            }

        });

        // 2.验证
        Person person = new Person();
        person.setId(12);
        person.setName("Leifeng");
        String s = sayService.saySomething("Welcome", person, "handsome guy!");
        System.out.println(s);
        assertNotNull(s);
    }

    @Test
    public void testQueryPerson() {
        // 1.预置预期行为
        List<Person> personList = new ArrayList<Person>();
        // 初始化List《Person》
        for (int i = 0; i < 10; i++) {
            Person person = new Person();
            person.setId(i + 1);
            person.setName("name" + i);
            personList.add(person);
        }
        when(sayDao.queryPerson(any(Person.class))).thenReturn(personList);

        // 2.验证
        Person query = new Person();
        query.setId(13);
        query.setName("Leifeng");

        List<Person> list = sayService.queryPerson(query);
        assertTrue(10 == list.size());

        // 重要(根据具体业务设计)
        assertTrue(3 == list.get(3).getFlag());
    }
}

DependencyInjectionAsMockitoTestExecutionListener类是在spring-test中的DependencyInjectionTestExecutionListener基础上扩展的一个结合mock的测试监听器。我们在测试的时候可以用注解TestExecutionListeners指定这个监听器来实现单元测试

package org.springframework.test.context.support;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.util.ArrayList;
import java.util.List;

import static org.mockito.Mockito.mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestContext;

/**
 * @author yanglin lv
 */
public class DependencyInjectionAsMockitoTestExecutionListener extends DependencyInjectionTestExecutionListener {

    private static String SETTER = "set";

    private static String GETTER = "get";

    @Override
    protected void injectDependencies(final TestContext testContext) throws Exception {
        super.injectDependencies(testContext);
        Object bean = testContext.getTestInstance();
        Class[] mockClass = getMockClass(bean.getClass());
        Method[] methods = bean.getClass().getDeclaredMethods();
        Class clz = bean.getClass();
        Object instance = null;
        List<MockObjectMap> objs = new ArrayList<MockObjectMap>();
        autowireMockBean(clz, bean, objs);
        List<Object> stubObjs = getStubInstance(clz, bean);
        autowireMockBeanForSpring(stubObjs, objs);
    }

    private void autowireMockBeanForSpring(List<Object> stubObjs, List<MockObjectMap> objs)
                                                                                           throws IllegalArgumentException,
                                                                                           IllegalAccessException,
                                                                                           InvocationTargetException {

        for (Object object : stubObjs) {
            Class claz = object.getClass();
            do {
                for (Method method : claz.getDeclaredMethods()) {
                    if (method.getName().startsWith(SETTER)) {
                        for (MockObjectMap mockObjectMap : objs) {
                            Object obj = method.getGenericParameterTypes()[0];
                            if (obj instanceof java.lang.reflect.Type
                                && mockObjectMap.getType().getName().equalsIgnoreCase(((Class) obj).getName())) {
                                method.invoke(object, mockObjectMap.getObj());
                                continue;
                            }

                        }

                    }
                }

                claz = claz.getSuperclass();
            } while (!claz.equals(Object.class));
        }

    }

    private void autowireMockBean(Class clz, Object bean, List<MockObjectMap> objs) throws IllegalArgumentException,
                                                                                   IllegalAccessException {

        for (Field field : clz.getFields()) {

            Annotation[] mockAnnotations = field.getAnnotations();
            for (Annotation annotation : mockAnnotations) {
                if (annotation instanceof org.mockito.Mock) {
                    MockObjectMap mockObjectMap = new MockObjectMap();
                    objs.add(mockObjectMap);
                    mockObjectMap.setType(field.getType());
                    mockObjectMap.setObj(mock(field.getType()));
                    field.setAccessible(true);
                    field.set(bean, mockObjectMap.getObj());

                    continue;
                }

            }

        }

    }

    /**
     * 取得测试类中所有的mock对象的类型
     * 
     * @param clazz
     * @return
     */
    private Class[] getMockClass(Class claz) {
        List<Class> clasList = new ArrayList<Class>();
        Field[] fields = claz.getDeclaredFields();
        for (Field field : fields) {
            Annotation[] mockAnnotations = field.getAnnotations();
            for (Annotation annotation : mockAnnotations) {
                if (annotation instanceof org.mockito.Mock) {
                    clasList.add(field.getType());
                    continue;
                }

            }
        }
        return clasList.toArray(new Class[0]);
    }

    /**
     * 取得测试类中测试桩类
     * 
     * @param clazz
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     */
    private List<Object> getStubInstance(Class clazz, Object bean) throws IllegalArgumentException,
                                                                  IllegalAccessException, InvocationTargetException {

        List<Object> objList = new ArrayList<Object>();
        Field[] fields = clazz.getDeclaredFields();// 测试类中所有的域名
        Method[] methods = clazz.getDeclaredMethods();
        for (Field field : fields) {
            Annotation[] mockAnnotations = field.getAnnotations();
            for (Annotation annotation : mockAnnotations) {
                if (annotation instanceof Autowired) {

                    for (Method method : methods) {
                        String name = field.getName();
                        if (method.getName().startsWith(GETTER) && method.getName().substring(3).equalsIgnoreCase(name)) {
                            objList.add(method.invoke(bean, null)); // 将所有的测试桩类放在objList
                        }
                    }

                }

            }
        }
        return objList;

    }

    private class MockObjectMap {

        private Object   obj;

        private Class<?> type;

        public Object getObj() {
            return obj;
        }

        public void setObj(Object obj) {
            this.obj = obj;
        }

        public Class<?> getType() {
            return type;
        }

        public void setType(Class<?> type) {
            this.type = type;
        }

    }

}


总结:这种方式可以真正的用spring来实现TDD面向接口的测试方案,对依赖的类做到完全屏蔽,对目前测试类和mock类设置期望输出简单实现
  • 大小: 10.9 KB
  • 大小: 12 KB
1
0
分享到:
评论
2 楼 lvyanglin 2016-07-14  
dongxurr123 写道
帅哥
DependencyInjectionAsMockitoTestExecutionListener 中的mock方法貌似没有实现哇? 父类中也没看到

这个文章 好多年前写的,但是貌似你没理解我的意思
1 楼 dongxurr123 2016-03-04  
帅哥
DependencyInjectionAsMockitoTestExecutionListener 中的mock方法貌似没有实现哇? 父类中也没看到

相关推荐

Global site tag (gtag.js) - Google Analytics