JUnit4
JUnit是一個幫助編寫和執(zhí)行單元測試的框架。可能很多人都接觸過單元測試,但是只是停留在copy別人的測試代碼再改一下的狀態(tài),下文嘗試較為體系列舉JUnit4中比較關(guān)鍵的一些知識點。轉(zhuǎn)載請注明來源「Bug總柴」
Assertions斷言
判斷結(jié)果是否滿足預(yù)期,Junit有以下幾種斷言方法:assertArrayEquals、assertEquals、assertFalse、assertNotNull、assertNotSame、assertNull、assertSame、assertTrue、assertThat。
Hamcrest Mathers
Hamcrest擴展JUnitassertThat的Matcher類型,支持以下matchers:
| 支持類型 | 常用matchers |
|---|---|
| Core | anything、describedAs、is |
| Logical | allOf、anyOf、not、both、either |
| Object | equalTo、hasToString、instanceOf、isCompatibleType、notNullValue、nullValue、sameInstance、theInstance |
| Beans | hasProperty |
| Collections | array、hasEntry、hasKey、hasValue、hasItem、hasItems、hasItemInArray、everyItem |
| Number | closeTo、greaterThan、greaterThanOrEqualTo、lessThan、lessThanOrEqualTo |
| Text | equalToIgnoringCase、equalToIgnoringWhiteSpace、containsString、endsWith、startsWith |
使用assertThat具有更好的可讀性和出錯信息,建議大多數(shù)情況下使用assertTaht來進行斷言判斷。
Runner執(zhí)行器
Runner執(zhí)行器用于組織和執(zhí)行在一個類中的測試,可以在執(zhí)行器中做一些必須的前置和后置工作。使用@RunWith注解可以指定測試的執(zhí)行類。如果沒有使用@RunWith指定測試執(zhí)行器,默認會使用BlockJunit4ClassRunner。每個測試類只能指定一個Runner。除了默認的Runner還有以下的Runner:
| Runner名稱 | 作用 | 備注 |
|---|---|---|
| Suite | 將分布在多個類中的測試組合在一起作為一個測試執(zhí)行 | JUnit自帶 文檔 |
| Categories | 執(zhí)行多個類具有某些類別標志的一組測試 | JUnit自帶 文檔 |
| Parameterized | 使用同一種類型的多個數(shù)據(jù)重復(fù)執(zhí)行同一個測試類的所有測試 | JUnit自帶 文檔 |
| Theories | 使用多種類型的數(shù)據(jù)的排列組合執(zhí)行同一個測試類的所有測試 | JUnit自帶 文檔 |
| SpringJUnit4ClassRunner | 提供Spring上下文支持的測試執(zhí)行類 | 繼承自BlockJUnit4ClassRunner |
| MockitoJUnitRunner | 最終會構(gòu)造DefaultInternalRunner,根據(jù)mockito的注解在測試之前生成mock對象 |
詳見mockito介紹 |
| PowerMockRunner | 最終通過PowerMockJUnit44RunnerDelegateImpl.executeTest()方法,將被@PrepareForTest、@PrepareOnlyThisForTest、@SuppressStaticInitializationFor標注的類使用MockClassLoader進行加載,實現(xiàn)對靜態(tài)以及final對象的mock |
詳見powermock文檔 |
| AndroidJUnit4 | 會根據(jù)是否在Android設(shè)備執(zhí)行,選擇AndroidJUnit4ClassRunner或者RobolectricTestRunner(AndroidX版本)。在Android中執(zhí)行測試時,可以獲得運行的Instrumentation和Bundle參數(shù),以及可以使用@UiThreadTest標記測試方法在UI線程執(zhí)行 |
原理可以參閱這篇文章 |
| RobolectricTestRunner | 直接在安卓真機或者模擬器運行測試通常會比較慢,RobolectricTestRunner繼承自SandboxTestRunner,以提供在JVM中的Android運行時環(huán)境 | 詳見Robolectric官網(wǎng) |
| 其他 | 其他的Runner可以看這里 |
Rule規(guī)則
使用Rule可以對一個或者一組測試的方法進行修改,可以向測試方法中添加額外邏輯來決定測試是否通過,也可以代替@Before、@After、@BeforeClass、@AfterClass來實現(xiàn)初始化和清理工作。換句話而言,Rule相當于是相對測試方法獨立的作用于測試方法中的額外處理邏輯。多個Rule可以順序疊加。如果一個規(guī)則標注為@Rule則對測試類的每個方法生效,如果一個規(guī)則標注為@ClassRule則只會在整個測試類的所有方法開始之前和結(jié)束只會生效一次。以下常見的Rules如下:
| Rules名稱 | 作用 | 備注 |
|---|---|---|
| ErrorCollector | 使用ErrorCollector.checkThat()方法可以在執(zhí)行完整個測試方法之后再報錯,不會因為測試方法中的某一個錯誤而提前終止測試 |
JUnit自帶 文檔 |
| ExpectedException | 使用ExpectedException.expect()方法指定測試方法需要拋出的異常,當測試方法沒有拋出異?;蛘邟仐壊环项A(yù)期的異常時判定測試失敗 |
JUnit自帶 文檔 |
| ExternalResource | 類似于@Before和@After的效果,只是用了Rule來實現(xiàn),可以聲明發(fā)生在測試之前和測試之后的行為 |
JUnit自帶文檔 |
| TemporaryFolder | 在測試方法之前創(chuàng)建一個存放測試臨時文件的目錄,在測試結(jié)束后會自動刪除 | JUnit自帶 文檔 |
| TestWatcher | 可以用來監(jiān)測測試方法執(zhí)行的生命周期,包括開始、成功、錯誤、結(jié)束等 | JUnit自帶 文檔 |
| TestName | 繼承自TestWatcher,用來獲取每個測試方法的名字 |
JUnit自帶 文檔 |
| Timeout | 將測試類中的每個測試方法都是用獨立的線程執(zhí)行,并等待一段時間。若等待時間內(nèi)沒有結(jié)果返回則報錯。如果設(shè)置等待時間為0,則表示沒有超時只是在線程中執(zhí)行。 | JUnit自帶 文檔 |
| RuleChain | 將多個Rule按照指定的順序作用于測試方法中 | JUnit自帶 文檔 |
| Verifier | ErrorCollector的基類,抽象類,表示可以在運行完測試方法后做一些驗證操作 | JUnit自帶 文檔 |
| MockitoRule | 是一個擴展MethodRule的接口,通過JUnitRule實現(xiàn),會在執(zhí)行測試方法之前,初始化所有mock對象。這個rule的作用與MockitoJUnitRunner類似 |
文檔 |
| PowerMockRule | 最終通過PowerMockAgentTestInitializer.initialize()方法將被@PrepareForTest、@PrepareOnlyThisForTest、@SuppressStaticInitializationFor標注的類使用MockClassLoader進行加載,實現(xiàn)對靜態(tài)以及final對象的mock,作用與PowerMockRunner類似 |
文檔 |
| ProviderTestRule | 在測試方法之前對ContentProvider進行初始化,可以執(zhí)行相應(yīng)的數(shù)據(jù)庫操作。 | 文檔 |
| ServiceTestRule | 調(diào)用ServiceTestRule.startService()或者ServiceTestRule.bindService()在測試方法中建立Service連接,在測試結(jié)束后會自動關(guān)閉Service。不適用于IntentService,可以對其他Service進行測試。 |
文檔 |
| ActivityTestRule | 可以自動在測試方法和@Before之前啟動Activity,并在測試方法結(jié)束和@After之后結(jié)束Activity。也可以手動調(diào)用ActivityTestRule.launchActivity()和ActivityTestRule.finishActivity()
|
文檔 |
| GrantPermissionRule | 幫助在Android API 23及以上的環(huán)境申請運行時權(quán)限。申請權(quán)限時可以避免用戶交互彈窗占用UI測試焦點。最終會調(diào)用PermissionRequester.requestPermissions()方法,通過執(zhí)行UiAutomationShellCommand直接在shell中為當前target申請權(quán)限 |
文檔 |
| ActivityScenarioRule | 作為ActivityTestRule的替代,在測試方法之前啟動一個activity,并在測試方法之后結(jié)束activity。同時可以在測試方法中獲得ActivityScenario
|
ActivityScenarioRule文檔 / ActivityScenario文檔 |
| InstantTaskExecutorRule | 用于Architecture Components的測試,可以將默認使用的后臺executor轉(zhuǎn)為同步執(zhí)行,讓測試可以馬上獲得結(jié)果 | 文檔 |
| CountingTaskExecutorRule | 可以使用CountingTaskExecutorRule.drainTasks()方法手動等待所有Architecture Components的后臺任務(wù)執(zhí)行完畢 |
文檔 |
| IntentsTestRule | 在測試之前會初始化Espresso的Intent,可以使用Espresso Intents.intended()方法校驗activity操作觸發(fā)的intent |
espresso intent |
測試默認執(zhí)行流程源碼分析
整個測試的執(zhí)行過程是對Statement根據(jù)@BeforeClass、@AfterClass、@Before、@After、Rules的按照裝飾者模式進行的層層包裝。最后會根據(jù)這些包裝的規(guī)則一步一步執(zhí)行測試。
// BlockJUnit4ClassRunner會繼承ParentRunner
public abstract class ParentRunner<T> extends Runner implements Filterable, Sortable {
// 執(zhí)行測試
@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);
}
}
// 在執(zhí)行類中測試的前后加上BeforeClass和AfterClass邏輯
protected Statement classBlock(final RunNotifier notifier) {
// 執(zhí)行測試類中的測試方法
Statement statement = childrenInvoker(notifier);
if (!areAllChildrenIgnored()) {
// 這里會對類中的測試加上Before和After的邏輯
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() {
// 最終先執(zhí)行runChild
ParentRunner.this.runChild(each, notifier);
}
});
}
} finally {
currentScheduler.finished();
}
}
}
// 默認JUnit4 Runner BlockJUnit4ClassRunner對runChild進行處理
public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
if (isIgnored(method)) {
notifier.fireTestIgnored(description);
} else {
// 調(diào)用methodBlock加入Before/After以及Rules邏輯
runLeaf(methodBlock(method), description, notifier);
}
}
// 最終會調(diào)用After/Before以及Rule邏輯
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);
// 在測試的前后加上Before/After以及withRules
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;
}
}
// 對于Rule規(guī)則,最終會調(diào)用TestRule.apply()方法
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) {
// 順序加上Rule的邏輯
result = each.apply(result, description);
}
return result;
}
}
JUnit后記
如果有能代替Runner的Rule,最好使用Rule,因為一個測試類可以指定多個Rule,但是只能聲明一個Runner。