<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    上善若水
    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 閱讀(3219) 評論(0)  編輯  收藏 所屬分類: JUnit
    主站蜘蛛池模板: 久久精品亚洲一区二区三区浴池| 国产亚洲色婷婷久久99精品91| 久久久无码精品亚洲日韩按摩 | 亚洲一卡二卡三卡| 18观看免费永久视频| 亚洲精品永久www忘忧草| 亚洲香蕉免费有线视频| 亚洲一区在线观看视频| 妞干网在线免费观看| 亚洲精品无播放器在线播放 | 亚洲精品国产自在久久| 国产免费MV大全视频网站| 在线播放亚洲第一字幕| 久久国产乱子精品免费女 | 亚洲欧美日韩中文高清www777| 处破痛哭A√18成年片免费| 免费观看AV片在线播放| 美女被免费喷白浆视频| 亚洲国产精品久久66| 最近免费中文字幕大全高清大全1 最近免费中文字幕mv在线电影 | 亚洲日韩中文在线精品第一| 一级做a爱过程免费视| 亚洲色大成网站WWW久久九九| 久久九九AV免费精品| 亚洲AV无码无限在线观看不卡| 永久免费av无码网站大全| 一级做a爰片久久毛片免费看| 亚洲国产成人久久精品影视| 成人网站免费观看| 一区二区三区视频免费| 久久av无码专区亚洲av桃花岛| 成人毛片免费观看视频大全| v片免费在线观看| 亚洲成a人片毛片在线| 四虎免费久久影院| 香港a毛片免费观看| 丰满亚洲大尺度无码无码专线| 国产偷v国产偷v亚洲高清| 国内精品免费视频自在线| 国产一级a毛一级a看免费视频| 亚洲第一成年人网站|