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

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

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

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

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

  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. @Mock
  11. public SayDao     sayDao;
  12. @Autowired
  13. public SayService sayService; // TODO 暂时用实现类
  14. @Test
  15. public void testSay() {
  16. // 1.设置预期行为 void
  17. when(sayDao.sayTo(null)).thenReturn("3");
  18. // 2.验证
  19. assertTrue(sayService.sayTo(null).equals("3"));
  20. }
  21. public SayDao getSayDao() {
  22. return sayDao;
  23. }
  24. public void setSayDao(SayDao sayDao) {
  25. this.sayDao = sayDao;
  26. }
  27. public SayService getSayService() {
  28. return sayService;
  29. }
  30. public void setSayService(SayService sayService) {
  31. this.sayService = sayService;
  32. }
  33. @Test
  34. public void testSayTo() {
  35. System.out.println("testSayTo...");
  36. // 1.设置预期行为
  37. when(sayDao.sayTo(any(Person.class))).thenAnswer(new Answer() {
  38. @Override
  39. public Object answer(InvocationOnMock invocation) throws Throwable {
  40. Object[] args = invocation.getArguments();
  41. // SayDao mock = (SayDao)invocation.getMock(); //Object mock = invocation.getMock();
  42. if (null != args && args.length > 0) {
  43. Person person = (Person) args[0];
  44. return person.getName();
  45. }
  46. return null;
  47. }
  48. });
  49. // 2.验证
  50. Person person = new Person();
  51. person.setId(11);
  52. person.setName("Leifeng");
  53. String s = sayService.sayTo(person);
  54. System.out.println(s);
  55. assertSame("Leifeng", s);
  56. }
  57. @Test
  58. public void testSaySomething() {
  59. System.out.println("testSaySomething...");
  60. // 1.设置预期行为
  61. when(sayDao.saySomething(anyString(), any(Person.class), anyString())).thenAnswer(new Answer<String>() {
  62. @Override
  63. public String answer(InvocationOnMock invocation) throws Throwable {
  64. Object[] args = invocation.getArguments();
  65. if (null != args && args.length > 0) {
  66. String hello = (String) args[0];
  67. Person person = (Person) args[1];
  68. String msg = (String) args[2];
  69. return hello + "," + person.getName() + "(" + person.getId() + ")" + ":" + msg;
  70. // SayDao dao = new SayDaoImpl();
  71. // return dao.saySomething(hello, person, msg);
  72. }
  73. return null;
  74. }
  75. });
  76. // 2.验证
  77. Person person = new Person();
  78. person.setId(12);
  79. person.setName("Leifeng");
  80. String s = sayService.saySomething("Welcome", person, "handsome guy!");
  81. System.out.println(s);
  82. assertNotNull(s);
  83. }
  84. @Test
  85. public void testQueryPerson() {
  86. // 1.预置预期行为
  87. List<Person> personList = new ArrayList<Person>();
  88. // 初始化List《Person》
  89. for (int i = 0; i < 10; i++) {
  90. Person person = new Person();
  91. person.setId(i + 1);
  92. person.setName("name" + i);
  93. personList.add(person);
  94. }
  95. when(sayDao.queryPerson(any(Person.class))).thenReturn(personList);
  96. // 2.验证
  97. Person query = new Person();
  98. query.setId(13);
  99. query.setName("Leifeng");
  100. List<Person> list = sayService.queryPerson(query);
  101. assertTrue(10 == list.size());
  102. // 重要(根据具体业务设计)
  103. assertTrue(3 == list.get(3).getFlag());
  104. }
  105. }
