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)