学无止境

导航

真正意义上的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中替换我们的测试类的依赖.





Java代码 复制代码 收藏代码
  1. /** 
  2.  * 类SayServiceTests.java的实现描述:Mock demo 
  3.  *  
  4.  * @author hujf 2011-3-2 下午02:04:08 
  5.  */  
  6. @ContextConfiguration(locations = { "/applicationContext.xml" })  
  7. @RunWith(SpringJUnit4ClassRunner.class)  
  8. @TestExecutionListeners({ org.springframework.test.context.support.DependencyInjectionAsMockitoTestExecutionListener.class })  
  9. public class SayServiceTest {  
  10.   
  11.     @Mock  
  12.     public SayDao     sayDao;  
  13.   
  14.     @Autowired  
  15.     public SayService sayService; // TODO 暂时用实现类  
  16.   
  17.     @Test  
  18.     public void testSay() {  
  19.         // 1.设置预期行为 void  
  20.   
  21.         when(sayDao.sayTo(null)).thenReturn("3");  
  22.         // 2.验证  
  23.         assertTrue(sayService.sayTo(null).equals("3"));  
  24.   
  25.     }  
  26.   
  27.     public SayDao getSayDao() {  
  28.         return sayDao;  
  29.     }  
  30.   
  31.     public void setSayDao(SayDao sayDao) {  
  32.         this.sayDao = sayDao;  
  33.     }  
  34.   
  35.     public SayService getSayService() {  
  36.         return sayService;  
  37.     }  
  38.   
  39.     public void setSayService(SayService sayService) {  
  40.         this.sayService = sayService;  
  41.     }  
  42.   
  43.     @Test  
  44.     public void testSayTo() {  
  45.         System.out.println("testSayTo...");  
  46.   
  47.         // 1.设置预期行为  
  48.         when(sayDao.sayTo(any(Person.class))).thenAnswer(new Answer() {  
  49.   
  50.             @Override  
  51.             public Object answer(InvocationOnMock invocation) throws Throwable {  
  52.                 Object[] args = invocation.getArguments();  
  53.                 // SayDao mock = (SayDao)invocation.getMock(); //Object mock = invocation.getMock();  
  54.   
  55.                 if (null != args && args.length > 0) {  
  56.                     Person person = (Person) args[0];  
  57.                     return person.getName();  
  58.                 }  
  59.                 return null;  
  60.             }  
  61.   
  62.         });  
  63.   
  64.         // 2.验证  
  65.         Person person = new Person();  
  66.         person.setId(11);  
  67.         person.setName("Leifeng");  
  68.   
  69.         String s = sayService.sayTo(person);  
  70.         System.out.println(s);  
  71.   
  72.         assertSame("Leifeng", s);  
  73.     }  
  74.   
  75.     @Test  
  76.     public void testSaySomething() {  
  77.         System.out.println("testSaySomething...");  
  78.         // 1.设置预期行为  
  79.         when(sayDao.saySomething(anyString(), any(Person.class), anyString())).thenAnswer(new Answer<String>() {  
  80.   
  81.             @Override  
  82.             public String answer(InvocationOnMock invocation) throws Throwable {  
  83.                 Object[] args = invocation.getArguments();  
  84.                 if (null != args && args.length > 0) {  
  85.                     String hello = (String) args[0];  
  86.                     Person person = (Person) args[1];  
  87.                     String msg = (String) args[2];  
  88.   
  89.                     return hello + "," + person.getName() + "(" + person.getId() + ")" + ":" + msg;  
  90.                     // SayDao dao = new SayDaoImpl();  
  91.                     // return dao.saySomething(hello, person, msg);  
  92.                 }  
  93.   
  94.                 return null;  
  95.             }  
  96.   
  97.         });  
  98.   
  99.         // 2.验证  
  100.         Person person = new Person();  
  101.         person.setId(12);  
  102.         person.setName("Leifeng");  
  103.         String s = sayService.saySomething("Welcome", person, "handsome guy!");  
  104.         System.out.println(s);  
  105.         assertNotNull(s);  
  106.     }  
  107.   
  108.     @Test  
  109.     public void testQueryPerson() {  
  110.         // 1.预置预期行为  
  111.         List<Person> personList = new ArrayList<Person>();  
  112.         // 初始化List《Person》  
  113.         for (int i = 0; i < 10; i++) {  
  114.             Person person = new Person();  
  115.             person.setId(i + 1);  
  116.             person.setName("name" + i);  
  117.             personList.add(person);  
  118.         }  
  119.         when(sayDao.queryPerson(any(Person.class))).thenReturn(personList);  
  120.   
  121.         // 2.验证  
  122.         Person query = new Person();  
  123.         query.setId(13);  
  124.         query.setName("Leifeng");  
  125.   
  126.         List<Person> list = sayService.queryPerson(query);  
  127.         assertTrue(10 == list.size());  
  128.   
  129.         // 重要(根据具体业务设计)  
  130.         assertTrue(3 == list.get(3).getFlag());  
  131.     }  
  132. }  
