上善若水
          In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
          posts - 146,comments - 147,trackbacks - 0

          初次用文字的方式記錄讀源碼的過程,不知道怎么寫,感覺有點貼代碼的嫌疑。不過中間還是加入了一些自己的理解和心得,希望以后能夠慢慢的改進,感興趣的童鞋湊合著看吧,感覺JUnit這個框架還是值得看的,里面有許多不錯的設計思想在,更何況它是Kent Beck和Erich Gamma這樣的大師寫的。。。。。

          深入JUnit源碼之Statement

          JUnit源碼最大的收獲就是看到這個Statement的設計,它也是我看到過的所有源碼中最喜歡的設計之一。JUnitRunner的運行過程就是Statement鏈的運行過程,Statement是對一個單元運行的封裝,每個Statement都只是執(zhí)行它本身所表達的邏輯,而將其他邏輯交給下一個Statement處理,而且基本上的Statement都存在對下一個節(jié)點的引用,從而由此構成一條Statement的鏈,從設計模式的角度上來看,這是一個職責連模式(Chain Of Responsibility Pattern)。JUnit中對@BeforeClass、@AfterClass、@Before@After@ClassRule、@Rule等邏輯就是通過Statement來實現(xiàn)的。首先來看一下Statement的類結構。


          Statement的類結構還是比較簡單的,首先Statement是所有類的父類,它只定義了一個抽象的evaluate()方法,由其他子類實現(xiàn)該方法;而且除了FailInvokeMethod類,其他類都有一個對Statement本身的引用。其實從實現(xiàn)上,每個Statement也是比較簡單的,這個接下來就可以看到了。每個Statement都只實現(xiàn)它自己的邏輯,而將其他邏輯代理給另一個Statement執(zhí)行,這樣可以在編寫每個Statement的時候只關注自己的邏輯,從而保持Statement本身的簡單,并且易于擴展,當一條Statement執(zhí)行完后,整個邏輯也就執(zhí)行完了。不過Statement這條鏈也不是憑空產(chǎn)生的,它也是要根據(jù)一定的邏輯構造起來的,關于Statement鏈的構造在JUnit中由Runner負責,為了保持本文的完整性,本文會首先會講解上述幾個Statement的源碼,同時簡單回顧Statement鏈的構造過程,最后將通過一個簡單的例子,將Statement的執(zhí)行過程用序列圖的方式表達出來,以更加清晰的表達Statement的執(zhí)行過程。不過本文不會詳細介紹Rules相關的代碼,這部分的代碼將會在下一節(jié)詳細介紹。

          RunBeforesRunAfters

          這兩個Statement是針對JUnit@BeforeClass、@Before的實現(xiàn)的,其中@BeforeClass是在測試類運行時,所有測試方法運行之前運行,并且對每個測試類只運行一次,這個注解修飾的方法必須是靜態(tài)的(在Runner一節(jié)中有談過它為什么要被設計成一定是要靜態(tài)方法,因為在運行每個測試方法是,測試類都會從新初始化一遍,如果不是靜態(tài)類,它只運行一次的話,它運行的結果無法保存下來);@Before是在每個測試方法運行之前都要運行。

          Statement的設計中,@BeforeClass注解的方法抽象成一個StatementRunBefores,而測試類中其他要運行的測試方法的運行過程是另一個Statementnext,在RunBefores中調(diào)用完所有這些方法,而將其他邏輯交給next; @Before注解的方法也是一樣的邏輯,它把接下來的測試方法看成是一個Statementnext,它調(diào)用完所有@Before注解的方法后,將接下來的事情交給next,因而他們共享RunBeforesStatement,唯一不同的是@BeforeClassRunBefores可以直接調(diào)用測試類中的方法,因為他們是靜態(tài)的,而@BeforeRunBefores需要傳入測試類的實例。

           1 public class RunBefores extends Statement {
           2     private final Statement fNext;
           3     private final Object fTarget;
           4     private final List<FrameworkMethod> fBefores;
           5     public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) {
           6        fNext= next;
           7        fBefores= befores;
           8        fTarget= target;
           9     }
          10     @Override
          11     public void evaluate() throws Throwable {
          12        for (FrameworkMethod before : fBefores)
          13            before.invokeExplosively(fTarget);
          14        fNext.evaluate();
          15     }
          16 }

          從源碼中可以看到,構造RunBefores時傳入下一個Statement、所有@BeforeClass@Before注解的方法以及測試類的實例,對@BeforeClass來說,傳入null即可。在運行evaluate()方法時,它依次調(diào)用@BeforeClass@Before注解的方法,后將接下來的邏輯交給next。從這段邏輯也可以看出如果有一個@BeforeClass@Before注解的方法拋異常,接下來的這些方法就都不會執(zhí)行了,包括測試方法。不過此時@AfterClass@After注解的方法還會執(zhí)行,這個在下一小節(jié)中即可知道。

          關于RunBefores的構造要,其實最重要的是要關注它的next Statement是什么,對于@BeforeClass對應的RunBefores來說,它的next Statement那些所有的測試方法運行而組成的Statement,而對于@Before對應的RunBefores來說,它的next Statement是測試方法的Statement

           1 protected Statement classBlock(final RunNotifier notifier) {
           2     Statement statement= childrenInvoker(notifier);
           3     statement= withBeforeClasses(statement);
           4     
           5 }
           6 protected Statement withBeforeClasses(Statement statement) {
           7     List<FrameworkMethod> befores= fTestClass
           8            .getAnnotatedMethods(BeforeClass.class);
           9     return befores.isEmpty() ? statement :
          10        new RunBefores(statement, befores, null);
          11 }
          12 protected Statement methodBlock(FrameworkMethod method) {
          13     
          14     Statement statement= methodInvoker(method, test);
          15     
          16     statement= withBefores(method, test, statement);
          17     
          18 }
          19 protected Statement withBefores(FrameworkMethod method, Object target,
          20        Statement statement) {
          21     List<FrameworkMethod> befores= getTestClass().getAnnotatedMethods(
          22            Before.class);
          23     return befores.isEmpty() ? statement : new RunBefores(statement,
          24            befores, target);
          25 }

          @AfterClass@After

          這兩個Statement是針對JUnit@AfterClass、@After的實現(xiàn)的,其中@AfterClass是在測試類運行時,所有測試方法結束之后運行,不管之前的方法是否有拋異常,并且對每個測試類只運行一次,這個注解修飾的方法必須是靜態(tài)的;@After是在每個測試方法運行結束后都要運行的,不管測試方法是否測試失敗。

          Statement的設計中,@AfterClass注解的方法抽象成一個StatementAfters,而測試類中之前要運行的過程是另一個Statementnext(其實這個叫before更好一些),在RunAfters中等所有之前的運行過程調(diào)用完后,再調(diào)用@AfterClass注解的方法; @After注解的方法也是一樣的邏輯,它把之前的測試方法包括@Before注解的方法看成是一個Statementnextbefore?),它等測試方法或@Before注解的方法調(diào)用完后,調(diào)用@After注解的方法,因而他們共享RunAftersStatement,唯一不同的是@AfterClassRunAfters可以直接調(diào)用測試類中的方法,因為他們是靜態(tài)的,而@AfterRunAfters需要傳入測試類的實例。

           1 public class RunAfters extends Statement {
           2     private final Statement fNext;
           3     private final Object fTarget;
           4     private final List<FrameworkMethod> fAfters;
           5     public RunAfters(Statement next, List<FrameworkMethod> afters, Object target) {
           6        fNext= next;
           7        fAfters= afters;
           8        fTarget= target;
           9     }
          10     @Override
          11     public void evaluate() throws Throwable {
          12        List<Throwable> errors = new ArrayList<Throwable>();
          13        try {
          14            fNext.evaluate();
          15        } catch (Throwable e) {
          16            errors.add(e);
          17        } finally {
          18            for (FrameworkMethod each : fAfters)
          19               try {
          20                   each.invokeExplosively(fTarget);
          21               } catch (Throwable e) {
          22                   errors.add(e);
          23               }
          24        }
          25        MultipleFailureException.assertEmpty(errors);
          26     }
          27 }

          從源碼中可以看到,構造RunAfters時傳入下一個Statement、所有@AfterClass@After注解的方法以及測試類的實例,對@AfterClass來說,傳入null即可。在運行evaluate()方法時,它會等之前的Statement執(zhí)行結束后,再依次調(diào)用@AfterClass@After注解的方法。從這段邏輯也可以看出無論之前Statement執(zhí)行是否拋異常,@AfterClass@After注解的方法都是會被執(zhí)行的,為了避免在執(zhí)行@AfterClass@After注解的方法拋出的異常覆蓋之前在運行@BeforeClass、@Before@Test注解方法拋出的異常,這里所有的異常都會觸發(fā)一次testFailure的事件,這個實現(xiàn)可以查看Runner小節(jié)的EachTestNotifier類的實現(xiàn)。

          對于RunAfters的構造,可能要注意的一點是傳入RunAftersStatementRunBefores的實例,這個其實還是好理解的,因為RunAfters是在傳入的Statement運行結束后運行,而RunBefores又是要在測試方法之前運行的,因而需要將RunAfters放在Statement鏈的最頭端,而后是RunAfters,最后才是測試方法調(diào)用的StatementInvokeMethod)。

           1 protected Statement classBlock(final RunNotifier notifier) {
           2     
           3     statement= withBeforeClasses(statement);
           4     statement= withBeforeClasses(statement);
           5     
           6 }
           7 protected Statement withAfterClasses(Statement statement) {
           8     List<FrameworkMethod> afters= fTestClass
           9            .getAnnotatedMethods(AfterClass.class);
          10     return afters.isEmpty() ? statement :
          11        new RunAfters(statement, afters, null);
          12 }
          13 protected Statement methodBlock(FrameworkMethod method) {
          14     
          15     statement= withBefores(method, test, statement);
          16     statement= withAfters(method, test, statement);
          17     
          18 }
          19 protected Statement withAfters(FrameworkMethod method, Object target,
          20        Statement statement) {
          21     List<FrameworkMethod> afters= getTestClass().getAnnotatedMethods(
          22            After.class);
          23     return afters.isEmpty() ? statement : new RunAfters(statement, afters,
          24            target);
          25 
           

          InvokeMethod、ExpectedExceptionFailOnTimeout

          之所有要把這三個Statement放在一起是因為他們都是和@Test注解相關的:

          1 @Retention(RetentionPolicy.RUNTIME)
          2 @Target({ElementType.METHOD})
          3 public @interface Test {
          4     Class<? extends Throwable> expected() default None.class;
          5     long timeout() default 0L;
          6 }

          @Test注解定義了兩個成員:expected指定當前測試方法如果拋出指定的異常則表明測試成功;而timeout指定當前測試方法如果超出指定的時間(以毫秒為單位),則測試失敗。在Statement設計中,這些邏輯都抽象成了一個Statement。而@Test注解的方法則被認為是真正要運行的測試方法,它的執(zhí)行過程也被抽象成了一個Statement。

          @Test注解的方法抽象出的Statement命名為InvokeMethod,它是一個非常簡單的Statement

           1 public class InvokeMethod extends Statement {
           2     private final FrameworkMethod fTestMethod;
           3     private Object fTarget;
           4     public InvokeMethod(FrameworkMethod testMethod, Object target) {
           5        fTestMethod= testMethod;
           6        fTarget= target;
           7     }
           8     @Override
           9     public void evaluate() throws Throwable {
          10        fTestMethod.invokeExplosively(fTarget);
          11     }
          12 }

          使用一個方法實例和測試類的實例構造InvokeMethod,在運行時直接調(diào)用該方法。并且InvokeMethod并沒有對其他Statement的引用,因而它是Statement鏈上的最后一個節(jié)點。

           1 protected Statement methodBlock(FrameworkMethod method) {
           2     
           3     Statement statement= methodInvoker(method, test);
           4     statement= possiblyExpectingExceptions(method, test, statement);
           5     statement= withPotentialTimeout(method, test, statement);
           6     
           7 }
           8 protected Statement methodInvoker(FrameworkMethod method, Object test) {
           9     return new InvokeMethod(method, test);
          10 }

          ExpectException用于處理當在@Test注解中定義了expected字段時,該注解所在的方法是否在運行過程中真的拋出了指定的異常,如果沒有,則表明測試失敗,因而它需要該測試方法對應的StatementInvokeMethod)的引用:

           1 public class ExpectException extends Statement {
           2     private Statement fNext;
           3     private final Class<? extends Throwable> fExpected;
           4     public ExpectException(Statement next, Class<? extends Throwable> expected) {
           5        fNext= next;
           6        fExpected= expected;
           7     }
           8     @Override
           9     public void evaluate() throws Exception {
          10        boolean complete = false;
          11        try {
          12            fNext.evaluate();
          13            complete = true;
          14        } catch (AssumptionViolatedException e) {
          15            throw e;
          16        } catch (Throwable e) {
          17            if (!fExpected.isAssignableFrom(e.getClass())) {
          18               String message= "Unexpected exception, expected<"
          19                          + fExpected.getName() + "> but was<"
          20                          + e.getClass().getName() + ">";
          21               throw new Exception(message, e);
          22            }
          23        }
          24        if (complete)
          25            throw new AssertionError("Expected exception: "
          26                   + fExpected.getName());
          27     }
          28 }

          使用InvokeMethod實例和一個expectedThrowable Class實例作為參數(shù)構造ExpectException,當InvokeMethod實例執(zhí)行后,判斷其拋出的異常是否為指定的異?;蛘咴摐y試方法沒有拋出異常,在這兩種情況下,測試都會失敗,因而需要它拋出異常以處理這種情況。

           1 protected Statement methodBlock(FrameworkMethod method) {
           2     
           3     Statement statement= methodInvoker(method, test);
           4     statement= possiblyExpectingExceptions(method, test, statement);
           5     
           6 }
           7 protected Statement possiblyExpectingExceptions(FrameworkMethod method,
           8        Object test, Statement next) {
           9     Test annotation= method.getAnnotation(Test.class);
          10     return expectsException(annotation) ? new ExpectException(next,
          11            getExpectedException(annotation)) : next;
          12 }

          FailOnTimeout是在@Test注解中指定了timeout值時,用于控制@Test注解所在方法的執(zhí)行時間是否超出了timeout的值,若是,則拋出異常,表明測試失敗。在JUnit4當前的實現(xiàn)中,它引用的Statement實例是ExpectException(如果expected字段定義了的話)或InvokeMethod。它通過將引用的Statement實例的執(zhí)行放到另一個線程中,然后通過控制線程的執(zhí)行時間以控制內(nèi)部引用的Statement實例的執(zhí)行時間,如果測試方法因內(nèi)部拋出異常而沒有完成,則拋出內(nèi)部拋出的異常實例,否則拋出執(zhí)行時間超時相關的異常。

           1 public class FailOnTimeout extends Statement {
           2     private final Statement fOriginalStatement;
           3     private final long fTimeout;
           4     public FailOnTimeout(Statement originalStatement, long timeout) {
           5        fOriginalStatement= originalStatement;
           6        fTimeout= timeout;
           7     }
           8     @Override
           9     public void evaluate() throws Throwable {
          10        StatementThread thread= evaluateStatement();
          11        if (!thread.fFinished)
          12            throwExceptionForUnfinishedThread(thread);
          13     }
          14     private StatementThread evaluateStatement() throws InterruptedException {
          15        StatementThread thread= new StatementThread(fOriginalStatement);
          16        thread.start();
          17        thread.join(fTimeout);
          18        thread.interrupt();
          19        return thread;
          20     }
          21     private void throwExceptionForUnfinishedThread(StatementThread thread)
          22            throws Throwable {
          23        if (thread.fExceptionThrownByOriginalStatement != null)
          24            throw thread.fExceptionThrownByOriginalStatement;
          25        else
          26            throwTimeoutException(thread);
          27     }
          28     private void throwTimeoutException(StatementThread thread) throws Exception {
          29        Exception exception= new Exception(String.format(
          30               "test timed out after %d milliseconds", fTimeout));
          31        exception.setStackTrace(thread.getStackTrace());
          32        throw exception;
          33     }
          34     private static class StatementThread extends Thread {
          35        private final Statement fStatement;
          36        private boolean fFinished= false;
          37        private Throwable fExceptionThrownByOriginalStatement= null;
          38        public StatementThread(Statement statement) {
          39            fStatement= statement;
          40        }
          41        @Override
          42        public void run() {
          43            try {
          44               fStatement.evaluate();
          45               fFinished= true;
          46            } catch (InterruptedException e) {
          47               //don't log the InterruptedException
          48            } catch (Throwable e) {
          49               fExceptionThrownByOriginalStatement= e;
          50            }
          51        }
          52     }
          53 }

          FailOnTimeout的構造過程如同上述的其他Statement類似:

           1 protected Statement methodBlock(FrameworkMethod method) {
           2     
           3     Statement statement= methodInvoker(method, test);
           4     statement= possiblyExpectingExceptions(method, test, statement);
           5     statement= withPotentialTimeout(method, test, statement);
           6     
           7 }
           8 protected Statement withPotentialTimeout(FrameworkMethod method,
           9        Object test, Statement next) {
          10     long timeout= getTimeout(method.getAnnotation(Test.class));
          11     return timeout > 0 ? new FailOnTimeout(next, timeout) : next;
          12 }

          FailRunRules

          Fail這個Statement是在創(chuàng)建測試類出錯時構造的Statement,這個設計思想有點類似Null Object模式,即保持編程模型的統(tǒng)一,即使在出錯的時候也用一個Statement去封裝,這也正是RunnerErrorReportingRunner的設計思想。

          Fail這個Statement很簡單,它在運行時重新拋出之前記錄的異常,其構造過程也是在創(chuàng)建測試類實例出錯時構造:

           1 protected Statement methodBlock(FrameworkMethod method) {
           2     Object test;
           3     try {
           4        test= new ReflectiveCallable() {
           5            @Override
           6            protected Object runReflectiveCall() throws Throwable {
           7               return createTest();
           8            }
           9        }.run();
          10     } catch (Throwable e) {
          11        return new Fail(e);
          12     }
          13     
          14 }
          15 public class Fail extends Statement {
          16     private final Throwable fError;
          17     public Fail(Throwable e) {
          18        fError= e;
          19     }
          20     @Override
          21     public void evaluate() throws Throwable {
          22        throw fError;
          23     }
          24 }

          RunRules這個Statement是對@ClassRule@Rule運行的封裝,它會將定義的所有Rule應用到傳入的Statement引用后返回,由于它內(nèi)部還有一些比較復雜的邏輯,關于Rule將有一個單獨的小節(jié)講解。這里主要關注RunRules這個Statement的實現(xiàn)和構造:

           1 public class RunRules extends Statement {
           2     private final Statement statement;
           3     public RunRules(Statement base, Iterable<TestRule> rules, Description description) {
           4        statement= applyAll(base, rules, description);
           5     }
           6     @Override
           7     public void evaluate() throws Throwable {
           8        statement.evaluate();
           9     }
          10     private static Statement applyAll(Statement result, Iterable<TestRule> rules,
          11            Description description) {
          12        for (TestRule each : rules)
          13            result= each.apply(result, description);
          14        return result;
          15     }
          16 }
          17 protected Statement classBlock(final RunNotifier notifier) {
          18     
          19     statement= withAfterClasses(statement);
          20     statement= withClassRules(statement);
          21     return statement;
          22 }
          23 private Statement withClassRules(Statement statement) {
          24     List<TestRule> classRules= classRules();
          25     return classRules.isEmpty() ? statement :
          26         new RunRules(statement, classRules, getDescription());
          27 }
          28 protected Statement methodBlock(FrameworkMethod method) {
          29     
          30     statement= withRules(method, test, statement);
          31     return statement;
          32 }
          33 private Statement withRules(FrameworkMethod method, Object target,
          34        Statement statement) {
          35     List<TestRule> testRules= getTestClass().getAnnotatedFieldValues(
          36         target, Rule.class, TestRule.class);
          37     return testRules.isEmpty() ? statement :
          38        new RunRules(statement, testRules, describeChild(method));
          39 }

          Rule分為@ClassRule@Rule@ClassRule是測試類級別的,它對一個測試類只運行一次,而@Rule是測試方法級別的,它在每個測試方法運行時都會運行。RunRules的構造過程中,我們可以發(fā)現(xiàn)RunRules才是最外層的Statement,TestRule中要執(zhí)行的邏輯要么比@BeforeClass、@Before注解的方法要早,要么比@AfterClass、@After注解的方法要遲。

          Statement執(zhí)行序列圖

          假設在一個測試類中存在多個@BeforeClass、@AfterClass@Before、@After注解的方法,并且有兩個測試方法testMethod1()testMethod2(),那么他們的運行序列圖如下所示(這里沒有考慮Rule,對@ClassRuleRunRules,它的鏈在最前端,而@RuleRunRules則是在RunAfters的前面,關于Rule將會在下一節(jié)中詳細講解):

           

           

           

          posted on 2012-05-11 23:53 DLevin 閱讀(3228) 評論(0)  編輯  收藏 所屬分類: JUnit
          主站蜘蛛池模板: 呼和浩特市| 望城县| 石棉县| 洪洞县| 富源县| 枞阳县| 离岛区| 阿图什市| 渝中区| 昌乐县| 广汉市| 托克逊县| 黔南| 龙江县| 西峡县| 雅安市| 平果县| 寿光市| 衡水市| 哈巴河县| 永仁县| 清丰县| 尚志市| 华阴市| 隆安县| 磐石市| 洪雅县| 舒兰市| 昂仁县| 延吉市| 烟台市| 屏东县| 南陵县| 读书| 洪洞县| 浙江省| 武汉市| 三台县| 莎车县| 娄烦县| 阳曲县|