Android单元测试框架有很多,我们首先分析Mockito.
Mockito作为Android常用单元测试框架之一,为大家熟悉的作用就是打桩来模拟环境,但是随着需求的变化往往需要对Mockito流程做出稍许修改,我们常用的是对Mockito的类进行继承,甚至如果Mockito不能实现我们的需求,还需要自定义单元测试框架。
一、Mockito的mock过程
Mockito.mock(Object.class);
这是Mockito模拟对象最基本的用法,模拟对象后就可以对对象进行打桩。所谓打桩,就是方法模拟,比如设置某个方法的返回值为自定义的值,在打桩之后就可以保证环境的证券,之后才能通过断言来进行单元测试。很明显,模拟后的对象不是原对象。通过输出模拟类的类信息,可以发现这个模拟的对象是原对象的子类。为什么我明明没有看到有 XXX entends Object{}这样的类却能够生成模拟对象的子类呢?在这里Mockito是通过动态生成字节码来生成模拟对象的子类。
Mockito引用库结构:
可以看出,Mockito引用了CGLIB框架,而CGLIB又引用了ASM框架,有TX可能会问,这两个框架是干什么用的呢?其实了解了这两个框架基本就能了解Mockito的运作原理。
先说说各自的作用,CGLIB的作用是创建动态代理,根据《Think in java》中的说法,动态代理的目的是为了添加额外的操作。而在单元测试模拟对象的过程中需要添加哪些操作呢?我们可以自己思考下,如果自己来实现模拟对象要做哪些事呢?
假如需要模拟的类如下:
public class MockTest
{
private void test(Object... args)
{
someOperate();
}
private void someOperate()
{
}
}
为了实现控制方法的返回值,我们需要在每个方法的开头添加类似如下代码
if(mocked)
{
try
{
mockMethod.invoke(receiver,args);
}
catch(Exception e)
{
e.printStackTrace();
}
}
新生成的类类似这样
public class MockTest
{
private boolean mocked;
private Method mockMethod;
private Object receiver;
private void test(Object... args)
{
if(mocked)
{
try
{
mockMethod.invoke(receiver,args);
}
catch(Exception e)
{
e.printStackTrace();
}
}
someOperate();
}
private void someOperate()
{
}
}
为了能够验证哪个方法是否已经执行或者执行多少次,需要记录方法调用,需要添加类似如下代码
if(recorded)
{
if(!methodsRecord.keySet().contains("test"))
{
methodsRecord.put("test",0);
}
int count=methodsRecord.get("test");
methodsRecord.put("test",++count);
}
之后新生成的类类似这样
public class MockTest
{
private boolean recorded;
private Map<String,Integer> methodsRecord=new HashMap<>();
private void test(Object... args)
{
if(recorded)
{
if(!methodsRecord.keySet().contains("test"))
{
methodsRecord.put("test",0);
}
int count=methodsRecord.get("test");
methodsRecord.put("test",++count);
}
someOperate();
}
private void someOperate()
{
}
}
除了这些,还有验证方法参数等,需要一个容器保存参数值等,这些方法都需要我们插入到模拟方法中。这些操作就是动态代理中的“额外操作”。关于动态代理可以看看这篇博文Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM),里面也讲了ASM的一些原理。
二、从源代码方面看Mockito通过动态代理实现对象模拟
通过上述第一行代码,我们可以查到模拟对象实现地方:org.mockito.internal MockitoCore.java
public <T> T mock(Class<T> typeToMock, MockSettings settings) {
if (!MockSettingsImpl.class.isInstance(settings)) {
throw new IllegalArgumentException(
"Unexpected implementation of '" + settings.getClass().getCanonicalName() + "'\n"
+ "At the moment, you cannot provide your own implementations that class.");
}
MockSettingsImpl impl = MockSettingsImpl.class.cast(settings);
MockCreationSettings<T> creationSettings = impl.confirm(typeToMock);
T mock = mockUtil.createMock(creationSettings);
mockingProgress.mockingStarted(mock, typeToMock);
return mock;
}
继续查找createMock()方法:这里通过org.mockito.internal.util 的MockUtil.java动态调用了org.mockito.internal.creation.cglib CglibMockMaker.java类
public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
InternalMockHandler mockitoHandler = cast(handler);
new AcrossJVMSerializationFeature().enableSerializationAcrossJVM(settings);
return new ClassImposterizer(new InstantiatorProvider().getInstantiator(settings)).imposterise(
new MethodInterceptorFilter(mockitoHandler, settings), settings.getTypeToMock(), settings.getExtraInterfaces());
}
可以看到主要生产<T>泛型的方法为imposterise()
public <T> T imposterise(final MethodInterceptor interceptor, Class<T> mockedType, Class<?>... ancillaryTypes) {
Class<Factory> proxyClass = null;
Object proxyInstance = null;
try {
setConstructorsAccessible(mockedType, true);
proxyClass = createProxyClass(mockedType, ancillaryTypes);
proxyInstance = createProxy(proxyClass, interceptor);
return mockedType.cast(proxyInstance);
} catch (ClassCastException cce) {
throw new MockitoException(join(
"ClassCastException occurred while creating the mockito proxy :",
" class to mock : " + describeClass(mockedType),
" created class : " + describeClass(proxyClass),
" proxy instance class : " + describeClass(proxyInstance),
" instance creation by : " + instantiator.getClass().getSimpleName(),
"",
"You might experience classloading issues, disabling the Objenesis cache *might* help (see MockitoConfiguration)"
), cce);
} finally {
setConstructorsAccessible(mockedType, false);
}
}
查找createProxy()方法
private Object createProxy(Class<Factory> proxyClass, final MethodInterceptor interceptor) {
Factory proxy;
try {
proxy = instantiator.newInstance(proxyClass);
} catch (InstantationException e) {
throw new MockitoException("Unable to create mock instance of type '" + proxyClass.getSuperclass().getSimpleName() + "'", e);
}
proxy.setCallbacks(new Callback[] {interceptor, SerializableNoOp.SERIALIZABLE_INSTANCE });
return proxy;
}
继续查找Initantiator的方法,发现creaye仅仅是将参数中的class对象进行实例化,所以应该查找上一块代码的createProxyClass()方法,好了接下来就是真正核心的部分
public Class<Factory> createProxyClass(Class<?> mockedType, Class<?>... interfaces) {
if (mockedType == Object.class) {
mockedType = ClassWithSuperclassToWorkAroundCglibBug.class;
}
Enhancer enhancer = new Enhancer() {
@Override
@SuppressWarnings("unchecked")
protected void filterConstructors(Class sc, List constructors) {
// Don't filter
}
};
Class<?>[] allMockedTypes = prepend(mockedType, interfaces);
enhancer.setClassLoader(SearchingClassLoader.combineLoadersOf(allMockedTypes));
enhancer.setUseFactory(true);
if (mockedType.isInterface()) {
enhancer.setSuperclass(Object.class);
enhancer.setInterfaces(allMockedTypes);
} else {
enhancer.setSuperclass(mockedType);
enhancer.setInterfaces(interfaces);
}
enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class, NoOp.class});
enhancer.setCallbackFilter(IGNORE_BRIDGE_METHODS);
if (mockedType.getSigners() != null) {
enhancer.setNamingPolicy(NAMING_POLICY_THAT_ALLOWS_IMPOSTERISATION_OF_CLASSES_IN_SIGNED_PACKAGES);
} else {
enhancer.setNamingPolicy(MockitoNamingPolicy.INSTANCE);
}
enhancer.setSerialVersionUID(42L);
try {
return enhancer.createClass();
} catch (CodeGenerationException e) {
if (Modifier.isPrivate(mockedType.getModifiers())) {
throw new MockitoException("\n"
+ "Mockito cannot mock this class: " + mockedType
+ ".\n"
+ "Most likely it is a private class that is not visible by Mockito");
}
throw new MockitoException("\n"
+ "Mockito cannot mock this class: " + mockedType
+ "\n"
+ "Mockito can only mock visible & non-final classes."
+ "\n"
+ "If you're not sure why you're getting this error, please report to the mailing list.", e);
}
}
这个地方可以看到在做很多设置,包括设置父类设置拦截函数等,最后实现Class对象的部分是Enhance.createClass()
protected Object create(Object key) {
try {
Class gen = null;
synchronized (source) {
ClassLoader loader = getClassLoader();
Map cache2 = null;
cache2 = (Map)source.cache.get(loader);
if (cache2 == null) {
cache2 = new HashMap();
cache2.put(NAME_KEY, new HashSet());
source.cache.put(loader, cache2);
} else if (useCache) {
Reference ref = (Reference)cache2.get(key);
gen = (Class) (( ref == null ) ? null : ref.get());
}
if (gen == null) {
Object save = CURRENT.get();
CURRENT.set(this);
try {
this.key = key;
if (attemptLoad) {
try {
gen = loader.loadClass(getClassName());
} catch (ClassNotFoundException e) {
// ignore
}
}
if (gen == null) {
byte[] b = strategy.generate(this);
String className = ClassNameReader.getClassName(new ClassReader(b));
getClassNameCache(loader).add(className);
gen = ReflectUtils.defineClass(className, b, loader);
}
if (useCache) {
cache2.put(key, new WeakReference(gen));
}
return firstInstance(gen);
} finally {
CURRENT.set(save);
}
}
}
return firstInstance(gen);
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw new CodeGenerationException(e);
}
}
主要部分在这一块
if (gen == null)
{
byte[] b = strategy.generate(this);//动态生成字节码的字节数组
String className = ClassNameReader.getClassName(new ClassReader(b));
getClassNameCache(loader).add(className);
gen = ReflectUtils.defineClass(className, b, loader);//通过字节数组生成class对象
}
其中strategy.generate(this)属于ASM部分,有兴趣的了解下,最后的ReflectUtils.defineClass()用来通过字节数组来生成模拟类。
好了,最后总结下,根据以上代码的分析,我们知道Mockito首先保存设置的类信息,例如模拟类的父类,对象方法拦截操作等,然后根据这些类的信息来动态生成class对象,这个对象是模拟对象的子类,所有方法都插入了拦截事件,可以动态的设置方法返回值,可以动态的获取某些方法的调用次数,可以动态获取某方法传入参数。最后将这些信息提供给开发者,用于开发者利用这些信息来进行对比,从而判断测试的正确还是错误。
参考:
1.Android最佳Mock单元测试方案:Junit + Mockito + Powermock
2.Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)