成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Junit源碼閱讀(五)

vpants / 912人閱讀

摘要:的作用是包裝從生成的邏輯,提供兩種方案生成和。最后從生成也異常簡單,也就是實現(xiàn)其方法返回該。

前言

盡管在第二次博客中我們講述了Runner的運行機(jī)制,但是許多其他特性比如Filter是如何與運行流程結(jié)合卻并不清楚。這次我們來回顧整理一下Junit的執(zhí)行流程,給出各種特性生效的機(jī)理,并分析一些代碼中精妙的地方。

Junit的執(zhí)行流程

JUnitCore的RunMain方法,使用jUnitCommandLineParseResult解析參數(shù)并生成Request。

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

在jUnitCommandLineParseResult的createRequest方法中,調(diào)用Request的classes方法生成Request,并對生成的Request進(jìn)行過濾

public Request createRequest(Computer computer) {
        if (parserErrors.isEmpty()) {
            Request request = Request.classes(
                    computer, classes.toArray(new Class[classes.size()]));
            return applyFilterSpecs(request);
        } else {
            return errorReport(new InitializationError(parserErrors));
        }
    }

接下來我們就要進(jìn)入核心部分了,先提出以下幾個問題:

如何為單個類生成Request

Filter的實現(xiàn)機(jī)制

對于錯誤如何把它納入以Request為初始并最終使用Runner的run這一套機(jī)制中

我們先回答第一個問題,其他問題我們會在之后慢慢解答:

接下來先給出classes方法的代碼

public static Request classes(Computer computer, Class... classes) {
        try {
            AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true);
            Runner suite = computer.getSuite(builder, classes);
            return runner(suite);
        } catch (InitializationError e) {
            return runner(new ErrorReportingRunner(e, classes));
        }
    }

問題再次被拆分為三個:

生成一個Builder

從Builder導(dǎo)出一個Runner

從Runner生成一個Request

AllDefaultPossibilitiesBuilder的邏輯是先后使用ignoredBuilder、annotatedBuilder、suiteMethodBuilder、junit3Builder、junit4Builder來生成Runner直到有一個成功則返回,然后getSuite方法將返回的Runner變?yōu)镾uite。

Computer的作用是包裝從builder生成Runner的邏輯,提供兩種方案——生成SingleClassRunner和Suite。我們在這里提一下生成Suite的邏輯,就是使用該builder不停為多個測試類生成對應(yīng)的Runner并放置到Suite的Runner列表中,Suite其他的初始化過程依從其父類,此處就不詳述。

最后從Runner生成Request也異常簡單,也就是實現(xiàn)其getRunner方法返回該Runner?,F(xiàn)在我們把注意力投放到builder如何導(dǎo)出Runner,以JUnit4Builder為例,下面給出代碼:

 public class JUnit4Builder extends RunnerBuilder {
    @Override
    public Runner runnerForClass(Class testClass) throws Throwable {
        return new BlockJUnit4ClassRunner(testClass);
    }
}

可以看出它其實就是直接生成了一個BlockJUnit4ClassRunner,下面我們關(guān)注該Runner的構(gòu)造過程。

    protected ParentRunner(Class testClass) throws InitializationError {
        this.testClass = createTestClass(testClass);
        validate();
    }

    protected TestClass createTestClass(Class testClass) {
        return new TestClass(testClass);
    }

可以看出構(gòu)造SingleClassRunne的過程就是解析生成TestClass的過程,我們在第二篇博客里已經(jīng)詳細(xì)講解過了,此處就不再贅述了??赡茉S多讀者看到這兒會很困惑,之前一直強調(diào)的描述測試樣例的Description到底是在哪里生成的呢?其實是在Runner的run過程里生成的,如下:

@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);
        }
    }
    @Override
    public Description getDescription() {
        Description description = Description.createSuiteDescription(getName(),
                getRunnerAnnotations());
        for (T child : getFilteredChildren()) {
            description.addChild(describeChild(child));
        }
        return description;
    }

可以明顯地得知Description是依據(jù)Runner來建立的,它的結(jié)構(gòu)和Runner應(yīng)該保持一致,它是Runner的附屬品,用來供與Runner交互的Notifier獲得信息。

Junit的Failure機(jī)制

下面我們主要關(guān)注在運行之后Result的生成和Failure的處理。Result是Notifier通過fireTestRunFinished(result)生成的,我們來看一看它具體做了什么。

    private abstract class SafeNotifier {
        private final List currentListeners;

        SafeNotifier() {
            this(listeners);
        }

        SafeNotifier(List currentListeners) {
            this.currentListeners = currentListeners;
        }

        void run() {
            int capacity = currentListeners.size();
            List safeListeners = new ArrayList(capacity);
            List failures = new ArrayList(capacity);
            for (RunListener listener : currentListeners) {
                try {
                    notifyListener(listener);
                    safeListeners.add(listener);
                } catch (Exception e) {
                    failures.add(new Failure(Description.TEST_MECHANISM, e));
                }
            }
            fireTestFailures(safeListeners, failures);
        }

        abstract protected void notifyListener(RunListener each) throws Exception;
    }

    public void fireTestRunFinished(final Result result) {
        new SafeNotifier() {
            @Override
            protected void notifyListener(RunListener each) throws Exception {
                each.testRunFinished(result);
            }
        }.run();
    }
    public void fireTestAssumptionFailed(final Failure failure) {
        new SafeNotifier() {
            @Override
            protected void notifyListener(RunListener each) throws Exception {
                each.testAssumptionFailure(failure);
            }
        }.run();
    }
     private void fireTestFailures(List listeners,
            final List failures) {
        if (!failures.isEmpty()) {
            new SafeNotifier(listeners) {
                @Override
                protected void notifyListener(RunListener listener) throws Exception {
                    for (Failure each : failures) {
                        listener.testFailure(each);
                    }
                }
            }.run();
        }
    }

