Junit源碼閱讀筆記一(從JunitCore開始)

1.寫在前面

基于junit 4.12版本,對junit源碼閱讀之后的理解和總結,如有不正確的地方,請多指正

2.junit的模塊

根據自己對源碼的理解,junit大體可以劃分為以下幾個模塊

模塊圖

1.Request負責發送測試指令
2.Runner負責運行測試用例
3.RunnerBuilder負責創建Runner
4.所有編寫的測試用例都被描述為一個TestClass,Runner運行時是通過解析TestClass來進行
5.Runner只是運行測試的一個入口,真正運行測試用例的實際上是Statement,Statement執行的時候會回調Runner子類中的一些具體方法
6.在Statement執行時,會過各種Rule
7.Statement執行完成后會產生一個執行結果Result
8.Notifier負責在測試用例運行期間的各種通知
接下對,結合源碼,對各個模塊進行分析

2.從JunitCore開始

junit是從JunitCore的main函數開始的

public static void main(String... args) {
    //創建JunitCore實例,并且運行runaMain方法
    //RealSystem只是封裝了打印輸出流,不影響主流程,暫時忽略
    Result result = new JUnitCore().runMain(new RealSystem(), args);

    //測試運行成功時正常退出,失敗時非正常退出
    System.exit(result.wasSuccessful() ? 0 : 1);
}

接下來看runMain方法

Result runMain(JUnitSystem system, String... args) {
    system.out().println("JUnit version " + Version.id());

    //解析參數參數并獲得解析結果
    JUnitCommandLineParseResult jUnitCommandLineParseResult = JUnitCommandLineParseResult.parse(args);
    //創建并添一個監聽器
    RunListener listener = new TextListener(system);
    addListener(listener);
    //創建請求,運行并返回運行結果
    return run(jUnitCommandLineParseResult.createRequest(defaultComputer()));
}

先忽略細枝末節,先來看下最主要的run方法做了什么事

public Result run(Request request) {
    //從Request中獲取Runner并調用run方法
    return run(request.getRunner());
}

繼續看run方法

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;
}

先忽略監聽,首先讓我們重點來關注是測試是怎么運行的。
進入runner.run方法可見,調用的是Runner的抽象run方法,可見真正的執行是由具體的實現來執行的
從以上流程中不難發現這個Runner是從Request中獲取的,那讓我們回過頭來看一下Request的蹊蹺

3.Request的創建

讓我們回到runMain方法中最后一行return run(jUnitCommandLineParseResult.createRequest(defaultComputer()));
由此可見Request是通過JUnitCommandLineParseResult來創建見的,我們先來看一下入參Computer,是通過defaultComputer()方法獲取的,這個方法很簡單,只是return new Computer(),看類的注釋Represents a strategy for computing runners and suites.,字面意思好像是說表示一種計算runnerssuites的策略,先不管了,繼續看createRequest方法

public Request createRequest(Computer computer) {
    //參數解析失敗的集合如果為空進入if塊
    //parserErrors是runMain方法中JUnitCommandLineParseResult. parse方法獲得
    if (parserErrors.isEmpty()) {
        //創建請求
        Request request = Request.classes(
                computer, classes.toArray(new Class<?>[classes.size()]));
        //對創建的默認的請求添加過濾功能
        return applyFilterSpecs(request);
    } else {
        //如果參數解析出錯,導出錯誤
        return errorReport(new InitializationError(parserErrors));
    }
}

進入Request.classes()方法繼續向下看

/**
 * Create a <code>Request</code> that, when processed, will run all the tests
 * in a set of classes.
 *
 * @param computer Helps construct Runners from classes
 * @param classes the classes containing the tests
 * @return a <code>Request</code> that will cause all tests in the classes to be run
 */
public static Request classes(Computer computer, Class<?>... classes) {
    try {
        //創建所有默認的可能的RunnerBuilder
        AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true);
        //獲取?Suite,Suite是Runner的具體實現
        Runner suite = computer.getSuite(builder, classes);
        //調用runner方法并返回
        return runner(suite);
    } catch (InitializationError e) {
        throw new RuntimeException(
                "Bug in saff's brain: Suite constructor, called as above, should always complete");
    }
}

看來這Request是從runner方法中創建的,繼續看runner方法

public static Request runner(final Runner runner) {
    return new Request() {
        @Override
        public Runner getRunner() {
            return runner;
        }
    };
}

Request的創建很簡單,直接new了一個匿名的Request并實現了getRunner()的抽象方法
至此,可以看到runMain方法中終于調用的Runner是哪來的了
就是從這個匿名的Request中實現的getRunner方法中獲取的,而這個Runner是由Request.classes方法中創建的,而這個Runner正是由Computer這個類獲取的Suite
以上是默認的Request方法的創建的RequestRunner的獲取,讓我們回到createRequest方法中繼續向下看applyFilterSpecs方法

private Request applyFilterSpecs(Request request) {
    try {
        for (String filterSpec : filterSpecs) {
            //根據參數解析的filterSpecs創建過濾器
            Filter filter = FilterFactories.createFilterFromFilterSpec(
                    request, filterSpec);
            request = request.filterWith(filter);
        }
        return request;
    } catch (FilterNotCreatedException e) {
        return errorReport(e);
    }
}

繼續看request.filterWith

public Request filterWith(Filter filter) {
    return new FilterRequest(this, filter);
}

直接new了一個FilterRequest,此處是采用裝飾者模式,對原有的Request進行加工,增加了過濾功能,不再贅述
因此可以看出,JunitCore中的run方法

public Result run(Request request) {
    return run(request.getRunner());
}

是從Request實例中(如果設置了過濾器,則是FilterRequest實例)調用getRunner()方法,獲取Suite實例,真正運行的是Suite中的run()方法
FilterRequest中的getRunner方法會過濾掉實現了Filterable并滿足過濾條件的Runner

下面讓我們來總結一下這個調用的時序

junitCore運行時.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,868評論 18 139
  • JUnit Intro Android基于JUnit Framework來書寫測試代碼。JUnit是基于Java語...
    chandarlee閱讀 2,303評論 0 50
  • 1.Runner 上一節講到了Junit的運行實際上是調用Runner中的run方法執行的,那么接下來總結一下Ru...
    春狗閱讀 1,285評論 0 4
  • 閱讀前提條件,了解JUnit4的基本用法。代碼版本: 3637550 從執行流程來分析 一般情況下使用IDE開發項...
    Turwe閱讀 4,073評論 1 7
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,754評論 18 399