更有效的mock,优化spring框架上的mock方法

更有效的mock,优化spring框架下的mock方法

本文主要是提供了一种解决方案,用于解决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 BaseTest<T> extends BaseServiceTest {
	private final Set<MockField> mockFields = new HashSet<MockField>();
	/**
	 * 既可 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<T> 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<T> firstClass = (Class<T>) 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<T> 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宿主,通过<code>getToMock()</code>指定,设置指定类型的mock实体
	 * 
	 * @param <E>
	 *            mock实体类型
	 * @param typeToMock
	 *            用来mock的实体类型
	 * @return mock实体
	 */
	protected <E> E mock(final Class<E> 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 <E>
	 *            mock对象类型
	 * 
	 * @param toMock
	 *            mock宿主
	 * @param fieldName
	 *            属性名称
	 * @param typeToMock
	 *            用来mock的对象类型
	 * @return mock对象
	 */
	protected <E> E mock(final Object toMock, final String fieldName,
			final Class<E> 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宿主,通过<code>getToMock()</code>指定,设置指定类型的mock实体
	 * 
	 * @param fieldName
	 *            指定的属性名称
	 * 
	 * @param <E>
	 *            mock实体类型
	 * @param typeToMock
	 *            用来mock的实体类型
	 * @return mock实体
	 */
	protected <E> E mock(final String fieldName, final Class<E> typeToMock) {
		final E mockObject = context.mock(typeToMock);
		mock(getToMock(), fieldName, mockObject);
		return mockObject;
	}
}

 3、派生自该基类的测试类实例

public class XXXXTest extends BaseTest<XXXXX > {
	@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实体