簡單概括一下,就是調(diào)用它所管理的Listener的testRunFinished來處理Result,處理完畢之后就把該Listener標(biāo)記為安全的,如果處理過程中出現(xiàn)異常,則將該異常加入failures列表,全部通知完畢后再次通知所有安全的Listener處理之前所有的Failure。這里還有一個問題,那就是Failure到底是怎樣在運行時生成的。由于斷言機(jī)制,所有的斷言失敗都會拋出對應(yīng)的異常,對于斷言異常,請看下文:

@Override
    protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
        Description description = describeChild(method);
        if (isIgnored(method)) {
            notifier.fireTestIgnored(description);
        } else {
            Statement statement;
            try {
                statement = methodBlock(method);
            }
            catch (Throwable ex) {
                statement = new Fail(ex);
            }
            runLeaf(statement, description, notifier);
        }
    }

Fail會直接拋出原有的異常,再次調(diào)用RunLeaf

    protected final void runLeaf(Statement statement, Description description,
            RunNotifier notifier) {
        EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
        eachNotifier.fireTestStarted();
        try {
            statement.evaluate();
        } catch (AssumptionViolatedException e) {
            eachNotifier.addFailedAssumption(e);
        } catch (Throwable e) {
            eachNotifier.addFailure(e);
        } finally {
            eachNotifier.fireTestFinished();
        }
    }

最后轉(zhuǎn)入addFailure處理,而addFailure會最終通知Notifier中的各個Listener處理
。那JunitCore到底使用了怎樣的Listener,它又執(zhí)行了怎樣的操作。

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

注意JunitCore的Run方法中加入了一個Result中內(nèi)置的Listener,其定義如下

@RunListener.ThreadSafe
    private class Listener extends RunListener {
        @Override
        public void testRunStarted(Description description) throws Exception {
            startTime.set(System.currentTimeMillis());
        }

        @Override
        public void testRunFinished(Result result) throws Exception {
            long endTime = System.currentTimeMillis();
            runTime.addAndGet(endTime - startTime.get());
        }

        @Override
        public void testFinished(Description description) throws Exception {
            count.getAndIncrement();
        }

        @Override
        public void testFailure(Failure failure) throws Exception {
            failures.add(failure);
        }

        @Override
        public void testIgnored(Description description) throws Exception {
            ignoreCount.getAndIncrement();
        }

        @Override
        public void testAssumptionFailure(Failure failure) {
            // do nothing: same as passing (for 4.5; may change in 4.6)
        }
    }

可以看出該Listener在testFailure中完成了添加Failure的動作,看到這里我簡直激動莫名,使用一個內(nèi)置的Listener子類來避免顯式為Result添加Failure,而是依然把這些操作集中在Notifier——Listener的觀察者體系里,可謂精妙絕倫!

同時還需注意在runMain方法中加入了一個TextListener來完成打印結(jié)果的工作。

Junit的初始化錯誤處理

上面我們講述了Junit如何處理樣例中的斷言錯誤以及運行時錯誤,但是當(dāng)初始化Request的過程中一旦發(fā)生異常依然需要繼續(xù)運行并提示測試失敗,這又是怎么實現(xiàn)的呢?

我們回顧之前createRequest方法中的errorReport,代碼如下:

    public static Request errorReport(Class klass, Throwable cause) {
        return runner(new ErrorReportingRunner(klass, cause));
    }

我們來看一下這個ErrorReportingRunner的實現(xiàn)

public class ErrorReportingRunner extends Runner {
    private final List causes;

    private final String classNames;

    public ErrorReportingRunner(Class testClass, Throwable cause) {
        this(cause, new Class[] { testClass });
    }
    
    public ErrorReportingRunner(Throwable cause, Class... testClasses) {
        if (testClasses == null || testClasses.length == 0) {
            throw new NullPointerException("Test classes cannot be null or empty");
        }
        for (Class testClass : testClasses) {
            if (testClass == null) {
                throw new NullPointerException("Test class cannot be null");
            }
        }
        classNames = getClassNames(testClasses);
        causes = getCauses(cause);
    }
    
    @Override
    public Description getDescription() {
        Description description = Description.createSuiteDescription(classNames);
        for (Throwable each : causes) {
            description.addChild(describeCause(each));
        }
        return description;
    }

