Instrumentation Test Class VS JUnit Test Class
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
@Rule
public ActivityTestRule<MainActivity> activity = new ActivityTestRule<MainActivity>(MainActivity.class);
@Test
public void testTxt(){
Espresso.onView(withId(R.id.textview0)).check(matches(withText("Hello World")));
}
}
上面的代碼給出的是一個(gè)很簡(jiǎn)單的Instrumentation Test Class。在代碼中我們看到了熟悉的@Rule,@Test注解。其實(shí),除此之外,我們還能夠使用JUnit支持的其他大部分注解,包括@Suite,@ClassRule,@BeforeClass,@AfterClass,@Before和@After等等,只是在這個(gè)Test Class中沒有體現(xiàn)出來而已??梢赃@樣說,一個(gè)Instrumentation Test Class本質(zhì)上就是一個(gè)JUnit Test Class。因?yàn)锳ndroid Instrumentation Test本來就是基于JUnit框架的。如果非要說它們之間的區(qū)別,那就只能是它們的默認(rèn)Runner不同。對(duì)于JUnit4而言, 它的默認(rèn)Runner是BlockJUnit4ClassRunner(請(qǐng)參照前面一篇文章),而對(duì)于Android Test而言,一個(gè)Test Class的默認(rèn)Runner就是上面的代碼中@RunWith所指明的AndroidJUnit4類。具體類的定義如下:
public final class AndroidJUnit4 extends AndroidJUnit4ClassRunner { ...}
public class AndroidJUnit4ClassRunner extends BlockJUnit4ClassRunner { ...}
AndroidJUnit4只是AndroidJUnit4ClassRunner的一個(gè)別名而已(讓你調(diào)皮,取那么長(zhǎng)的類名),而AndroidJUnit4ClassRunner其實(shí)又是繼承于BlockJUnit4ClassRunner的。查看其源碼,可以發(fā)現(xiàn)AndroidJUnit4ClassRunner的執(zhí)行邏輯99%都交由BlockJUnit4ClassRunner處理,也就是上一篇文章所分析的流程,而它們唯一的一點(diǎn)區(qū)別就是AndroidJUnit4ClassRunner對(duì)@Test中的timeout的處理稍有不同,這里不再具體分析。所以我們可以這樣說,一個(gè)Instrumentation Test Class的執(zhí)行流程同一個(gè)Normal JUnit Test Class是一致的。這里所說的執(zhí)行流程指的僅僅是Test Class對(duì)應(yīng)的Runner執(zhí)行的邏輯,不包括Runner的構(gòu)造和Instrumentation Test的入口流程,這個(gè)流程我們放在下文進(jìn)行分析。
About Instrumentation
我們先來看看Instrumentation Test中的Instrumentation到底是什么,它又是干嘛的?不賣關(guān)子了,Instrumentation其實(shí)是Android Framework中的一個(gè)類,它的作用簡(jiǎn)而言之就是能夠監(jiān)控Android系統(tǒng)和我們Application之間的交互。我們都知道,一個(gè)Application有一個(gè)ActivityThread對(duì)象,負(fù)責(zé)和ActivityMangerService打交道來管理App的運(yùn)行,比如啟動(dòng)某個(gè)Activity,發(fā)送廣播等其他操作。而這個(gè)Instrumentation會(huì)在App啟動(dòng)階段被初始化,然后作為一個(gè)實(shí)例變量保存到ActivityThread對(duì)象中。Application的創(chuàng)建、Activity生命周期方法的回調(diào)等其他操作,都會(huì)經(jīng)過Instrumentation來完成。e.g.
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
...
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
...
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
mInstrumentation.callActivityOnCreate(activity, r.state);
...
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
...
mInstrumentation.callActivityOnPostCreate(activity, r.state);
...
}
這里不再一一列舉,可以自行查看Instrumentation的代碼。那這個(gè)Instrumentation在Android Test中有什么作用呢?在App正常運(yùn)行的時(shí)候,系統(tǒng)會(huì)幫助App維護(hù)運(yùn)行組件的狀態(tài)和信息,這些管理是有必要的,因?yàn)檫@個(gè)過程是非常復(fù)雜的,交由開發(fā)者自己去完成很容易造成系統(tǒng)的混亂。系統(tǒng)管理的好處就是簡(jiǎn)單方便,但同時(shí)也造成了開發(fā)者不能很方便的得到運(yùn)行組件的信息,好在正常運(yùn)行的極大多數(shù)情況下我們都不需要訪問這些信息。然而當(dāng)我們?cè)跍y(cè)試時(shí),就另當(dāng)別論了,我們可能需要頻繁地訪問當(dāng)前正在運(yùn)行的某個(gè)組件的信息,比如Activity。這個(gè)時(shí)候,Instrumentation就派上用場(chǎng)了。有些時(shí)候我們可能需要擴(kuò)展當(dāng)前的Instrumentation類,為此Android允許我們?cè)贏ndroidManifest文件中創(chuàng)建<instrumentaion>標(biāo)簽,用來指定在創(chuàng)建Instrumentation時(shí)使用我們自定義的類,<instrumentaion>中需要至少包含以下兩個(gè)屬性:
- android:name:指定使用這個(gè)類來創(chuàng)建,而不是系統(tǒng)默認(rèn)的Instrumentation類,需要為Instrumentation的子類才合法。
- android:targetPackage:指定要監(jiān)控的目標(biāo)app包名;這里一般是指我們需要測(cè)試的目標(biāo)app包名。
請(qǐng)注意,Android僅在開發(fā)者進(jìn)行app測(cè)試的時(shí)候,也就是說只能在我們的Instrumentation Test Class的測(cè)試代碼中才能訪問到Instrumentation。當(dāng)App正常運(yùn)行時(shí)沒有可供訪問的開放接口使用(當(dāng)然不排除某些黑科技方法),這一點(diǎn)非常重要。當(dāng)然,這并不代表app正常運(yùn)行時(shí),系統(tǒng)沒有構(gòu)建Instrumentation對(duì)象;只是系統(tǒng)在這個(gè)時(shí)候會(huì)忽略我們的<instrumentaion>標(biāo)簽,創(chuàng)建一個(gè)默認(rèn)的Instrumentation對(duì)象。
Run An Instrumentation Test Class
下面,我們通過Android Studio來運(yùn)行上面的Test Class,來看看IDE是怎么做的?當(dāng)然,我們首先需要在build.gradle文件中添加下面的配置:
android {
...
defaultConfig {
...
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
}
...
}
現(xiàn)在,我們先不用去考慮上面的配置到底起了的是什么作用?右鍵Test Class->Run 起來再說,同時(shí)請(qǐng)注意觀察Run窗口的相應(yīng)輸出。直接粘貼如下:
$ adb push D:\AndroidCode\StudioCode\AndroidTestPractice\app\build\outputs\apk\app-debug.apk /data/local/tmp/com.lcd.androidtestpractice
$ adb shell pm install -r "/data/local/tmp/com.lcd.androidtestpractice"
pkg: /data/local/tmp/com.lcd.androidtestpractice
Success
$ adb push D:\AndroidCode\StudioCode\AndroidTestPractice\app\build\outputs\apk\app-debug-androidTest-unaligned.apk /data/local/tmp/com.lcd.androidtestpractice.test
$ adb shell pm install -r "/data/local/tmp/com.lcd.androidtestpractice.test"
pkg: /data/local/tmp/com.lcd.androidtestpractice.test
Success
Running tests
$ adb shell am instrument -w -r -e debug false -e class com.lcd.androidtestpractice.MainActivityTest com.lcd.androidtestpractice.test/android.support.test.runner.AndroidJUnitRunner
Client not ready yet..Test running started
Tests ran to completion.
從Run窗口的輸出可以很清晰的看到IDE進(jìn)行了哪些暗箱操作?
- 打包并安裝com.lcd.androidtestpractice包,這個(gè)包是我們的主程序App包,沒什么好說的。
- 打包并安裝com.lcd.androidtestpractice.test。原來,IDE幫我們打了一個(gè)新的apk包,包名為主包名后面加上
.test后綴。我們找到對(duì)應(yīng)的目錄,發(fā)現(xiàn)確實(shí)是存在對(duì)應(yīng)的新apk。我們可以反編譯一下這個(gè)apk,看看這個(gè)包里都包含了哪些內(nèi)容。下面給出反編譯后結(jié)果的幾張截圖:
AndroidTestCompile依賴的代碼
我們的測(cè)試代碼
代碼部分包含了build.gradle文件中androidTestCompile指定要編譯的部分、包含有我們的測(cè)試代碼;再來看看AndroidManifest文件的內(nèi)容。
manifest文件
這個(gè)清單文件內(nèi)容較少,首先里面沒有聲明任何的組件,所以安裝之后,不會(huì)在Launcher上看到對(duì)應(yīng)的應(yīng)用圖標(biāo)。再仔細(xì)看看,我們發(fā)現(xiàn)了<instrumentation>標(biāo)簽,里面指定了name屬性值為android.support.test.runner.AndroidJUnitRunner且targetPackage屬性值為com.lcd.androidtestpractice。targetPackage屬性比較好理解,這個(gè)包名的值就是我們主程序的app包名,這里的意思就是指定它為要測(cè)試的目標(biāo)app;再來看看name屬性,我們發(fā)現(xiàn)這個(gè)值和我們剛剛在build.gradle中配置的testInstrumentationRunner屬性值相等。它們會(huì)不會(huì)有什么聯(lián)系呢?Bingo!當(dāng)我們?cè)赽uild.gradle文件中運(yùn)用testInstrumentationRunner 'customname'時(shí),Android Studio在打包測(cè)試APK時(shí)就會(huì)在manifest中添加<Instrumentation>標(biāo)簽,并且將name屬性值指定為customname。那這里為什么要指定testInstrumentationRunner值為AndroidJUnitRunner呢?能不能是另外一個(gè)其他的值呢?當(dāng)然可以!這里的值其實(shí)只需要是指向一個(gè)Instrumentation類或者子類的全域限定的類名。我們之所以指定為AndroidJUnitRunner是因?yàn)锳ndroid將測(cè)試代碼的執(zhí)行邏輯放到這個(gè)類中,測(cè)試代碼就靠它來運(yùn)行的。顯然,我們完全可以實(shí)現(xiàn)一個(gè)自定義類繼承于AndroidJUnitRunner,并通過testInstrumentationRunner來聲明使用它,這樣不僅不會(huì)阻礙我們測(cè)試代碼的運(yùn)行,還可以通過覆寫它的某些方法來達(dá)成某些目標(biāo)。這在某些情況下很有用,比如我們想要自定義測(cè)試時(shí)創(chuàng)建的Application對(duì)象,就可以通過繼承AndroidJUnitRunner并重寫public Application newApplication(ClassLoader cl, String className, Context context)來實(shí)現(xiàn)。這里有個(gè)例子可以看看 - 不多說了,我們現(xiàn)在再回過頭來看看IDE執(zhí)行的第三步。
adb shell am instrument -w -r -e debug false -e class com.lcd.androidtestpractice.MainActivityTest com.lcd.androidtestpractice.test/android.support.test.runner.AndroidJUnitRunner
其實(shí)就是通過adb運(yùn)行am instrument命令。所以AndroidTest也是可以通過ADB手動(dòng)運(yùn)行的,IDE只是為我們簡(jiǎn)化了流程。來看具體的命令,很明顯里面指定的MainActivityTest就是我們要執(zhí)行的Test Class,而com.lcd.androidtestpractice.test/android.support.test.runner.AndroidJUnitRunner這一部分其實(shí)標(biāo)識(shí)了一個(gè)Instrumentation。前半部分com.lcd.androidtestpractice.test為apk包名,后半部分AndroidJUnitRunner為目標(biāo)類名,兩個(gè)部分加起來就唯一確定了一個(gè)Instrumentation對(duì)象。命令中其他各種配置參數(shù)和使用方法就不多說了,更多詳情在這里。我們關(guān)注的是這個(gè)命令到底干了些什么?
Am Instrument
Am Instrument命令會(huì)調(diào)用Am中的runInstrument()方法,在這個(gè)方法中,解析輸入的參數(shù)并最終將請(qǐng)求發(fā)送到ActivityManagerService中的startInstrumentation方法。好吧,一切還是得由AMS來完成。
public boolean startInstrumentation(ComponentName className,
String profileFile, int flags, Bundle arguments,
IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection,
int userId, String abiOverride) {
...
InstrumentationInfo ii = null; //包含當(dāng)前App的<Instrumentation>標(biāo)簽信息
ApplicationInfo ai = null; //包含目標(biāo)App的<Application>標(biāo)簽信息
try {
ii = mContext.getPackageManager().getInstrumentationInfo(
className, STOCK_PM_FLAGS);
ai = AppGlobals.getPackageManager().getApplicationInfo(
ii.targetPackage, STOCK_PM_FLAGS, userId);
} catch (PackageManager.NameNotFoundException e) {
} catch (RemoteException e) {
}
//通過PackageManager檢查目標(biāo)App和測(cè)試App的簽名是否相同
//只有簽名相同才能進(jìn)行Instrumentation Test
//簽名不相同,失敗并拋出異常
int match = mContext.getPackageManager().checkSignatures(
ii.targetPackage, ii.packageName);
if (match < 0 && match != PackageManager.SIGNATURE_FIRST_NOT_SIGNED) {
...
reportStartInstrumentationFailure(watcher, className, msg);
throw new SecurityException(msg);
}
//先停止當(dāng)前的Target App
forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, false, userId,
"start instr");
//重新啟動(dòng)目標(biāo)App進(jìn)程
ProcessRecord app = addAppLocked(ai, false, abiOverride);
//記錄必要信息,注意這里是唯一賦值的地方
//所以只有從這個(gè)入口過去的,才會(huì)創(chuàng)建Instrumentation實(shí)例
//否則,即使在manifest中指定,也不會(huì)創(chuàng)建對(duì)應(yīng)實(shí)例
app.instrumentationClass = className;
app.instrumentationInfo = ai;
app.instrumentationArguments = arguments;
...
}
return true;
}
這里傳入startInstrumentation的參數(shù)className是一個(gè)Component對(duì)象,它是在Am中由com.lcd.androidtestpractice.test/android.support.test.runner.AndroidJUnitRunner解析過來的,而argument這個(gè)bundle在這里則包含有我們需要執(zhí)行的Test Class類名。其他流程請(qǐng)看上面代碼中的注釋說明。這里要強(qiáng)調(diào)一下,一旦檢查通過,該方法會(huì)停止當(dāng)前正在運(yùn)行的目標(biāo)App,然后重新啟動(dòng)目標(biāo)進(jìn)程。當(dāng)目標(biāo)進(jìn)程的ActivityThread對(duì)象創(chuàng)建以后,會(huì)通過attachApplication()方法請(qǐng)求Ams給它綁定一個(gè)Application。來看看Ams的處理:
private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
...
//執(zhí)行Test App和Target App的dexopt
ensurePackageDexOpt(app.instrumentationInfo != null
? app.instrumentationInfo.packageName
: app.info.packageName);
if (app.instrumentationClass != null) {
ensurePackageDexOpt(app.instrumentationClass.getPackageName());
}
//因?yàn)閍pp.instrumentationInfo已經(jīng)在startInstrumentation方法中賦值為目標(biāo)App的ApplicationInfo
//所以不為空
ApplicationInfo appInfo = app.instrumentationInfo != null
? app.instrumentationInfo : app.info;
...
//返回到ActivityThread中去處理,這里的appInfo為Target App的ApplicationInfo
thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(mConfiguration), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked());
...
}
之后,輾轉(zhuǎn)回到ActivityThread的handleBindApplication方法。
private void handleBindApplication(AppBindData data) {
mBoundApplication = data;
//data.appInfo是Ams傳過來的參數(shù),為target app的ApplicationInfo
//所以這里會(huì)根據(jù)ApplicationInfo去load Target Apk
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
//創(chuàng)建一個(gè)Target App的context對(duì)象
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
...
//同樣是從Ams傳過來的參數(shù)
//= 'com.lcd.androidtestpractice.test/android.support.test.runner.AndroidJUnitRunner`
if (data.instrumentationName != null) {
InstrumentationInfo ii = null;
try {
//獲取<instrumentation>標(biāo)簽的信息
ii = appContext.getPackageManager().
getInstrumentationInfo(data.instrumentationName, 0);
} catch (PackageManager.NameNotFoundException e) {
}
...
//記錄Instrumentation相關(guān)信息
mInstrumentationPackageName = ii.packageName;
mInstrumentationAppDir = ii.sourceDir;//Test App的路徑
mInstrumentationSplitAppDirs = ii.splitSourceDirs;
mInstrumentationLibDir = ii.nativeLibraryDir;
mInstrumentedAppDir = data.info.getAppDir();//Target App的路徑
mInstrumentedSplitAppDirs = data.info.getSplitAppDirs();
mInstrumentedLibDir = data.info.getLibDir();
//根據(jù)InstrumentataionInfo構(gòu)造對(duì)應(yīng)Test App的ApplicationInfo
ApplicationInfo instrApp = new ApplicationInfo();
instrApp.packageName = ii.packageName;
instrApp.sourceDir = ii.sourceDir;
instrApp.publicSourceDir = ii.publicSourceDir;
instrApp.splitSourceDirs = ii.splitSourceDirs;
instrApp.splitPublicSourceDirs = ii.splitPublicSourceDirs;
instrApp.dataDir = ii.dataDir;
instrApp.nativeLibraryDir = ii.nativeLibraryDir;
//根據(jù)ApplicationInfo Load Test App
//appContext.getClassLoader()是target app的classloader
//這里傳入它作為test apk的base class loader
LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
appContext.getClassLoader(), false, true, false);
//構(gòu)造一個(gè)Test App的context對(duì)象
ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
try {
//這個(gè)classloader對(duì)應(yīng)為test app的classloader,所以能夠加載test apk中的類
java.lang.ClassLoader cl = instrContext.getClassLoader();
//從test apk中創(chuàng)建一個(gè)Instrumentation實(shí)例對(duì)象
//這里其實(shí)就是構(gòu)造一個(gè)android.support.test.runner.AndroidJUnitRunner實(shí)例
mInstrumentation = (Instrumentation)
cl.loadClass(data.instrumentationName.getClassName()).newInstance();
} catch (Exception e) {
...
}
//初始化
mInstrumentation.init(this,
instrContext,/*對(duì)應(yīng)test app的context,從Instrumentation調(diào)用getContext就返回這個(gè)context*/
appContext,/*對(duì)應(yīng)target app的context,從Instrumentation調(diào)用getTargetContext就返回這個(gè)context*/
new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher,
data.instrumentationUiAutomationConnection);
...
} else {
//創(chuàng)建一個(gè)系統(tǒng)默認(rèn)的Instrumentation對(duì)象
mInstrumentation = new Instrumentation();
}
//創(chuàng)建application對(duì)象,這里data.info指向target app的loadedapk對(duì)象
Application app = data.info.makeApplication(data.restrictedBackupMode,null);
mInitialApplication = app;
...
//調(diào)用Instrumentation的onCreate方法
mInstrumentation.onCreate(data.instrumentationArgs);
...
}
具體的說明請(qǐng)看上面的注釋。上面的方法調(diào)用之后,ActivityThread中的mPackages變量會(huì)包含兩個(gè)LoadedApk,分別對(duì)應(yīng)Test app和target app。我們可以看到,通過使用Instrumentation,Android將Test App和Target App同時(shí)加載到了同一個(gè)進(jìn)程中。到這里,我們已經(jīng)創(chuàng)建了AndroidJUnitRunner對(duì)象實(shí)例。來看看onCreate方法。
public class AndroidJUnitRunner extends MonitoringInstrumentation {
...
@Override
public void onCreate(Bundle arguments) {
//保存并解析參數(shù)
mArguments = arguments;
parseRunnerArgs(mArguments);
//調(diào)用父類實(shí)現(xiàn)
super.onCreate(arguments);
...
start();
}
}
再來看看父類MonitoringInstrumentation的onCreate實(shí)現(xiàn)。
public void onCreate(Bundle arguments) {
//向InstrumentationRegistry中注冊(cè)這個(gè)Instrumentation實(shí)例
//這樣在測(cè)試代碼中,就可以通過InstrumentationRegistry.getInstrumentation()方法獲取這個(gè)實(shí)例
InstrumentationRegistry.registerInstance(this, arguments);
ActivityLifecycleMonitorRegistry.registerInstance(mLifecycleMonitor);
ApplicationLifecycleMonitorRegistry.registerInstance(mApplicationMonitor);
IntentMonitorRegistry.registerInstance(mIntentMonitor);
mHandlerForMainLooper = new Handler(Looper.getMainLooper());
final int corePoolSize = 0;
final long keepAliveTime = 0L;
mExecutorService = new ThreadPoolExecutor(corePoolSize, Integer.MAX_VALUE, keepAliveTime,
TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactory() {
@Override
public Thread newThread(Runnable runnable) {
Thread thread = Executors.defaultThreadFactory().newThread(runnable);
thread.setName(MonitoringInstrumentation.class.getSimpleName());
return thread;
}
});
Looper.myQueue().addIdleHandler(mIdleHandler);
//調(diào)用Instrumentation的onCreate方法,空方法,不關(guān)注
super.onCreate(arguments);
specifyDexMakerCacheProperty();
setupDexmakerClassloader();
}
onCreate方法結(jié)束之后,AndroidJUnitRunner緊接著調(diào)用start()方法。start()的實(shí)現(xiàn)位于基類Instrumentation中,如下:
public void start() {
if (mRunner != null) {
throw new RuntimeException("Instrumentation already started");
}
mRunner = new InstrumentationThread("Instr: " + getClass().getName());
mRunner.start();
}
private final class InstrumentationThread extends Thread {
...
public void run() {
...
onStart();
}
}
InstrumentationThread是一個(gè)線程類,在start()方法中會(huì)新建這個(gè)線程并啟動(dòng),線程轉(zhuǎn)而執(zhí)行onStart()方法。該方法的具體實(shí)現(xiàn)在AndroidJUnitRunner中。
@Override
public void onStart() {
...
TestExecutor.Builder executorBuilder = new TestExecutor.Builder(this);
addListeners(mRunnerArgs, executorBuilder);
TestRequest testRequest = buildRequest(mRunnerArgs, getArguments());
results = executorBuilder.build().execute(testRequest);
...
finish(Activity.RESULT_OK, results);
}
山重水復(fù)疑無路,很接近了,耐心!在onStart()這個(gè)方法中構(gòu)建了一個(gè)TestRequest,然后又構(gòu)建一個(gè)TestExecutor,并調(diào)用其execute()方法執(zhí)行這個(gè)test request,最后調(diào)用finish()方法。我們先來看看finish做了什么?
public void finish(int resultCode, Bundle results) {
...
mThread.finishInstrumentation(resultCode, results);
}
finish方法通過ActivityThread的finishInstrumentation方法通知Ams完成測(cè)試工作,Ams最后來做一些收尾的清理工作并結(jié)束當(dāng)前進(jìn)程。代碼就不給出了。所以到finish的時(shí)候,我們的測(cè)試工作已經(jīng)完成了,所以我們可以肯定我們的測(cè)試代碼就是通過TestExecutor來運(yùn)行的?,F(xiàn)在回頭來看看它的execute()方法,看看具體怎么執(zhí)行。
public Bundle execute(TestRequest testRequest) {
...
JUnitCore testRunner = new JUnitCore();
setUpListeners(testRunner);
junitResults = testRunner.run(testRequest.getRequest());
junitResults.getFailures().addAll(testRequest.getFailures());
...
}
柳暗花明又一村??!execute()方法中構(gòu)建了一個(gè)JUnitCore對(duì)象,調(diào)用其run(request)方法執(zhí)行。等等,這里的JUnitCore不就是我們上篇分析的JUnit執(zhí)行Test Class的入口嗎?還需要繼續(xù)嗎?我想,到這里應(yīng)該可以告一段落了,因?yàn)橥蟮膱?zhí)行邏輯跟我們前面分析的JUnit的執(zhí)行邏輯是一樣一樣的。這里在提一點(diǎn),Android通過覆寫AllDefaultPossibilitiesBuilder來為Test Class生成默認(rèn)Runner。
class AndroidRunnerBuilder extends AllDefaultPossibilitiesBuilder {
...
public AndroidRunnerBuilder(AndroidRunnerParams runnerParams) {
super(true);
mAndroidJUnit3Builder = new AndroidJUnit3Builder(runnerParams);
mAndroidJUnit4Builder = new AndroidJUnit4Builder(runnerParams);
mAndroidSuiteBuilder = new AndroidSuiteBuilder(runnerParams);
mAndroidAnnotatedBuilder = new AndroidAnnotatedBuilder(this, runnerParams);
mIgnoredBuilder = new IgnoredBuilder();
}
...
}
Android在為一個(gè)Test Class構(gòu)造Runner時(shí),使用的就是這個(gè)RunnerBuilder。該RunnerBuilder提供了基于不同JUnit版本的默認(rèn)Android Runner版本。比如我們的Test Class不顯示的聲明@RunWith,那么基于JUnit4,RunnerBuilder給我們構(gòu)造的就是AndroidJUnit4ClassRunner,其實(shí)就是AndroidJUnit4這個(gè)Runner。
最后的最后,再提一點(diǎn)。那就是class loader的問題。因?yàn)槭莾蓚€(gè)apk運(yùn)行在同一個(gè)進(jìn)程里面,怎么保證類的加載不會(huì)出錯(cuò)呢?其實(shí)Android這點(diǎn)已經(jīng)為我們處理了。在ActivityThread中有幾個(gè)成員變量,保存了Test App和Target App的apk路徑信息。
String mInstrumentationPackageName = null; //test app package name
String mInstrumentationAppDir = null; //test app apk path
String[] mInstrumentationSplitAppDirs = null;
String mInstrumentationLibDir = null;
String mInstrumentedAppDir = null; //target app apk path
String[] mInstrumentedSplitAppDirs = null;
String mInstrumentedLibDir = null;
這些信息用于LoadedApk構(gòu)建相應(yīng)的classloader,從而可以滿足從test app或者target app中正確的load我們想要的類。具體可以看LoadedApk中的getClassLoader()方法,這里就不再講述。最后的最后的最后,給出android官方的一張圖,請(qǐng)自行腦補(bǔ)!



