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相當(dāng)于是相對測試方法獨立的作用于測試方法中的額外處理邏輯蛛淋。多個Rule可以順序疊加。如果一個規(guī)則標注為@Rule
則對測試類的每個方法生效篡腌,如果一個規(guī)則標注為@ClassRule
則只會在整個測試類的所有方法開始之前和結(jié)束只會生效一次褐荷。以下常見的Rules如下:
Rules名稱 | 作用 | 備注 |
---|---|---|
ErrorCollector | 使用ErrorCollector.checkThat() 方法可以在執(zhí)行完整個測試方法之后再報錯,不會因為測試方法中的某一個錯誤而提前終止測試 |
JUnit自帶 文檔 |
ExpectedException | 使用ExpectedException.expect() 方法指定測試方法需要拋出的異常嘹悼,當(dāng)測試方法沒有拋出異撑迅Γ或者拋棄不符合預(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中為當(dāng)前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伴网。