    @Override
    public void run(RunNotifier notifier) {
        for (Throwable each : causes) {
            runCause(each, notifier);
        }
    }

    private String getClassNames(Class... testClasses) {
        final StringBuilder builder = new StringBuilder();
        for (Class testClass : testClasses) {
            if (builder.length() != 0) {
                builder.append(", ");
            }
            builder.append(testClass.getName());
        }
        return builder.toString();
    }

    @SuppressWarnings("deprecation")
    private List getCauses(Throwable cause) {
        if (cause instanceof InvocationTargetException) {
            return getCauses(cause.getCause());
        }
        if (cause instanceof InitializationError) {
            return ((InitializationError) cause).getCauses();
        }
        if (cause instanceof org.junit.internal.runners.InitializationError) {
            return ((org.junit.internal.runners.InitializationError) cause)
                    .getCauses();
        }
        return Arrays.asList(cause);
    }

    private Description describeCause(Throwable child) {
        return Description.createTestDescription(classNames, "initializationError");
    }

    private void runCause(Throwable child, RunNotifier notifier) {
        Description description = describeCause(child);
        notifier.fireTestStarted(description);
        notifier.fireTestFailure(new Failure(description, child));
        notifier.fireTestFinished(description);
    }
}

上面的大致邏輯依然是先生成Description(因為沒有到ParentRunner的run那一步,Description沒有生成,故需要在此生成),然后移交給Notifier處理

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/65449.html

相關(guān)文章

  • Junit源碼閱讀(一)

    摘要:是對測試樣例的建模,用來組合多個測試樣例,是中的核心內(nèi)容。也是一個虛類,子類應(yīng)該實現(xiàn)方法來決定對于是否運行。如下列代碼所示組合了和,為運行時異常和斷言錯誤屏蔽了不一致的方面,可以向上提供錯誤信息和樣例信息。 Junit的工程結(jié)構(gòu) showImg(/img/bVsEeS); 從上圖可以清楚的看出Junit大致分為幾個版塊,接下來一一簡略介紹這些版塊的作用。 runner:定義了Jun...

    Gilbertat 評論0 收藏0
  • Junit源碼閱讀(六)之Junit中的設(shè)計模式

    摘要:前言在這次的博客中我們將著重于的許多集成性功能來討論中的種種設(shè)計模式。裝飾器模式裝飾器模式是為了在原有功能上加入新功能,在中絕對屬于使用最頻繁架構(gòu)中最核心的模式,等都是通過裝飾器模式來完成擴(kuò)展的。 前言 在這次的博客中我們將著重于Junit的許多集成性功能來討論Junit中的種種設(shè)計模式??梢哉fJunit的實現(xiàn)本身就是GOF設(shè)計原則的范例教本,下面就讓我們開始吧。 裝飾器模式 裝飾器...

    jlanglang 評論0 收藏0
  • Junit源碼閱讀(三)之精致的Validator

    摘要:前言在建立的過程中,往往需要對當(dāng)前的測試樣例和注解進(jìn)行驗證,比如檢查測試類是否含有非靜態(tài)內(nèi)部類,測試類是否是的。的驗證機(jī)制非常精致而優(yōu)美,在本次博客中我們就主要來談一談機(jī)制的實現(xiàn)。首先在中定義三個默認(rèn)的類,如下。 前言 在建立Runner的過程中,往往需要對當(dāng)前的測試樣例和注解進(jìn)行驗證,比如檢查測試類是否含有非靜態(tài)內(nèi)部類,測試類是否是Public的。Junit的驗證機(jī)制非常精致而優(yōu)美...

    李世贊 評論0 收藏0
  • Junit源碼閱讀(四)之自定義擴(kuò)展

    摘要:前言上次的博客中我們著重介紹了的機(jī)制,這次我們將聚焦到自定義擴(kuò)展上來。在很多情形下我們需要在測試過程中加入一些自定義的動作,這些就需要對進(jìn)行包裝,為此提供了以接口和為基礎(chǔ)的擴(kuò)展機(jī)制。 前言 上次的博客中我們著重介紹了Junit的Validator機(jī)制,這次我們將聚焦到自定義擴(kuò)展Rule上來。在很多情形下我們需要在測試過程中加入一些自定義的動作,這些就需要對statement進(jìn)行包裝,...

    Little_XM 評論0 收藏0
  • Spring Boot快速入門():使用MyBatis(注解形式)進(jìn)行數(shù)據(jù)庫操作

    摘要:添加依賴新建項目選擇三個依賴對于已存在的項目可以在加入,將會幫你自動配置好配置基本信息然后在下添加基本配置數(shù)據(jù)庫連接地址數(shù)據(jù)庫賬號數(shù)據(jù)庫密碼數(shù)據(jù)庫驅(qū)動創(chuàng)建實體創(chuàng)建一個實體,包含姓名年齡屬性創(chuàng)建數(shù)據(jù)訪問接口創(chuàng)建一個 添加依賴 新建項目選擇web,MyBatis,MySQL三個依賴 showImg(https://segmentfault.com/img/bV2l1L?w=1684&h=1...

    lentoo 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<