上善若水
          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都只是執行它本身所表達的邏輯,而將其他邏輯交給下一個Statement處理,而且基本上的Statement都存在對下一個節點的引用,從而由此構成一條Statement的鏈,從設計模式的角度上來看,這是一個職責連模式(Chain Of Responsibility Pattern)。JUnit中對@BeforeClass@AfterClass@Before@After@ClassRule@Rule等邏輯就是通過Statement來實現的。首先來看一下Statement的類結構。


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

          RunBeforesRunAfters

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

          Statement的設計中,@BeforeClass注解的方法抽象成一個StatementRunBefores,而測試類中其他要運行的測試方法的運行過程是另一個Statementnext,在RunBefores中調用完所有這些方法,而將其他邏輯交給next @Before注解的方法也是一樣的邏輯,它把接下來的測試方法看成是一個Statementnext,它調用完所有@Before注解的方法后,將接下來的事情交給next,因而他們共享RunBeforesStatement,唯一不同的是@BeforeClassRunBefores可以直接調用測試類中的方法,因為他們是靜態的,而@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()方法時,它依次調用@BeforeClass@Before注解的方法,后將接下來的邏輯交給next。從這段邏輯也可以看出如果有一個@BeforeClass@Before注解的方法拋異常,接下來的這些方法就都不會執行了,包括測試方法。不過此時@AfterClass@After注解的方法還會執行,這個在下一小節中即可知道。

          關于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的實現的,其中@AfterClass是在測試類運行時,所有測試方法結束之后運行,不管之前的方法是否有拋異常,并且對每個測試類只運行一次,這個注解修飾的方法必須是靜態的;@After是在每個測試方法運行結束后都要運行的,不管測試方法是否測試失敗。

          Statement的設計中,@AfterClass注解的方法抽象成一個StatementAfters,而測試類中之前要運行的過程是另一個Statementnext(其實這個叫before更好一些),在RunAfters中等所有之前的運行過程調用完后,再調用@AfterClass注解的方法; @After注解的方法也是一樣的邏輯,它把之前的測試方法包括@Before注解的方法看成是一個Statementnextbefore?),它等測試方法或@Before注解的方法調用完后,調用@After注解的方法,因而他們共享RunAftersStatement,唯一不同的是@AfterClassRunAfters可以直接調用測試類中的方法,因為他們是靜態的,而@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執行結束后,再依次調用@AfterClass@After注解的方法。從這段邏輯也可以看出無論之前Statement執行是否拋異常,@AfterClass@After注解的方法都是會被執行的,為了避免在執行@AfterClass@After注解的方法拋出的異常覆蓋之前在運行@BeforeClass@Before@Test注解方法拋出的異常,這里所有的異常都會觸發一次testFailure的事件,這個實現可以查看Runner小節的EachTestNotifier類的實現。

          對于RunAfters的構造,可能要注意的一點是傳入RunAftersStatementRunBefores的實例,這個其實還是好理解的,因為RunAfters是在傳入的Statement運行結束后運行,而RunBefores又是要在測試方法之前運行的,因而需要將RunAfters放在Statement鏈的最頭端,而后是RunAfters,最后才是測試方法調用的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 
           

          InvokeMethodExpectedExceptionFailOnTimeout

          之所有要把這三個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注解的方法則被認為是真正要運行的測試方法,它的執行過程也被抽象成了一個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,在運行時直接調用該方法。并且InvokeMethod并沒有對其他Statement的引用,因而它是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 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實例作為參數構造ExpectException,當InvokeMethod實例執行后,判斷其拋出的異常是否為指定的異常或者該測試方法沒有拋出異常,在這兩種情況下,測試都會失敗,因而需要它拋出異常以處理這種情況。

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

           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是在創建測試類出錯時構造的Statement,這個設計思想有點類似Null Object模式,即保持編程模型的統一,即使在出錯的時候也用一個Statement去封裝,這也正是RunnerErrorReportingRunner的設計思想。

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

           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引用后返回,由于它內部還有一些比較復雜的邏輯,關于Rule將有一個單獨的小節講解。這里主要關注RunRules這個Statement的實現和構造:

           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的構造過程中,我們可以發現RunRules才是最外層的StatementTestRule中要執行的邏輯要么比@BeforeClass@Before注解的方法要早,要么比@AfterClass@After注解的方法要遲。

          Statement執行序列圖

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

           

           

           

          posted on 2012-05-11 23:53 DLevin 閱讀(3229) 評論(0)  編輯  收藏 所屬分類: JUnit
          主站蜘蛛池模板: 基隆市| 合江县| 洞头县| 大港区| 大名县| 革吉县| 平顺县| 鹤岗市| 舟山市| 彭水| 敦化市| 唐河县| 扶风县| 丹寨县| 阳江市| 乐陵市| 新密市| 梅州市| 明光市| 板桥市| 盐边县| 青冈县| 收藏| 英山县| 静海县| 湖北省| 台北县| 卢氏县| 香格里拉县| 琼中| 寿光市| 泸定县| 哈密市| 调兵山市| 荣昌县| 瓮安县| 页游| 宣威市| 邻水| 沐川县| 泰安市|