JUnit Intro
Android基于JUnit Framework來書寫測試代碼。JUnit是基于Java語言的流行的、廣泛被使用的測試框架牵咙,當(dāng)前最新的版本是JUnit4野瘦。相對于之前的版本而言,JUnit4允許開發(fā)者使用更清晰预愤、簡潔、靈活的方式來構(gòu)建測試代碼咳胃。Android也提供相應(yīng)更新支持JUnit4植康,并建議開發(fā)者基于JUnit4來寫測試代碼。因此展懈,我們這里主要學(xué)習(xí)JUnit4的使用销睁。
JUnit的一個(gè)測試類就是一個(gè)普通的Java Class供璧。通過使用JUnit4提供的注解可以快速便捷的構(gòu)建Test Class。e.g.
public class CalculatorTest {
private Calculator calculator;
@BeforeClass
public static void setUpBeforeClass(){
}
@Before
public void setupBeforeTest(){
calculator = new Calculator();
}
@Test
public void testPlus() throws Exception {
assertEquals(calculator.plus(2, 3), 5);
}
@Test
public void testMinus() throws Exception {
assertEquals(calculator.minus(5, 3), 2);
}
@Test
public void testMultiply() throws Exception {
assertEquals(calculator.multiply(2, 3), 6);
}
@Test
public void testDivide(){
assertEquals(calculator.divide(10, 2), 5);
}
@After
public void cleanupAfterTest(){
calculator = null;
}
@AfterClass
public static void cleanupAfterClass(){
}
}
上面的代碼展示了一個(gè)簡單的Test Class冻记。包含了最常用的注解睡毒,注解名稱代表的含義一目了然,說明如下:
-
@Test:方法注解檩赢,要求方法簽名為
public void
吕嘀。聲明該方法為一個(gè)Test Case Method。一個(gè)Test Class可以包含若干個(gè)這樣的方法贞瞒,JUnit 會依次運(yùn)行這些方法偶房。該注解中又包含兩個(gè)可配置的值:- timeout:設(shè)置一個(gè)單位為毫秒的時(shí)間值,如果該測試方法運(yùn)行的時(shí)間超過該指定值军浆,則該測試方法失斪匮蟆;一般用來捕獲或者終止循環(huán)乒融;e.g.
@Test(timeout=100) public void infinity() { while(true); }
- *exception*:指定對應(yīng)的測試方法會拋出某個(gè)異常掰盘;如果該測試方法沒有拋出指定的異常,則測試不通過赞季;e.g.
@Test(expected = ArithmeticException.class) public void testDivideExpectException(){ calculator.divide(10, 0); }
-
@Before :方法注解愧捕,要求方法簽名為
public void
。每一個(gè)Test Class中允許包含多個(gè)這種方法申钩,對應(yīng)方法會在執(zhí)行該Test Class中的每個(gè)test method之前調(diào)用次绘。 - @After :同@Before對應(yīng),只是調(diào)用時(shí)機(jī)不同撒遣,該方法會在該Test Class中每個(gè)test method執(zhí)行完成之后調(diào)用邮偎。
-
@BeforeClass :方法注解,要求方法簽名為
public static void
义黎。該方法會在執(zhí)行該Test Class時(shí)被調(diào)用且只會被調(diào)用一次禾进;一般在該方法中初始化一些全局的、開銷比較大的操作廉涕,比如初始化數(shù)據(jù)庫連接等泻云。 - @AfterClass :同@BeforeClass注解對應(yīng),只是調(diào)用時(shí)機(jī)不同火的,該方法會在Test Class執(zhí)行完所有的Test method后調(diào)用且只會調(diào)用一次壶愤;在該方法中可以做一些清理的工作。
Base Concept
-
Runner類:JUnit將如何運(yùn)行一個(gè)Test Class抽象為一個(gè)
Runner
類 馏鹤。JUnit4提供了一個(gè)基礎(chǔ)的抽象Runner子類ParentRunner<T>
。
public abstract class ParentRunner<T> extends Runner implements Filterable, Sortable {
......
/** * Returns a list of objects that define the children of this Runner. */
protected abstract List<T> getChildren();
protected abstract void runChild(T child, RunNotifier notifier);
.....
}
該類是一個(gè)泛型類娇哆,可以將ParentRunner看成是一棵Test Tree的父親節(jié)點(diǎn)湃累,對應(yīng)的類型參數(shù)T 就是代表其下的子節(jié)點(diǎn)的類型勃救。針對一個(gè)具體的Test Class,ParentRunner層負(fù)責(zé)處理@BeforeClass治力、@AfterClass和@ClassRule注解的方法蒙秒,遍歷并執(zhí)行所有的child。JUnit允許自定義Runner宵统,通過@RunWith注解可以指定一個(gè)Test Class使用某個(gè)Runner晕讲。
-
Statement:抽象類,代表了一個(gè)Test Class執(zhí)行過程中的一個(gè)或多個(gè)動作马澈,通過
evaluate()
方法來執(zhí)行這些動作瓢省。類似于Java中的Runnable接口,evaluate()
就相當(dāng)于run()
方法痊班。
public abstract class Statement {
/** Run the action, throwing a Throwable if anything goes wrong. */
public abstract void evaluate() throws Throwable;
}
How JUnit Run勤婚?
-
Build Runner
我們可以很方便的通過IDE來運(yùn)行我們的Test Class,也可以自己通過命令行工具運(yùn)行涤伐。因?yàn)楸举|(zhì)上我們只是運(yùn)行了一個(gè)普通的Java程序而已馒胆,IDE只不過是幫我們寫好了命令、封裝好參數(shù)而已凝果。
java -cp .;C:\Users\Administrator.gradle\caches\modules-2\files-2.1\junit\junit\4.12\2973d150c0dc1fefe998f834810d68f278ea58ec\junit-4.12.jar;D:\AndroidCode\StudioCode\AndroidTestPractice\app\build\intermediates\classes\test\debug;D:\AndroidCode\StudioCode\AndroidTestPractice\app\build\intermediates\classes\debug;D:\AndroidCode\StudioCode\AndroidTestPractice\build\generated\mockable-android-23.jar;C:\Users\Administrator.gradle\caches\modules-2\files-2.1\org.hamcrest\hamcrest-core\1.3\42a25dc3219429f0e5d060061f71acb49bf010a0\hamcrest-core-1.3.jar; org.junit.runner.JUnitCore com.lcd.androidtestpractice.ExampleUnitTest com.lcd.androidtestpractice.CalculatorTest
上面的命令指定運(yùn)行ExampleUnitTest和CalculatorTest兩個(gè)Test Class祝迂。-cp
后面指定class path,多個(gè)path之間使用;
分號隔開器净。我們需要指定所有需要的classpath(包括JUnit的jar路徑型雳、JUnit所依賴的其他jar包的路徑、Test Classes路徑和其依賴的其他所有類路徑)掌动,需要指定運(yùn)行JUnit的入口類org.junit.runner.JUnitCore
以及指定我們需要運(yùn)行的Test Classes四啰。那么長的執(zhí)行語句,想想還是用IDE吧
不管是IDE還是CMD粗恢,最終的統(tǒng)一入口都是JUnitCore
柑晒。在其main方法中會解析參數(shù),這些參數(shù)其實(shí)就是我們需要運(yùn)行的所有Test Class的全域限定名稱眷射。JUnit將這些參數(shù)封裝成為一個(gè)Request對象執(zhí)行匙赞,其實(shí)就是從request中取出Runner調(diào)用run方法執(zhí)行,這些步驟比較簡單妖碉,在此略過分析涌庭。
```java
public class JUnitCore {
private final RunNotifier notifier = new RunNotifier();
...
public static void main(String... args) {
Result result = new JUnitCore().runMain(new RealSystem(), args);
System.exit(result.wasSuccessful() ? 0 : 1);
}
public Result run(Request request) {
return run(request.getRunner());
}
...
}
```
我們先來看看Runner的實(shí)現(xiàn)到底是個(gè)什么對象,如何構(gòu)造的欧宜?因?yàn)榫唧w的代碼比較簡單坐榆,這里我就不一一分析了,有興趣的同學(xué)可以自己閱讀源碼冗茸。 我直接給出答案:JUnit通過RunnerBuilder
類來針對具體的Test Class為其構(gòu)造具體的Runner實(shí)現(xiàn)席镀。且在JUnit4中提供AllDefaultPossibilitiesBuilder
類為默認(rèn)使用的builder匹中。
public class AllDefaultPossibilitiesBuilder extends RunnerBuilder {
private final boolean canUseSuiteMethod;
public AllDefaultPossibilitiesBuilder(boolean canUseSuiteMethod) {
this.canUseSuiteMethod = canUseSuiteMethod;
}
@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
List<RunnerBuilder> builders = Arrays.asList(
ignoredBuilder(),
annotatedBuilder(),
suiteMethodBuilder(),
junit3Builder(),
junit4Builder());
for (RunnerBuilder each : builders) {
Runner runner = each.safeRunnerForClass(testClass);
if (runner != null) {
return runner;
}
}
return null;
}
protected JUnit4Builder junit4Builder() {
return new JUnit4Builder();
}
protected JUnit3Builder junit3Builder() {
return new JUnit3Builder();
}
protected AnnotatedBuilder annotatedBuilder() {
return new AnnotatedBuilder(this);
}
protected IgnoredBuilder ignoredBuilder() {
return new IgnoredBuilder();
}
protected RunnerBuilder suiteMethodBuilder() {
if (canUseSuiteMethod) {
return new SuiteMethodBuilder();
}
return new NullBuilder();
}
}
代碼很清晰,通過Builder的runnerForClass(Class<?> testClass)
方法為一個(gè)具體的Test Class構(gòu)建對應(yīng)的Runner豪诲。該方法會依次遍歷AllDefaultPossibilitiesBuilder
中內(nèi)置的Builders顶捷,調(diào)用每個(gè)builder的safeRunnerForClass
方法請求為該Test Class生成Runner。如果返回為null屎篱,則說明這個(gè)builder無法為此Test Class構(gòu)建Runner服赎,繼續(xù)遍歷其他Builder。否則返回交播。
-
IgnoredBuilder:該Builder檢查Test Class是否使用@Ignore注解修飾重虑,如果是,返回
IgnoredClassRunner
堪侯,否則返回null嚎尤。IgnoredClassRunner
在執(zhí)行時(shí),其實(shí)什么都沒干伍宦,等于就是忽略執(zhí)行這個(gè)Test Class芽死。public class IgnoredBuilder extends RunnerBuilder { @Override public Runner runnerForClass(Class<?> testClass) { if (testClass.getAnnotation(Ignore.class) != null) { return new IgnoredClassRunner(testClass); } return null; }
}
```
- AnnotatedBuilder:該Builder檢查Test Class是否使用@RunWith注解修飾,如果有次洼,會通過反射構(gòu)建對應(yīng)的Runner對象返回关贵,否則返回null。這里不再給出具體的代碼卖毁。
- SuiteMethodBuilder和JUnit3Builder:這兩個(gè)Builder是兼容老版本的揖曾,用來構(gòu)建基于JUnit3的Runner,不再討論亥啦。
-
JUnit4Builder:構(gòu)建基于JUnit4的runner炭剪,該builder直接返回一個(gè)
BlockJUnit4ClassRunner
的對象。所以如果前面的Builder都沒有能夠?yàn)門est Class構(gòu)建Runner翔脱,則這個(gè)就是其默認(rèn)的Runner奴拦。
到現(xiàn)在為止,Runner對象已經(jīng)構(gòu)建并且返回届吁。直接調(diào)用它的run()
方法就相當(dāng)于執(zhí)行這個(gè)Test Class错妖。
public Result run(Runner runner) {
Result result = new Result();
RunListener listener = result.createListener();
notifier.addFirstListener(listener);
try {
notifier.fireTestRunStarted(runner.getDescription());
runner.run(notifier);
notifier.fireTestRunFinished(result);
} finally {
removeListener(listener);
}
return result;
}
ParentRunner.run() Flow
上面說到了Runner的run方法。在JUnit4中的Runner一般為ParentRunner的子類疚沐,所以相應(yīng)的這里從ParentRunner的
run(final RunNotifier notifier)
方法開始分析暂氯。首先通過classBlock(notifier)
方法返回一個(gè)statement
對象,包含了一系列要執(zhí)行的動作亮蛔,直接調(diào)用evaluate()
方法執(zhí)行痴施。
@Override
public void run(final RunNotifier notifier) {
...
Statement statement = classBlock(notifier);
statement.evaluate();
...
}
- 那這個(gè)statement對象是怎么構(gòu)造的呢?具體里面包含哪些執(zhí)行動作?
protected Statement classBlock(final RunNotifier notifier) {
Statement statement = childrenInvoker(notifier);
if (!areAllChildrenIgnored()) {
statement = withBeforeClasses(statement);
statement = withAfterClasses(statement);
statement = withClassRules(statement);
}
return statement;
}
- 首先是通過
childrenInvoker
方法封裝執(zhí)行所有child的動作到statement中晾剖,該動作等于是調(diào)用runChildren
方法锉矢。
protected Statement childrenInvoker(final RunNotifier notifier) {
return new Statement() {
@Override
public void evaluate() {
runChildren(notifier);
}
};
}
- 然后是通過
withBeforeClasses()
方法來封裝@BeforeClass注解修飾的方法梯嗽,如果Test Class中存在使用@BeforeClass注解修飾的方法齿尽,則new一個(gè)RunBefores
對象返回,否則直接返回原來的statement對象灯节。
protected Statement withBeforeClasses(Statement statement) {
List<FrameworkMethod> befores = testClass
.getAnnotatedMethods(BeforeClass.class);
return befores.isEmpty() ? statement :
new RunBefores(statement, befores, null);
}
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();
}
}
RunBefores
繼承于Statement循头,如果返回的是RunBefores
對象,當(dāng)執(zhí)行其evaluate()
方法時(shí)炎疆,會先執(zhí)行所有的befores指定的動作卡骂,即執(zhí)行所有的@BeforeClass修飾的方法,之后執(zhí)行next指代的動作形入,這里next指代的其實(shí)就是上一步的runChildren
動作全跨。
- 同理,之后是通過
withAfterClasses()
方法加入@AfterClass對應(yīng)的動作亿遂。如果Test Class中存在使用@AfterClass注解修飾的方法浓若,則new一個(gè)RunAfters
對象返回,否則直接返回原來的statement對象蛇数。
protected Statement withAfterClasses(Statement statement) {
List<FrameworkMethod> afters = testClass
.getAnnotatedMethods(AfterClass.class);
return afters.isEmpty() ? statement :
new RunAfters(statement, afters, null);
}
public class RunAfters extends Statement {
private final Statement next;
private final Object target;
private final List<FrameworkMethod> afters;
public RunAfters(Statement next, List<FrameworkMethod> afters, Object target) {
this.next = next;
this.afters = afters;
this.target = target;
}
@Override
public void evaluate() throws Throwable {
...
next.evaluate();
...
for (FrameworkMethod each : afters) {
each.invokeExplosively(target);
}
...
}
}
RunAfters
同樣繼承于Statement挪钓,如果返回的是RunAfters
對象,當(dāng)執(zhí)行其evaluate()
方法時(shí)耳舅,會先執(zhí)行next指代的動作碌上,即先執(zhí)行上一步的動作。然后才會執(zhí)行所有的afters指定的動作浦徊,即執(zhí)行所有的@AfterClass修飾的方法馏予。
- 下一步,通過
withClassRules
方法添加ClassRule中的動作盔性。
private Statement withClassRules(Statement statement) {
List<TestRule> classRules = classRules();
return classRules.isEmpty() ? statement :
new RunRules(statement, classRules, getDescription());
}
這里有必要先來了解一下什么是TestRule
霞丧。先看它的類定義:
public interface TestRule {
Statement apply(Statement base, Description description);
}
TestRule允許我們在Test Class運(yùn)行過程中插入自定義的一些操作。具體的可以在base statement執(zhí)行的前后加入一些其他操作邏輯纯出。下面的代碼自定義一個(gè)Rule蚯妇,該Rule先執(zhí)行doSomethingBefore()
方法,然后執(zhí)行statement暂筝,最后執(zhí)行doSomethingAfter()
方法箩言。e.g.
public class ClassRuleTest {
@ClassRule
public static TestRule myRule(){
return new TestRule() {
private void doSomethingBefore(){}
private void doSomethingAfter(){}
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
doSomethingBefore(); //do something before base actions
base.evaluate();
doSomethingAfter(); //do something after base actions
}
};
}
};
}
}
TestRule又分為類級別的Rule和實(shí)例級別的Rule。
- @ClassRule:用來聲明一個(gè)類級別的Rule焕襟,可修飾方法和字段陨收;當(dāng)修飾方法時(shí),要求方法簽名為public static
且方法的返回類型為TestRule
或者其子類型;同樣的务漩,修飾變量時(shí)拄衰,要求變量為public static
且類型為TestRule
或者其子類型;JUnit在搜索ClassRule時(shí)饵骨,會先查找符合條件的方法翘悉,并調(diào)用,將返回值添加到TestRule
列表中居触;然后搜索符合條件的Field字段妖混,同樣加入列表中。
- @Rule:用來聲明一個(gè)實(shí)例級別的Rule轮洋,可修飾方法和字段制市;方法要求為public
且返回類型為TestRule
或者其子類型;修飾變量時(shí)要求為public
的實(shí)例變量且類型為TestRule
或者其子類型弊予;JUnit同樣會在適當(dāng)?shù)臅r(shí)機(jī)搜索Test Class中的所有實(shí)例Rule并運(yùn)用祥楣。
現(xiàn)在回到上面的流程,如果Test Class中不存在類級別的Rule汉柒,則直接返回上一步的statement對象误褪;否則構(gòu)建一個(gè)`RunRules`對象返回。`RunRules`同樣繼承于`Statement`竭翠,在其構(gòu)造函數(shù)中會遍歷所有的ClassRule并調(diào)用apply方法返回一個(gè)新的statement振坚,這樣就給每個(gè)Rule提供了在base statement基礎(chǔ)上插入自定義操作的機(jī)會。上面的分析斋扰,其實(shí)主要涉及的是Test Class類層面的操作《砂耍現(xiàn)在我們先來梳理一下最終返回的class statement可能包含的操作和其執(zhí)行流程流程:
這里我們假設(shè)ClassRule、BeforeClass和AfterClass都存在传货,如果某一項(xiàng)不存在屎鳍,則只需忽略掉流程圖中對應(yīng)的部分即可。因?yàn)門estRule的特殊性问裕,某個(gè)TestRule可能在Statement前后都添加了自定義操作逮壁,所以流程圖中ClassRule將對應(yīng)兩個(gè)部分。當(dāng)然粮宛,某個(gè)TestRule可能只在base statement基礎(chǔ)操作前添加自定義操作窥淆,那么其對應(yīng)的后置操作部分相當(dāng)于什么都沒干;反之亦然巍杈!這里Statement對象的層層嵌套忧饭,其實(shí)是使用了設(shè)計(jì)模式中的裝飾器模式,感興趣的同學(xué)可以私下了解一下筷畦。
-
runChildren
下面我們來分析runChildren的流程词裤。runChildren會首先獲取Children列表刺洒,然后遍歷并在每個(gè)Child上調(diào)用runChild方法。runChild方法在ParentRunner中是一個(gè)抽象方法吼砂,由具體的subclass類來實(shí)現(xiàn)逆航。
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();
}
}
JUnit4提供了ParentRunner的兩個(gè)直接實(shí)現(xiàn)BlockJUnit4ClassRunner
和Suite
。
-
BlockJUnit4ClassRunner:JUnit4默認(rèn)Ruuner渔肩,運(yùn)行Test Class下的所有Test method因俐。該Runner包含的每個(gè)Child其實(shí)是一個(gè)
FrameworkMethod
對象,代表一個(gè)@Test方法赖瞒。
public class BlockJUnit4ClassRunner extends ParentRunner<FrameworkMethod> {
@Override
protected List<FrameworkMethod> getChildren() {
return computeTestMethods();
}
/**
* Returns the methods that run tests. Default implementation returns all
* methods annotated with {@Test} on this class and superclasses that
* are not overridden.
*/
protected List<FrameworkMethod> computeTestMethods() {
return getTestClass().getAnnotatedMethods(Test.class);
}
...
}
可以看到女揭,getChildren()方法返回的Children列表其實(shí)是FrameworkMethod
對象列表,即Test Class中所有使用@Test注解修飾的方法列表栏饮。在ParentRunner中調(diào)用runChildren時(shí),其實(shí)是在每個(gè)@Test方法上調(diào)用runChild方法磷仰。在該方法中袍嬉,首先檢查是否忽略,其實(shí)就是檢查是否有@Ignore注解灶平;如果忽略伺通,則該Test Method得不到執(zhí)行;否則逢享,通過methodBlock(method)
方法返回一個(gè)Statement對象罐监,并調(diào)用runLeaf方法運(yùn)行這個(gè)statement對象。
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 final void runLeaf(Statement statement, Description description,
RunNotifier notifier) {
...
statement.evaluate();
...
}
那這個(gè)statement對象里面又封裝了哪些操作呢瞒爬?來看methodBlock方法怎么構(gòu)造這個(gè)對象:
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;
}
首先弓柱,通過createTest()
方法直接構(gòu)造了一個(gè)Test Class的實(shí)例test。createTest()
方法使用的是反射的方式構(gòu)建實(shí)例對象侧但,使用的是Test Class默認(rèn)構(gòu)造函數(shù)矢空,如果Test Class類中聲明有其他的構(gòu)造函數(shù),請保證默認(rèn)的構(gòu)造函數(shù)存在禀横,否則會因?yàn)闊o法創(chuàng)建實(shí)例而拋出異常屁药。無法然后才開始處理Statement。這里我們看到了類似ParentRunner中的處理邏輯柏锄,一層層的statement的嵌套酿箭。首先methodInvoker
返回的基礎(chǔ)statement封裝了在創(chuàng)建的實(shí)例test上運(yùn)行@Test方法的操作,接著是封裝@Test
中聲明的expected異常和timeout的處理邏輯趾娃,再然后就是withBefores封裝所有的@Before方法操作缭嫡、withAfters封裝所有@After方法操作和withRules封裝所有的@Rule操作。這里跟前面已經(jīng)分析過的@BeforeClass茫舶、@AfterClass和@ClassRule操作的封裝原理是相通的械巡,就不一一貼出代碼說明了。
- Suite:該Runner允許將多個(gè)Test Class通過@SuiteClasses注解聲明為一個(gè)測試套件來運(yùn)行;當(dāng)執(zhí)行該Test Class時(shí)讥耗,會依次執(zhí)行注解中包含的所有Test Class有勾。一般我們可以通過如下的方式使用
@RunWith(Suite.class)
@SuiteClass({TestClass1.class, TestClass2.class...})
public class MySuite {}
通過@RunWith注解,在為該MySuite這個(gè)Test Class構(gòu)建具體的Runner時(shí)古程,返回的就是Suite對象蔼卡。
public class Suite extends ParentRunner<Runner> {
...
private final List<Runner> runners;
@Override
protected List<Runner> getChildren() {
return runners;
}
@Override
protected void runChild(Runner runner, final RunNotifier notifier) {
runner.run(notifier);
}
...
}
Suite中的每一個(gè)Child都是一個(gè)Runner,因?yàn)镾uite中包含了一系列的Test Classes挣磨,Suite對象在構(gòu)建的時(shí)候雇逞,就會為這些Test Class都構(gòu)建相應(yīng)Runner(Runner構(gòu)建的流程參照上面的說明);這些Runners作為Suite的Children保存到runners列表中茁裙。當(dāng)在ParentRunner中調(diào)用runChildren時(shí)塘砸,其實(shí)就是在這些Runner對象上依次調(diào)用runChild方法。而runChild方法實(shí)現(xiàn)很簡單晤锥,直接交給對應(yīng)的Runner來處理掉蔬。所以Suite并不關(guān)心如何去執(zhí)行它包含的每個(gè)Test Class,真正的執(zhí)行還是由Test Class自己決定的矾瘾。到這里女轿,我們r(jià)unChildren的執(zhí)行邏輯已經(jīng)分析完了。根據(jù)上面的分析壕翩,下面給出相應(yīng)的流程圖來說明最終每個(gè)child statement具體的執(zhí)行過程:
child statement的執(zhí)行流程對照class statement的流程蛉迹。這里也是假設(shè)Rule、Before和After都存在放妈,如果不存在北救,請忽略圖中對應(yīng)的部分。