/**
* 类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指定这个监听器来实现单元测试

  1. package org.springframework.test.context.support;
  2. import java.lang.annotation.Annotation;
  3. import java.lang.reflect.Field;
  4. import java.lang.reflect.InvocationTargetException;
  5. import java.lang.reflect.Method;
  6. import java.util.ArrayList;
  7. import java.util.List;
  8. import static org.mockito.Mockito.mock;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.test.context.TestContext;
  11. /**
  12. * @author yanglin lv
  13. */
  14. public class DependencyInjectionAsMockitoTestExecutionListener extends DependencyInjectionTestExecutionListener {
  15. private static String SETTER = "set";
  16. private static String GETTER = "get";
  17. @Override
  18. protected void injectDependencies(final TestContext testContext) throws Exception {
  19. super.injectDependencies(testContext);
  20. Object bean = testContext.getTestInstance();
  21. Class[] mockClass = getMockClass(bean.getClass());
  22. Method[] methods = bean.getClass().getDeclaredMethods();
  23. Class clz = bean.getClass();
  24. Object instance = null;
  25. List<MockObjectMap> objs = new ArrayList<MockObjectMap>();
  26. autowireMockBean(clz, bean, objs);
  27. List<Object> stubObjs = getStubInstance(clz, bean);
  28. autowireMockBeanForSpring(stubObjs, objs);
  29. }
  30. private void autowireMockBeanForSpring(List<Object> stubObjs, List<MockObjectMap> objs)
  31. throws IllegalArgumentException,
  32. IllegalAccessException,
  33. InvocationTargetException {
  34. for (Object object : stubObjs) {
  35. Class claz = object.getClass();
  36. do {
  37. for (Method method : claz.getDeclaredMethods()) {
  38. if (method.getName().startsWith(SETTER)) {
  39. for (MockObjectMap mockObjectMap : objs) {
  40. Object obj = method.getGenericParameterTypes()[0];
  41. if (obj instanceof java.lang.reflect.Type
  42. && mockObjectMap.getType().getName().equalsIgnoreCase(((Class) obj).getName())) {
  43. method.invoke(object, mockObjectMap.getObj());
  44. continue;
  45. }
  46. }
  47. }
  48. }
  49. claz = claz.getSuperclass();
  50. } while (!claz.equals(Object.class));
  51. }
  52. }
  53. private void autowireMockBean(Class clz, Object bean, List<MockObjectMap> objs) throws IllegalArgumentException,
  54. IllegalAccessException {
  55. for (Field field : clz.getFields()) {
  56. Annotation[] mockAnnotations = field.getAnnotations();
  57. for (Annotation annotation : mockAnnotations) {
  58. if (annotation instanceof org.mockito.Mock) {
  59. MockObjectMap mockObjectMap = new MockObjectMap();
  60. objs.add(mockObjectMap);
  61. mockObjectMap.setType(field.getType());
  62. mockObjectMap.setObj(mock(field.getType()));
  63. field.setAccessible(true);
  64. field.set(bean, mockObjectMap.getObj());
  65. continue;
  66. }
  67. }
  68. }
  69. }
  70. /**
  71. * 取得测试类中所有的mock对象的类型
  72. *
  73. * @param clazz
  74. * @return
  75. */
  76. private Class[] getMockClass(Class claz) {
  77. List<Class> clasList = new ArrayList<Class>();
  78. Field[] fields = claz.getDeclaredFields();
  79. for (Field field : fields) {
  80. Annotation[] mockAnnotations = field.getAnnotations();
  81. for (Annotation annotation : mockAnnotations) {
  82. if (annotation instanceof org.mockito.Mock) {
  83. clasList.add(field.getType());
  84. continue;
  85. }
  86. }
  87. }
  88. return clasList.toArray(new Class[0]);
  89. }
  90. /**
  91. * 取得测试类中测试桩类
  92. *
  93. * @param clazz
  94. * @return
  95. * @throws InvocationTargetException
  96. * @throws IllegalAccessException
  97. * @throws IllegalArgumentException
  98. */
  99. private List<Object> getStubInstance(Class clazz, Object bean) throws IllegalArgumentException,
  100. IllegalAccessException, InvocationTargetException {
  101. List<Object> objList = new ArrayList<Object>();
  102. Field[] fields = clazz.getDeclaredFields();// 测试类中所有的域名
  103. Method[] methods = clazz.getDeclaredMethods();
  104. for (Field field : fields) {
  105. Annotation[] mockAnnotations = field.getAnnotations();
  106. for (Annotation annotation : mockAnnotations) {
  107. if (annotation instanceof Autowired) {
  108. for (Method method : methods) {
  109. String name = field.getName();
  110. if (method.getName().startsWith(GETTER) && method.getName().substring(3).equalsIgnoreCase(name)) {
  111. objList.add(method.invoke(bean, null)); // 将所有的测试桩类放在objList
  112. }
  113. }
  114. }
  115. }
  116. }
  117. return objList;
  118. }
  119. private class MockObjectMap {
  120. private Object   obj;
  121. private Class<?> type;
  122. public Object getObj() {
  123. return obj;
  124. }
  125. public void setObj(Object obj) {
  126. this.obj = obj;
  127. }
  128. public Class<?> getType() {
  129. return type;
  130. }
  131. public void setType(Class<?> type) {
  132. this.type = type;
  133. }
  134. }
  135. }
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类设置期望输出简单实现