/**
* 类SayServiceTests.java的实现描述:Mock demo
*
* @author hujf 2011-3-2 下午02:04:08
*/
@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指定这个监听器来实现单元测试
Java代码 复制代码 收藏代码
  1. package org.springframework.test.context.support;  
  2.   
  3. import java.lang.annotation.Annotation;  
  4. import java.lang.reflect.Field;  
  5. import java.lang.reflect.InvocationTargetException;  
  6. import java.lang.reflect.Method;  
  7.   
  8. import java.util.ArrayList;  
  9. import java.util.List;  
  10.   
  11. import static org.mockito.Mockito.mock;  
  12. import org.springframework.beans.factory.annotation.Autowired;  
  13. import org.springframework.test.context.TestContext;  
  14.   
  15. /** 
  16.  * @author yanglin lv 
  17.  */  
  18. public class DependencyInjectionAsMockitoTestExecutionListener extends DependencyInjectionTestExecutionListener {  
  19.   
  20.     private static String SETTER = "set";  
  21.   
  22.     private static String GETTER = "get";  
  23.   
  24.     @Override  
  25.     protected void injectDependencies(final TestContext testContext) throws Exception {  
  26.         super.injectDependencies(testContext);  
  27.         Object bean = testContext.getTestInstance();  
  28.         Class[] mockClass = getMockClass(bean.getClass());  
  29.         Method[] methods = bean.getClass().getDeclaredMethods();  
  30.         Class clz = bean.getClass();  
  31.         Object instance = null;  
  32.         List<MockObjectMap> objs = new ArrayList<MockObjectMap>();  
  33.         autowireMockBean(clz, bean, objs);  
  34.         List<Object> stubObjs = getStubInstance(clz, bean);  
  35.         autowireMockBeanForSpring(stubObjs, objs);  
  36.     }  
  37.   
  38.     private void autowireMockBeanForSpring(List<Object> stubObjs, List<MockObjectMap> objs)  
  39.                                                                                            throws IllegalArgumentException,  
  40.                                                                                            IllegalAccessException,  
  41.                                                                                            InvocationTargetException {  
  42.   
  43.         for (Object object : stubObjs) {  
  44.             Class claz = object.getClass();  
  45.             do {  
  46.                 for (Method method : claz.getDeclaredMethods()) {  
  47.                     if (method.getName().startsWith(SETTER)) {  
  48.                         for (MockObjectMap mockObjectMap : objs) {  
  49.                             Object obj = method.getGenericParameterTypes()[0];  
  50.                             if (obj instanceof java.lang.reflect.Type  
  51.                                 && mockObjectMap.getType().getName().equalsIgnoreCase(((Class) obj).getName())) {  
  52.                                 method.invoke(object, mockObjectMap.getObj());  
  53.                                 continue;  
  54.                             }  
  55.   
  56.                         }  
  57.   
  58.                     }  
  59.                 }  
  60.   
  61.                 claz = claz.getSuperclass();  
  62.             } while (!claz.equals(Object.class));  
  63.         }  
  64.   
  65.     }  
  66.   
  67.     private void autowireMockBean(Class clz, Object bean, List<MockObjectMap> objs) throws IllegalArgumentException,  
  68.                                                                                    IllegalAccessException {  
  69.   
  70.         for (Field field : clz.getFields()) {  
  71.   
  72.             Annotation[] mockAnnotations = field.getAnnotations();  
  73.             for (Annotation annotation : mockAnnotations) {  
  74.                 if (annotation instanceof org.mockito.Mock) {  
  75.                     MockObjectMap mockObjectMap = new MockObjectMap();  
  76.                     objs.add(mockObjectMap);  
  77.                     mockObjectMap.setType(field.getType());  
  78.                     mockObjectMap.setObj(mock(field.getType()));  
  79.                     field.setAccessible(true);  
  80.                     field.set(bean, mockObjectMap.getObj());  
  81.   
  82.                     continue;  
  83.                 }  
  84.   
  85.             }  
  86.   
  87.         }  
  88.   
  89.     }  
  90.   
  91.     /** 
  92.      * 取得测试类中所有的mock对象的类型 
  93.      *  
  94.      * @param clazz 
  95.      * @return 
  96.      */  
  97.     private Class[] getMockClass(Class claz) {  
  98.         List<Class> clasList = new ArrayList<Class>();  
  99.         Field[] fields = claz.getDeclaredFields();  
  100.         for (Field field : fields) {  
  101.             Annotation[] mockAnnotations = field.getAnnotations();  
  102.             for (Annotation annotation : mockAnnotations) {  
  103.                 if (annotation instanceof org.mockito.Mock) {  
  104.                     clasList.add(field.getType());  
  105.                     continue;  
  106.                 }  
  107.   
  108.             }  
  109.         }  
  110.         return clasList.toArray(new Class[0]);  
  111.     }  
  112.   
  113.     /** 
  114.      * 取得测试类中测试桩类 
  115.      *  
  116.      * @param clazz 
  117.      * @return 
  118.      * @throws InvocationTargetException 
  119.      * @throws IllegalAccessException 
  120.      * @throws IllegalArgumentException 
  121.      */  
  122.     private List<Object> getStubInstance(Class clazz, Object bean) throws IllegalArgumentException,  
  123.                                                                   IllegalAccessException, InvocationTargetException {  
  124.   
  125.         List<Object> objList = new ArrayList<Object>();  
  126.         Field[] fields = clazz.getDeclaredFields();// 测试类中所有的域名  
  127.         Method[] methods = clazz.getDeclaredMethods();  
  128.         for (Field field : fields) {  
  129.             Annotation[] mockAnnotations = field.getAnnotations();  
  130.             for (Annotation annotation : mockAnnotations) {  
  131.                 if (annotation instanceof Autowired) {  
  132.   
  133.                     for (Method method : methods) {  
  134.                         String name = field.getName();  
  135.                         if (method.getName().startsWith(GETTER) && method.getName().substring(3).equalsIgnoreCase(name)) {  
  136.                             objList.add(method.invoke(bean, null)); // 将所有的测试桩类放在objList  
  137.                         }  
  138.                     }  
  139.   
  140.                 }  
  141.   
  142.             }  
  143.         }  
  144.         return objList;  
  145.   
  146.     }  
  147.   
  148.     private class MockObjectMap {  
  149.   
  150.         private Object   obj;  
  151.   
  152.         private Class<?> type;  
  153.   
  154.         public Object getObj() {  
  155.             return obj;  
  156.         }  
  157.   
  158.         public void setObj(Object obj) {  
  159.             this.obj = obj;  
  160.         }  
  161.   
  162.         public Class<?> getType() {  
  163.             return type;  
  164.         }  
  165.   
  166.         public void setType(Class<?> type) {  
  167.             this.type = type;  
  168.         }  
  169.   
  170.     }  
  171.   
  172. }  
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类设置期望输出简单实现

  


From WizNote


posted on 2016-05-08 21:26  Danica_Z  阅读(594)  评论(0编辑  收藏  举报