真正意义上的spring环境中的单元测试方案spring-test与mokito完美结合
- 博客分类:
- java 测试
单元测试SpringCC++C# 一.要解决的问题:
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
- *
- * @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());
- }
- }
/** * 类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指定这个监听器来实现单元测试
- 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;
- }
- }
- }
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类设置期望输出简单实现