本文主要是提供了一种解决方案,用于解决spring管理的测试用例在mock过程中,如何有效管理mock宿主和mock实体,并优化mock方法
一、基础类
1、Sping配置基础
@ContextConfiguration(locations = { "classpath:spring.xml" }) public abstract class BaseServiceTest extends AbstractTransactionalJUnit4SpringContextTests { /** * 日志器 */ protected Logger logger = LoggerFactory.getLogger(getClass()); /** * 确认已有异常被抛出 */ protected void checkExceptionRaise() { Assert.assertTrue("can't reach here, a exception should be raised", false); } }
2、通用测试基类
public abstract class BaseTestextends BaseServiceTest { private final Set mockFields = new HashSet (); /** * 既可 mock 接口也可以 mock 类 */ protected final Mockery context = new JUnit4Mockery() { { setImposteriser(ClassImposteriser.INSTANCE); } }; /** * */ @After public void restoreField() { context.assertIsSatisfied(); for (final MockField mockField : mockFields) { ReflectionTestUtils.setField(mockField.getToMock(), mockField.getFieldName(), mockField.getNativeValue()); } } /** * 提取实体类型信息 * * @return 实体类型信息 */ private Class extractGenericParameterInfo() { for (Class> current = getClass(); (current != BaseTest.class) || (current != Object.class); current = current.getSuperclass()) { final Type genericSuperType = current.getGenericSuperclass(); if (!(genericSuperType instanceof ParameterizedType)) { continue; } final ParameterizedType genericSuperClass = (ParameterizedType) genericSuperType; final Type[] actualTypes = genericSuperClass .getActualTypeArguments(); if (actualTypes.length == 0) { continue; } final Type firstType = actualTypes[0]; if (!(firstType instanceof Class)) { continue; } @SuppressWarnings("unchecked") final Class firstClass = (Class ) firstType; return firstClass; } throw new IllegalStateException("无法获取有效的模板参数信息"); } /** * @return */ private Object getMockHost() { Object toMock = getToMock(); if (toMock != null) { return toMock; } final Field[] fields = this.getClass().getDeclaredFields(); for (final Field field : fields) { final Class genericParameterInfo = extractGenericParameterInfo(); if (genericParameterInfo.isAssignableFrom(field.getType())) { Assert.assertNull("重复的mock宿主", toMock); toMock = ReflectionTestUtils.getField(this, field.getName()); } } Assert.assertNotNull("mock宿主不能为空", toMock); return toMock; } /** * @return mock宿主 */ protected Object getToMock() { return null; } /** * 为本测试用例指定的mock宿主,通过 getToMock()
指定,设置指定类型的mock实体 * * @param* mock实体类型 * @param typeToMock * 用来mock的实体类型 * @return mock实体 */ protected E mock(final Class typeToMock) { final E mockObject = context.mock(typeToMock); final Object toMock = getMockHost(); String fieldName = null; final Field[] fields = toMock.getClass().getDeclaredFields(); for (final Field field : fields) { if (typeToMock.isAssignableFrom(field.getType())) { Assert.assertNull("重复的mock实体", fieldName); fieldName = field.getName(); } } Assert.assertNotNull("无法定位mock实体", fieldName); mock(toMock, fieldName, mockObject); return mockObject; } /** * mock对象 * * @param * mock对象类型 * * @param toMock * mock宿主 * @param fieldName * 属性名称 * @param typeToMock * 用来mock的对象类型 * @return mock对象 */ protected E mock(final Object toMock, final String fieldName, final Class typeToMock) { final E mockObject = context.mock(typeToMock); mock(toMock, fieldName, mockObject); return mockObject; } /** * @param toMock * 用来插入mock对象的大对象 * @param fieldName * 属性名称 * @param mockObject * mock对象 */ protected void mock(final Object toMock, final String fieldName, final Object mockObject) { final MockField mockField = new MockField(); mockField.setFieldName(fieldName); mockField.setNativeValue(ReflectionTestUtils .getField(toMock, fieldName)); mockField.setToMock(toMock); Assert.assertTrue("不允许重复mock", !mockFields.contains(mockField)); mockFields.add(mockField); ReflectionTestUtils.setField(toMock, fieldName, mockObject); } /** * 为本测试用例指定的mock宿主,通过 getToMock()
指定,设置指定类型的mock实体 * * @param fieldName * 指定的属性名称 * * @param* mock实体类型 * @param typeToMock * 用来mock的实体类型 * @return mock实体 */ protected E mock(final String fieldName, final Class typeToMock) { final E mockObject = context.mock(typeToMock); mock(getToMock(), fieldName, mockObject); return mockObject; } }
3、派生自该基类的测试类实例
public class XXXXTest extends BaseTest{ @Autowired private XXXXX xxxxx; @Test public void testTTTTTTT() { final YYYYY mockYYYYY = mock(YYYYYY.class); context.checking(new Expectations() { { oneOf(mockYYYYY).doSomething() will(returnValue(true)) } }); Assert.assertTrue(phaseManageBizImpl.TTTTTTT()); } }
二、技术难点
1、mock对象的还原,能够使spring的对象不被单个测试用例破坏
如果不剥离注入到mock宿主中的mock实体,下次再次使用该mock宿主时,由于spring不会对bean再次进行初始化,因此第二次使用的mock宿主行为是不可控的。
2、使用反射简化mock接口
三、优势
1、使用@after和@before完成,不需要增加单个测试用例的工作量
2、获取mock实体时方法简单
final YYYYY mockYYYYY = mock(YYYYYY.class);
可以直接mock掉XXXXX中的YYYYY类型变量,不需要指定宿主和实体名字即可完成mock对象注入和剥离
3、能够通过指定属性名进行扩展
mock方法有多个overwrite,允许通过指定field那么来注入mock实体