Android Test Base--JUnit Framework

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勤婚?

  1. 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。這里不再給出具體的代碼卖毁。
  • SuiteMethodBuilderJUnit3Builder:這兩個(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;
    }
  1. ParentRunner.run() Flow

  2. 上面說到了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();
        ...
    }
  1. 那這個(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;
    }
  1. 首先是通過childrenInvoker方法封裝執(zhí)行所有child的動作到statement中晾剖,該動作等于是調(diào)用runChildren方法锉矢。
protected Statement childrenInvoker(final RunNotifier notifier) {
        return new Statement() {
            @Override
            public void evaluate() {
                runChildren(notifier);
            }
        };
    }
  1. 然后是通過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動作全跨。

  1. 同理,之后是通過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修飾的方法馏予。

  1. 下一步,通過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í)行流程流程:
class statement

這里我們假設(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é)可以私下了解一下筷畦。

  1. 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)BlockJUnit4ClassRunnerSuite

  • 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

child statement的執(zhí)行流程對照class statement的流程蛉迹。這里也是假設(shè)Rule、Before和After都存在放妈,如果不存在北救,請忽略圖中對應(yīng)的部分。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末大猛,一起剝皮案震驚了整個(gè)濱河市扭倾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挽绩,老刑警劉巖膛壹,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異唉堪,居然都是意外死亡模聋,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門唠亚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來链方,“玉大人,你說我怎么就攤上這事灶搜∷钍矗” “怎么了工窍?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長前酿。 經(jīng)常有香客問我患雏,道長,這世上最難降的妖魔是什么罢维? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任淹仑,我火速辦了婚禮,結(jié)果婚禮上肺孵,老公的妹妹穿的比我還像新娘匀借。我一直安慰自己,他們只是感情好平窘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布吓肋。 她就那樣靜靜地躺著,像睡著了一般初婆。 火紅的嫁衣襯著肌膚如雪蓬坡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天磅叛,我揣著相機(jī)與錄音,去河邊找鬼萨赁。 笑死弊琴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的杖爽。 我是一名探鬼主播敲董,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼慰安!你這毒婦竟也來了腋寨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤化焕,失蹤者是張志新(化名)和其女友劉穎萄窜,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體撒桨,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡查刻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凤类。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片穗泵。...
    茶點(diǎn)故事閱讀 40,144評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谜疤,靈堂內(nèi)的尸體忽然破棺而出佃延,到底是詐尸還是另有隱情现诀,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布履肃,位于F島的核電站仔沿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏榆浓。R本人自食惡果不足惜于未,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望陡鹃。 院中可真熱鬧烘浦,春花似錦、人聲如沸萍鲸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脊阴。三九已至握侧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嘿期,已是汗流浹背品擎。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留备徐,地道東北人萄传。 一個(gè)月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像蜜猾,于是被迫代替她去往敵國和親秀菱。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理蹭睡,服務(wù)發(fā)現(xiàn)衍菱,斷路器,智...
    卡卡羅2017閱讀 134,672評論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法肩豁,類相關(guān)的語法脊串,內(nèi)部類的語法,繼承相關(guān)的語法蓖救,異常的語法洪规,線程的語...
    子非魚_t_閱讀 31,644評論 18 399
  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗(yàn)。 張土汪:刷leetcod...
    土汪閱讀 12,747評論 0 33
  • 單元測試 單測定義 單元測試(Unit Testing)又稱為模塊測試, 是針對程序模塊(軟件設(shè)計(jì)的最小單位)來進(jìn)...
    運(yùn)維開發(fā)筆記閱讀 1,995評論 0 2
  • 望月樓臺伊人獨(dú)抱月 白玉盤灑下滿壺銀漿 應(yīng)嫦娥邀你一醉方休 借一縷星光且作酒盞 將舉杯欲泯去千萬愁 奈何斬不斷青絲...
    郁間非流離閱讀 118評論 2 6