JUnit 4是一種流行的Java單元測試工具。使用JUnit 4和androidx.test包能夠爲Android程序編寫單元測試和Instrument測試。下面我對JUnit 4在Android程序上的使用作一些簡單的分析。android
首先簡單介紹一下JUnit 4。JUnit 4是一個單元測試框架,簡單來講,JUnit 4的工做就是運行單元測試,而後將結果展現給用戶。在這個過程當中涉及3個核心概念:表示單元測試的Statement、運行單元測試的Runner,以及接收測試數據,展現結果的RunNotifier。api
下面的代碼摘錄自org.junit.runners.ParentRunner,這段代碼很好的展現了3個概念之間的關係:Runner運行Statement,將結果通知給RunNotifier。app
@Override public void run(final RunNotifier notifier) { EachTestNotifier testNotifier = new EachTestNotifier(notifier, getDescription()); try { Statement statement = classBlock(notifier); statement.evaluate(); } catch (AssumptionViolatedException e) { testNotifier.addFailedAssumption(e); } catch (StoppedByUserException e) { throw e; } catch (Throwable e) { testNotifier.addFailure(e); } }
org.junit.runner.Runner是一個抽象類,擁有兩個抽象方法:框架
public abstract Description getDescription(); public abstract void run(RunNotifier notifier);
一般咱們不會直接接觸到Runner類。若是想本身編寫Runner,能夠從org.junit.runners.BlockJUnit4ClassRunner派生。BlockJUnit4ClassRunner也是JUnit 4默認的Runner類。若是不想使用默認Runner,能夠在測試類上添加註解@RunWith,設置測試須要的Runner。在測試Android程序時,一般會加上ide
@RunWith(AndroidJUnit4::class)
這行代碼會使用androidx.test.ext.junit.runners.AndroidJUnit4做爲Runner。AndroidJUnit4是一個包裝類,它會檢查系統屬性java.runtime.name,若是其中包含字符串android,AndroidJUnit4會使用androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner做爲實際的Runner。不然將使用org.robolectric.RobolectricTestRunner。而AndroidJUnit4ClassRunner正式派生自BlockJUnit4ClassRunner。函數
這裏說一下@RunWith生效的過程。在運行測試的時候(好比Gradle使用的org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor任務),首先經過org.junit.runner.Request獲得ClassRequest對象,ClassRequest內部經過AllDefaultPossibilitiesBuilder按照下列順序工具
逐一尋找合適的RunnerBuilder,而後構建出Runner。聽起來很複雜,實際上只需兩行代碼:單元測試
final Request request = Request.aClass((Class)testClass); final Runner runner = request.getRunner();
Statement表示一個測試用例,測試用例只有一個動做:執行。所以Statement類很是簡單:測試
public abstract class Statement { public abstract void evaluate() throws Throwable; }
若是測試失敗,Statement拋出異常。若是執行成功,no news is good news。Statement雖然簡潔,經過組合能夠構造出很是豐富的用法。好比下面這段代碼
protected Statement withBefores(FrameworkMethod method, Object target, Statement statement) { List<FrameworkMethod> befores = getTestClass().getAnnotatedMethods(Before.class); return befores.isEmpty() ? statement : new RunBefores(statement, befores, target); }
這段代碼摘自BlockJUnit4ClassRunner類。熟悉JUnit 4的人一看就會明白,這是處理@Before註解的地方。實際上,根據這段代碼,你們應該就能夠猜出RunBefores類大體是什麼樣子了。爲了例子的完整性,咱們把RunBefores類的代碼也展現在這裏。
package org.junit.internal.runners.statements; import java.util.List; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.Statement; public class RunBefores extends Statement { private final Statement next; private final Object target; private final List<FrameworkMethod> befores; public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) { this.next = next; this.befores = befores; this.target = target; } @Override public void evaluate() throws Throwable { for (FrameworkMethod before : befores) { before.invokeExplosively(target); } next.evaluate(); } }
如同上面的@Before例子,JUnit 4的不少特性都是經過Statement組合實現的。下面這段代碼也是BlockJUnit4ClassRunner的一部分,從這裏咱們能夠看出,@Rule也是經過封裝Statement實現的。
protected Statement methodBlock(FrameworkMethod method) { Object test; try { test = new ReflectiveCallable() { @Override protected Object runReflectiveCall() throws Throwable { return createTest(); } }.run(); } catch (Throwable e) { return new Fail(e); } Statement statement = methodInvoker(method, test); statement = possiblyExpectingExceptions(method, test, statement); statement = withPotentialTimeout(method, test, statement); statement = withBefores(method, test, statement); statement = withAfters(method, test, statement); statement = withRules(method, test, statement); return statement; }
爲了方便讀者參考,咱們把org.junit.rules.TestRule接口和RunRules類貼在這裏。
public interface TestRule { Statement apply(Statement base, Description description); }
package org.junit.rules; import org.junit.runner.Description; import org.junit.runners.model.Statement; public class RunRules extends Statement { private final Statement statement; public RunRules(Statement base, Iterable<TestRule> rules, Description description) { statement = applyAll(base, rules, description); } @Override public void evaluate() throws Throwable { statement.evaluate(); } private static Statement applyAll(Statement result, Iterable<TestRule> rules, Description description) { for (TestRule each : rules) { result = each.apply(result, description); } return result; } }
@BeforeClass註解也使用了Statement,下面的代碼來自ParentRunner。ParentRunner是BlockJUnit4ClassRunner的父類。
protected Statement classBlock(final RunNotifier notifier) { Statement statement = childrenInvoker(notifier); if (!areAllChildrenIgnored()) { statement = withBeforeClasses(statement); statement = withAfterClasses(statement); statement = withClassRules(statement); } return statement; }
Runner負責執行測試用例,測試的結果通知給RunNotifier。RunNotifier是一個RunListener集合,測試信息最終由RunListener處理。咱們運行單元測試時,控制檯輸出的信息就是由TextListener生成的。
public class RunListener { public void testRunStarted(Description description) throws Exception {} public void testRunFinished(Result result) throws Exception {} public void testStarted(Description description) throws Exception {} public void testFinished(Description description) throws Exception {} public void testFailure(Failure failure) throws Exception {} public void testAssumptionFailure(Failure failure) {} public void testIgnored(Description description) throws Exception {} }
在運行測試時,ParentRunner負責將測試事件通知給RunNotifier。
protected final void runLeaf(Statement statement, Description description, RunNotifier notifier) { EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description); eachNotifier.fireTestStarted(); try { statement.evaluate(); } catch (AssumptionViolatedException e) { eachNotifier.addFailedAssumption(e); } catch (Throwable e) { eachNotifier.addFailure(e); } finally { eachNotifier.fireTestFinished(); } }
在前面介紹Runner、Statement和RunNotifier時,已經不止一次提到了BlockJUnit4ClassRunner和ParentRunner。這是兩個很是重要的類,有了前面的基礎,在這裏咱們能夠作一些深刻分析。
ParentRunner是一個抽象類,它有兩個重要的函數:run和runLeaf,這兩個函數在前面已經介紹過了。ParentRunner還有兩個重要接口:
protected abstract void runChild(T child, RunNotifier notifier); protected abstract List<T> getChildren();
runChild負責執行一個測試方法。正如方法classBlock所暗示的,ParentRunner運行一個測試類。ParentRunner將一組測試方法看做本身的child。child經過getChildren得到。ParentRunner將各個child所表明的測試用例經過childrenInvoker封裝成一個Statement,在加上@BeforeClass和@AfterClass,構形成最終的Statement。
@Override public void run(final RunNotifier notifier) { EachTestNotifier testNotifier = new EachTestNotifier(notifier, getDescription()); try { Statement statement = classBlock(notifier); statement.evaluate(); } catch (AssumptionViolatedException e) { testNotifier.addFailedAssumption(e); } catch (StoppedByUserException e) { throw e; } catch (Throwable e) { testNotifier.addFailure(e); } } protected Statement classBlock(final RunNotifier notifier) { Statement statement = childrenInvoker(notifier); if (!areAllChildrenIgnored()) { statement = withBeforeClasses(statement); statement = withAfterClasses(statement); statement = withClassRules(statement); } return statement; } protected Statement childrenInvoker(final RunNotifier notifier) { return new Statement() { @Override public void evaluate() { runChildren(notifier); } }; } private void runChildren(final RunNotifier notifier) { final RunnerScheduler currentScheduler = scheduler; try { for (final T each : getFilteredChildren()) { currentScheduler.schedule(new Runnable() { public void run() { ParentRunner.this.runChild(each, notifier); } }); } } finally { currentScheduler.finished(); } }
BlockJUnit4ClassRunner派生自ParentRunner<FrameworkMethod>。一樣如方法methodBlock所暗示,BlockJUnit4ClassRunner更關注測試方法層面的工做:根據註解尋找測試方法,並將測試方法封裝成Statement。
@Override protected void runChild(final FrameworkMethod method, RunNotifier notifier) { Description description = describeChild(method); if (isIgnored(method)) { notifier.fireTestIgnored(description); } else { runLeaf(methodBlock(method), description, notifier); } } protected Statement methodBlock(FrameworkMethod method) { Object test; try { test = new ReflectiveCallable() { @Override protected Object runReflectiveCall() throws Throwable { return createTest(); } }.run(); } catch (Throwable e) { return new Fail(e); } Statement statement = methodInvoker(method, test); statement = possiblyExpectingExceptions(method, test, statement); statement = withPotentialTimeout(method, test, statement); statement = withBefores(method, test, statement); statement = withAfters(method, test, statement); statement = withRules(method, test, statement); return statement; }
這裏簡單說明一下methodBlock方法。FrameworkMethod表明一個Java方法,也就是@Test測試方法。methodBlock首先創建測試類實例,而後用methodInvoker將測試方法和類實例封裝成Statement,再加上@Before等註解,構造出完成的Statement。
如今介紹一下Android單元測試中遇到的AndroidJUnit4ClassRunner。AndroidJUnit4ClassRunner派生自BlockJUnit4ClassRunner,核心代碼是重寫了方法methodInvoker。
@Override protected Statement methodInvoker(FrameworkMethod method, Object test) { if (UiThreadStatement.shouldRunOnUiThread(method)) { return new UiThreadStatement(super.methodInvoker(method, test), true); } return super.methodInvoker(method, test); }
咱們知道methodInvoker將測試方法封裝爲Statement,AndroidJUnit4ClassRunner封裝的UIThreadStatement是作什麼用的呢?顧名思義,在UI線程中測試。
@Override public void evaluate() throws Throwable { if (runOnUiThread) { final AtomicReference<Throwable> exceptionRef = new AtomicReference<>(); runOnUiThread( new Runnable() { @Override public void run() { try { base.evaluate(); } catch (Throwable throwable) { exceptionRef.set(throwable); } } }); Throwable throwable = exceptionRef.get(); if (throwable != null) { throw throwable; } } else { base.evaluate(); } }
shouldRunOnUiThread的判斷標準也很簡單,檢查是否有UiThread註解。
public static boolean shouldRunOnUiThread(FrameworkMethod method) { Class<? extends Annotation> deprecatedUiThreadTestClass = loadUiThreadClass("android.test.UiThreadTest"); if (hasAnnotation(method, deprecatedUiThreadTestClass)) { return true; } else { // to avoid circular dependency on Rules module use the class name directly @SuppressWarnings("unchecked") // reflection Class<? extends Annotation> uiThreadTestClass = loadUiThreadClass("androidx.test.annotation.UiThreadTest"); if (hasAnnotation(method, deprecatedUiThreadTestClass) || hasAnnotation(method, uiThreadTestClass)) { return true; } } return false; }
對於Instrument測試,還須要在測試類中聲明ActivityTestRule。
@get:Rule val activityRule = ActivityTestRule(MainActivity::class.java)
ActivityTestRule將測試方法封裝爲AndroidStatement。AndroidStatement在運行前發送指令到設備,啓動應用。在測試後關閉應用。
@Override public void evaluate() throws Throwable { MonitoringInstrumentation instrumentation = ActivityTestRule.this.instrumentation instanceof MonitoringInstrumentation ? (MonitoringInstrumentation) ActivityTestRule.this.instrumentation : null; try { if (activityFactory != null && instrumentation != null) { instrumentation.interceptActivityUsing(activityFactory); } if (launchActivity) { launchActivity(getActivityIntent()); } base.evaluate(); } finally { if (instrumentation != null) { instrumentation.useDefaultInterceptingActivityFactory(); } T hardActivityRef = activity.get(); if (hardActivityRef != null) { finishActivity(); } activityResult = null; ActivityLifecycleMonitorRegistry.getInstance().removeLifecycleCallback(lifecycleCallback); } }
除了AndroidTestRule,androidx還提供了下列規則:
規則 | 說明 |
---|---|
GrantPermissionRule | 運行測試方法前申請權限。 |
ProviderTestRule | 測試ContentProvider。 |
ServiceTestRule | 測試服務。 |
PortForwardRule | 轉發端口。 |
分析這些規則,只要看apply方法返回了什麼Statement,以及這些Statement的evaluate作了什麼。