簡單易懂, JUnit 框架問答

本文算是一個關於Junit4相關的知識分享,可是不一樣於網上大段的源碼分析,模式學習文章,我想經過問答的形式,引出代碼來簡明闡述JUnit4是如何實現須要的功能的。
考慮到任何一個框架,都是爲了解決問題而存在的。那麼我想,帶着問題去看源碼會不會事半功倍呢?
Note:本文基於Junit4.11源碼java

Junit4怎麼就能跑Case?

衆所周知,JUnit框架能幫助跑單元測試的Case,那麼它究竟是如何實現的呢?換句話說,若是沒有JUnit,咱們會怎麼執行Case?
OK,很簡單,一個case就是一個方法,那要想執行他們,直接在Main方法裏調用就能夠了。
可是當Case不少時,咱們就得一個一個在Main方法裏顯示的調用Case。
雖然稍顯繁瑣,仍是能夠解決問題的,不過拓展性就不敢恭維了,別人也沒辦法使用咱們已寫的東西?!框架

那麼,若是要上升到框架層面,可以讓更多的人使用,該怎麼作呢? Junit給了咱們很好的例子.ide

好的單元測試框架必定是最大限度的方便使用者,讓其儘量只關注單元測試自己,而不是其餘一些冗餘的事情.
很明顯Junit作到了這一點, 它不須要你去顯示的本身調用Case,一切都幫你作好,你只要告訴它要測哪一個類就行了,以JUnit4.11默認的Runner爲例:源碼分析

BlockJUnit4ClassRunner aRunner = new BlockJUnit4ClassRunner(JunitTest.class);
  aRunner.run(new RunNotifier());

Debug進去,咱們就會發現,原來JUnit是用反射機制來跑咱們寫的Case。單元測試

public class InvokeMethod extends Statement {
    private final FrameworkMethod fTestMethod;
    private Object fTarget;

    public InvokeMethod(FrameworkMethod testMethod, Object target) {
        fTestMethod = testMethod;
        fTarget = target;
    }

    @Override
    public void evaluate() throws Throwable {
        fTestMethod.invokeExplosively(fTarget);
    }
}

這裏的InvokeMethod就是實際執行方法的類,跟進去方法:fTestMethod.invokeExplosively(fTarget);
咱們就會看到Method.invoke(obj,args)的最終反射調用。學習

public Object invokeExplosively(final Object target, final Object... params)
        throws Throwable {
        return new ReflectiveCallable() {
            @Override
            protected Object runReflectiveCall() throws Throwable {
                return fMethod.invoke(target, params);
            }
        }.run();
    }

如何實現 @Test 標記功能?

Junit在4.0之後進行了一個大的版本變更,引入了Java 1.5的註解功能,就像咱們經常使用的 @Test, @BeforeClass, @AfterClass 同樣,那麼這些功能是如何實現的呢?
以 @Test 爲例:測試

@Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.METHOD})
 public @interface Test {
    static class None extends Throwable {
        private static final long serialVersionUID = 1L;

        private None() {
        }
    }
    Class<? extends Throwable> expected() default None.class;
    long timeout() default 0L;
}

它以RetentionPolicy.RUNTIME標記, 就表示這個註解在運行時仍然會被JVM保存,所以就能夠經過反射獲得它:ui

protected List<FrameworkMethod> computeTestMethods() {
    return getTestClass().getAnnotatedMethods(Test.class);
}

這樣JUnit就獲得了全部你要跑的Case。lua

而對於 @BeforeClass, @AfterClass 都是一個原理。code

Junit4如何判斷結果?

上面說到如何找Case,如何跑Case,那麼對於一個完成的執行流程,咱們還缺一塊:你的Case是Pass仍是Fail?
好比下面:

org.junit.Assert.assertTrue("check assert method", false);

咱們一般是用Assert去作斷言的,還有其餘的方法,好比assertEqulas,assertNotEqulas。顯而易見,上面的斷言結果必定是Fail的,那具體是如何實現的呢?
要解開謎題,咱們先看看Assert.assertTrue(String message, boolean condition)方法的具體實現:

static public void assertTrue(String message, boolean condition) {
    if (!condition) {
        fail(message);
    }
}

static public void fail(String message) {
    if (message == null) {
        throw new AssertionError();
    }
    throw new AssertionError(message);
}

當condition是false時,它會拋一個java.lang.AssertionError異常。這個類是從Java 1.4引入的,而且它跟全部的Java的異常同樣都是從Throwable繼承來的。
那Junit如何捕獲呢?
org.junit.runners.ParentRunner<T>類咱們找到了答案:

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

這個方法目的就是執行單個Case,它是用Try Catch來捕獲全部非預期的異常錯誤。
只要Catch到Throwable異常了,很明顯Case就掛了。反之,Case則是pass的。

這裏還能夠繼續深刻,好比咱們知道,@Test 有一個expected參數,可讓咱們填入指望捕獲的異常,那麼JUnit如何實現的呢?

總結

好了,咱們來看看咱們都說了啥:

  • Junit經過 @Test 註解,找到咱們想跑的Case
  • 經過反射,來執行咱們的Case
  • 最後經過Catch Throwable exception來判斷咱們的case 是Pass仍是Fail

很明顯JUnit還有不少其餘的功能,好比TestSuite,Rule,各類Runners,這裏不在一一羅列了。
我想,做爲表達一條Case執行的主線,講到這裏應該已經足夠了,不是嘛?

若是您看了本篇博客,以爲對您有所收穫,請點擊下面的 [推薦]
若是您想轉載本博客,請註明出處大卡的博客[http://www.cnblogs.com/jinsdu/] 若是您對本文有意見或者建議,歡迎留言

相關文章
相關標籤/搜索