<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源碼之Builder、RequestJUnitCore

    經(jīng)過前面三節(jié)的RunnerStatement、Rule的講解,事實上JUnit的核心運行邏輯已經(jīng)完成了,剩下的就是一些外圍的支持代碼,包括Runner的構(gòu)建、用Assert對測試方法的運行結(jié)果的驗證代碼以及為了兼容性而存在的一些代碼。本節(jié)將關(guān)注Runner的構(gòu)建部分,在JUnit中通過RequestRunnerBuilder共同支持。

    JUnit中,RunnerBuilder對根據(jù)測試類創(chuàng)建Runner邏輯的封裝,特別是它支持@RunWith注解以在測試類中指定需要的執(zhí)行的Runner,這也是自定義的Runner可以很方便的插入JUnit框架運行的原因,這個設計其實也蠻具有參考價值的:通過注解的方式為用戶提供插入點,以擴展框架本身的功能;而在實現(xiàn)工程中,通過外圍Builder來支持,從而不影響核心設計。

    Request有點類似配置實例的感覺,用戶可以根據(jù)自己的需求來定制Runner創(chuàng)建的信息,如代理給RunnerBuilder為每個測試類創(chuàng)建相應的Runner,根據(jù)用戶的需求以決定是否要有filtersort操作等。用戶可以定義自己的Request實例,并傳遞給JUnitCore以實現(xiàn)自己特定的需求。

    在現(xiàn)實中有可能存在這樣的一個需求,即用戶想同時運行多個測試類,而且這些測試類之間又是相互獨立的,在JUnit中,使用Suite來表達這個需求,但是在Request中并沒有一個單獨的Request接收多個Class實例以創(chuàng)建Suite這個Runner,而是它使用了一個獨立的類Computer來完成這樣的功能,在Request中定義一個靜態(tài)方法來處理這個問題。不過我很奇怪為什么要這么做,這個設計和現(xiàn)存的編程模型是格格不入的,個人不太贊同。

    大體介紹了RunnerBuilderRequest的用途和設計,接下來將詳細介紹他們的源碼實現(xiàn),先從RunnerBuilder開始。

    RunnerBuilder

    RunnerBuilder的核心就是根據(jù)給出的Class實例,創(chuàng)建相應的Runner。一般創(chuàng)建Runner遵循的邏輯是:

    1.       如果Class中有@Ignored注解,那么將創(chuàng)建IgnoredClassRunner,該Runner在運行時什么都不做,只是觸發(fā)testIgnored事件。

    2.       如果Class中有@RunWith注解,則使用@RunWith注解中指定的Runner

    3.       如果Class中有靜態(tài)suite()方法的存在,則使用SuiteMethod這個Runner(兼容JUnit3)。

    4.       如果Class繼承了TestCase類,則使用JUnit38ClassRunner(兼容JUnit3)。

    5.       否則,使用BlockJUnit4ClassRunnerJUnit4中默認的Runner)。

    其實這個邏輯就是AllDefaultPossibilitiesBuilder中構(gòu)造Runner的實現(xiàn)。在JUnit內(nèi)部大量的使用這個RunnerBuilder來構(gòu)造Runner。其源碼如下:

     1 public class AllDefaultPossibilitiesBuilder extends RunnerBuilder {
     2     private final boolean fCanUseSuiteMethod;
     3     public AllDefaultPossibilitiesBuilder(boolean canUseSuiteMethod) {
     4        fCanUseSuiteMethod= canUseSuiteMethod;
     5     }
     6     @Override
     7     public Runner runnerForClass(Class<?> testClass) throws Throwable {
     8        List<RunnerBuilder> builders= Arrays.asList(
     9               ignoredBuilder(),
    10               annotatedBuilder(),
    11               suiteMethodBuilder(),
    12               junit3Builder(),
    13               junit4Builder());
    14        for (RunnerBuilder each : builders) {
    15            Runner runner= each.safeRunnerForClass(testClass);
    16            if (runner != null)
    17               return runner;
    18        }
    19        return null;
    20     }
    21     protected JUnit4Builder junit4Builder() {
    22        return new JUnit4Builder();
    23     }
    24     protected JUnit3Builder junit3Builder() {
    25        return new JUnit3Builder();
    26     }
    27     protected AnnotatedBuilder annotatedBuilder() {
    28        return new AnnotatedBuilder(this);
    29     }
    30     protected IgnoredBuilder ignoredBuilder() {
    31        return new IgnoredBuilder();
    32     }
    33     protected RunnerBuilder suiteMethodBuilder() {
    34        if (fCanUseSuiteMethod)
    35            return new SuiteMethodBuilder();
    36        return new NullBuilder();
    37     }
    38 }

    其中fCanUseSuiteMethod用于表達測試類中靜態(tài)的suite()方法是否被視為用于獲得多個實例運行的方法,這個是為了兼容JUnit3而存在,而且在JUnit內(nèi)部的使用時一般都是給true。再加上junit3Builder()放在junit4Builder()之前構(gòu)造RunnerBuilder,表明為了兼容JUnit3JUnit4JUnit3中的風格為首選風格。

    這里將不對為了兼容JUnit3而創(chuàng)建的RunnerBuilder做介紹,因而下面只會介紹IgnoredBuilderAnnotatedBuilder、JUnit4Builder,事實上它都太簡單了,以至于基本上不用什么介紹了。IgnoredBuilder會檢查傳入的Class實例是否有@Ignored注解,若有,則創(chuàng)建IgnoredClassRunner,否則返回null;AnnotatedBuilder檢查傳入的Class實例是否有@RunWith注解,若有,則使用@RunWith注解中指定的Runner,否則,返回null,這里需要注意的是在用戶自定義的Runner中,必須包含一個以Class實例作為參數(shù)的構(gòu)造函數(shù),或者以Class實例和RunnerBuilder實例作為參數(shù)的構(gòu)造函數(shù),否則在構(gòu)造自定義的Runner時會出錯;JUnit4Builder直接根據(jù)傳入的測試類Class的實例創(chuàng)建BlockJUnit4ClassRunner

     1 public class IgnoredBuilder extends RunnerBuilder {
     2     @Override
     3     public Runner runnerForClass(Class<?> testClass) {
     4        if (testClass.getAnnotation(Ignore.class!= null)
     5            return new IgnoredClassRunner(testClass);
     6        return null;
     7     }
     8 }
     9 public class AnnotatedBuilder extends RunnerBuilder {
    10     private static final String CONSTRUCTOR_ERROR_FORMAT= "Custom runner class %s should have a public constructor with signature %s(Class testClass)";
    11     private RunnerBuilder fSuiteBuilder;
    12     public AnnotatedBuilder(RunnerBuilder suiteBuilder) {
    13        fSuiteBuilder= suiteBuilder;
    14     }
    15     @Override
    16     public Runner runnerForClass(Class<?> testClass) throws Exception {
    17        RunWith annotation= testClass.getAnnotation(RunWith.class);
    18        if (annotation != null)
    19            return buildRunner(annotation.value(), testClass);
    20        return null;
    21     }
    22     public Runner buildRunner(Class<? extends Runner> runnerClass,
    23            Class<?> testClass) throws Exception {
    24        try {
    25            return runnerClass.getConstructor(Class.class).newInstance(
    26                   new Object[] { testClass });
    27        } catch (NoSuchMethodException e) {
    28            try {
    29               return runnerClass.getConstructor(Class.class,
    30                      RunnerBuilder.class).newInstance(
    31                      new Object[] { testClass, fSuiteBuilder });
    32            } catch (NoSuchMethodException e2) {
    33               String simpleName= runnerClass.getSimpleName();
    34               throw new InitializationError(String.format(
    35                      CONSTRUCTOR_ERROR_FORMAT, simpleName, simpleName));
    36            }
    37        }
    38     }
    39 }
    40 public class JUnit4Builder extends RunnerBuilder {
    41     @Override
    42     public Runner runnerForClass(Class<?> testClass) throws Throwable {
    43        return new BlockJUnit4ClassRunner(testClass);
    44     }
    45 }

    RunBuilder類本身也定義了一些方法,以幫助其他Runner,如Suite,構(gòu)建其內(nèi)部通過其他方式取到的測試類Class實例。這里對parent字段的存在有必要解釋一下,因為我剛開始看到的時候也很費解,addParent()方法只在一個方法中調(diào)用一次,而且就這個類來看也不存在遞歸,為什么會有對相同parents的驗證?要解釋這個問題,需要知道Suite的構(gòu)造函數(shù)還會調(diào)用runners()方法,加入有一次調(diào)用parent為一個使用Suite的類,這個類同時又在children中出現(xiàn),那么在調(diào)用該方法使將給類加入到parents中,而后在構(gòu)造children中的該類時又會調(diào)用該方法,將想用的Class實例加入parents中,從而引起異常。

     1 public abstract class RunnerBuilder {
     2     private final Set<Class<?>> parents= new HashSet<Class<?>>();
     3     public abstract Runner runnerForClass(Class<?> testClass) throws Throwable;
     4     public Runner safeRunnerForClass(Class<?> testClass) {
     5        try {
     6            return runnerForClass(testClass);
     7        } catch (Throwable e) {
     8            return new ErrorReportingRunner(testClass, e);
     9        }
    10     }
    11     Class<?> addParent(Class<?> parent) throws InitializationError {
    12        if (!parents.add(parent))
    13            throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName()));
    14        return parent;
    15     }
    16     void removeParent(Class<?> klass) {
    17        parents.remove(klass);
    18     }
    19     public List<Runner> runners(Class<?> parent, Class<?>[] children)
    20            throws InitializationError {
    21        addParent(parent);
    22        try {
    23            return runners(children);
    24        } finally {
    25            removeParent(parent);
    26        }
    27     }
    28     public List<Runner> runners(Class<?> parent, List<Class<?>> children)
    29            throws InitializationError {
    30        return runners(parent, children.toArray(new Class<?>[0]));
    31     }
    32     private List<Runner> runners(Class<?>[] children) {
    33        ArrayList<Runner> runners= new ArrayList<Runner>();
    34        for (Class<?> each : children) {
    35            Runner childRunner= safeRunnerForClass(each);
    36            if (childRunner != null)
    37               runners.add(childRunner);
    38        }
    39        return runners;
    40     }
    41 }

    最后來看一下RunnerBuilder的類結(jié)構(gòu)圖吧,了解一下目前存在的幾個RunnerBuilder


    Request

    Request是對RunnerBuilder的封裝,它提供了改變RunnerBuilder創(chuàng)建出的Runner的接口,如創(chuàng)建Runner后,用FilterSorter過濾或重新排列測試方法的順序。就目前JUnit只有FilterSorter可以對Runner做一些自定義的配置。Filter可以定義那些測試方法是可以運行的,比如在eclipse中提供的對一個測試方法單獨運行就是使用它來實現(xiàn);或者用戶可以自己定義一個可以運行方法的集合,然后只要遇到這樣的方法,然后根據(jù)這個集合來編寫自定義的Filter。Sorter則用于排列Runner內(nèi)部測試方法的執(zhí)行順序,但是這個定制只是對一個Runner中的測試方法有用,它并不會排列跨Runner之間的測試方法。不廢話了,先來看一下Request的類結(jié)構(gòu)圖吧。


    Request的結(jié)構(gòu)比較簡單,而且代碼實現(xiàn)也比較簡單,Request是一個抽象類,它定義了一個getRunner()的抽象方法,這個方法只是返回一個Runner實例。其中ClassRequest根據(jù)一個測試類,使用AllDefaultPossibilitiesBuilder創(chuàng)建一個Runner;FilterRequest則以一個RequestFilter實例為構(gòu)造參數(shù),在實現(xiàn)getRunner()方法時,根據(jù)傳入的Request獲取Runner,并對改Runner應用傳入的Filter以過濾掉那些不需要運行的測試方法;SortingRequest也是以一個RequestComparator<Description>為構(gòu)造參數(shù),在實現(xiàn)getRunner()方法是,根據(jù)傳入的Request獲取Runner,并根據(jù)comparator構(gòu)造Sorter對剛獲取到的Runner排序。這些實現(xiàn)有點Decorator模式的味道。

     1 public class ClassRequest extends Request {
     2     private final Class<?> fTestClass;
     3     private boolean fCanUseSuiteMethod;
     4     public ClassRequest(Class<?> testClass, boolean canUseSuiteMethod) {
     5        fTestClass= testClass;
     6        fCanUseSuiteMethod= canUseSuiteMethod;
     7     }
     8     public ClassRequest(Class<?> testClass) {
     9        this(testClass, true);
    10     }
    11     @Override
    12     public Runner getRunner() {
    13        return new AllDefaultPossibilitiesBuilder(fCanUseSuiteMethod).safeRunnerForClass(fTestClass);
    15     }
    16 }
    17 public final class FilterRequest extends Request {
    18     private final Request fRequest;
    19     private final Filter fFilter;
    20     public FilterRequest(Request classRequest, Filter filter) {
    21        fRequest= classRequest;
    22        fFilter= filter;
    23     }
    24     @Override
    25     public Runner getRunner() {
    26        try {
    27            Runner runner= fRequest.getRunner();
    28            fFilter.apply(runner);
    29            return runner;
    30        } catch (NoTestsRemainException e) {
    31            return new ErrorReportingRunner(Filter.classnew Exception(String
    32                   .format("No tests found matching %s from %s", fFilter
    33                          .describe(), fRequest.toString())));
    34        }
    35     }
    36 }
    37 public class SortingRequest extends Request {
    38     private final Request fRequest;
    39     private final Comparator<Description> fComparator;
    40     public SortingRequest(Request request, Comparator<Description> comparator) {
    41        fRequest= request;
    42        fComparator= comparator;
    43     }
    44     @Override
    45     public Runner getRunner() {
    46        Runner runner= fRequest.getRunner();
    47        new Sorter(fComparator).apply(runner);
    48        return runner;
    49     }
    50 }
    51 public abstract class Request {
    52     public abstract Runner getRunner();
    53 }

    除了Request類結(jié)構(gòu),Request類本身還提供了多個工場方法,以一種不需要知道Request類結(jié)構(gòu)的方法創(chuàng)建Request,也算是一種封裝吧,使用起來比較方便,而且隨著框架的演化,可以添加或刪除子類而不需要考慮用戶是否使用了某個子類。如果做的安全一些、然后不考慮測試的話,可以把FilterRequestSortingRequest的可見性降低,如包級別的。除了一些靜態(tài)的工場方法,RequestFilterSorter也提供了各自的方法支持,在我們得到一個Request的引用后,只需要調(diào)用這兩個方法即可構(gòu)造需要的RequestFilterRequestSortingRequest)。

     1 public abstract class Request {
     2     public static Request method(Class<?> clazz, String methodName) {
     3        Description method= Description.createTestDescription(clazz, methodName);
     4        return Request.aClass(clazz).filterWith(method);
     5     }
     6     public static Request aClass(Class<?> clazz) {
     7        return new ClassRequest(clazz);
     8     }
     9     public static Request classWithoutSuiteMethod(Class<?> clazz) {
    10        return new ClassRequest(clazz, false);
    11     }
    12     public static Request classes(Computer computer, Class<?> classes) {
    13        try {
    14            AllDefaultPossibilitiesBuilder builder= new AllDefaultPossibilitiesBuilder(true);
    15            Runner suite= computer.getSuite(builder, classes);
    16            return runner(suite);
    17        } catch (InitializationError e) {
    18            throw new RuntimeException(
    19                   "Bug in saff's brain: Suite constructor, called as above, should always complete");
    20        }
    21     }
    22     public static Request classes(Class<?> classes) {
    23        return classes(JUnitCore.defaultComputer(), classes);
    24     }
    25     public static Request runner(final Runner runner) {
    26        return new Request(){
    27            @Override
    28            public Runner getRunner() {
    29               return runner;
    30            }     
    31        };
    32     }
    33     public Request filterWith(Filter filter) {
    34        return new FilterRequest(this, filter);
    35     }
    36     public Request filterWith(final Description desiredDescription) {
    37        return filterWith(Filter.matchMethodDescription(desiredDescription));
    38     }
    39     public Request sortWith(Comparator<Description> comparator) {
    40        return new SortingRequest(this, comparator);
    41     }
    42 }
    43 public class Computer {
    44     public static Computer serial() {
    45        return new Computer();
    46     }
    47     public Runner getSuite(final RunnerBuilder builder,
    48            Class<?>[] classes) throws InitializationError {
    49        return new Suite(new RunnerBuilder() {
    50            @Override
    51            public Runner runnerForClass(Class<?> testClass) throws Throwable {
    52               return getRunner(builder, testClass);
    53            }
    54        }, classes);
    55     }
    56     protected Runner getRunner(RunnerBuilder builder, Class<?> testClass) throws Throwable {
    57        return builder.runnerForClass(testClass);
    58     }
    59 }

     

    這里對Computer這個類引入的意義一直沒有弄明白,為什么不直接在Request.classes()方法中創(chuàng)建Suite?即使要提取到Computer中以給創(chuàng)建Runner提供擴展點,直接在getSuite()方法中使用builder創(chuàng)建Suite就可以了啊,但是又要將getRunner()方法提取出來,這個提取可以給子類在根據(jù)buildertestClass創(chuàng)建Runner提供擴展點,但是以我目前的水平,還是看不多這個擴展點存在的意義。

    JUnitCore

    JUnitCoreJUnit中運行Request的門面類,同時它也提供了對命令模式的測試實現(xiàn),它接收多個測試類作為參數(shù),然后運行這些測試類中的所有測試方法。其實現(xiàn)是從傳入的參數(shù)中取到所有的測試類的Class實例,然后根據(jù)這些測試類的Class實例創(chuàng)建Request實例,從創(chuàng)建的Request實例中可以取得Runner實例,運行該Runner,并處理事件邏輯,最后如果所有測試通過,則退出值為0,否則為1。為了統(tǒng)計測試結(jié)果信息,JUnit還提供了一個默認的RunListener實現(xiàn):TextRunListener,這個Listener在每個測試方法開始的時候打印一個點’.’,當一個測試方法失敗是打印E,當一個測試方法被忽略時打印I,當所有測試方法執(zhí)行完成后打印總體統(tǒng)計時間,如運行時間、所有錯誤信息的異常堆棧以及最后成功多少、失敗多少等信息。對于基于JUnit編寫更適合項目本身的測試運行的用戶來說,最重要的就是幾個run()方法,這些用戶可以通過實現(xiàn)自己特定的邏輯以創(chuàng)建出符合自己需求的Request或通過某種方式查找到所有自己要運行的測試類等,然后調(diào)用你需要的run()方法。

     1 public class JUnitCore {
     2     private RunNotifier fNotifier;
     3     public JUnitCore() {
     4        fNotifier= new RunNotifier();
     5     }
     6     public static Result runClasses(Computer computer, Class<?> classes) {
     7        return new JUnitCore().run(computer, classes);
     8     }
     9     public static Result runClasses(Class<?> classes) {
    10        return new JUnitCore().run(defaultComputer(), classes);
    11     }
    12     public Result run(Class<?> classes) {
    13        return run(Request.classes(defaultComputer(), classes));
    14     }
    15     public Result run(Computer computer, Class<?> classes) {
    16        return run(Request.classes(computer, classes));
    17     }
    18     public Result run(Request request) {
    19        return run(request.getRunner());
    20     }
    21     public Result run(Runner runner) {
    22        Result result= new Result();
    23        RunListener listener= result.createListener();
    24        fNotifier.addFirstListener(listener);
    25        try {
    26            fNotifier.fireTestRunStarted(runner.getDescription());
    27            runner.run(fNotifier);
    28            fNotifier.fireTestRunFinished(result);
    29        } finally {
    30            removeListener(listener);
    31        }
    32        return result;
    33     }
    34 }

     

     

    posted on 2012-05-12 00:08 DLevin 閱讀(3521) 評論(0)  編輯  收藏 所屬分類: JUnit
    主站蜘蛛池模板: 国产成人涩涩涩视频在线观看免费| 两性色午夜视频免费播放| 国产成人亚洲合集青青草原精品 | 黄色视频在线免费观看| 精品特级一级毛片免费观看| 亚洲a∨无码精品色午夜| 婷婷国产偷v国产偷v亚洲| 国产精品亚洲精品日韩电影| 深夜福利在线免费观看| 一区二区三区在线免费观看视频 | 亚洲第一页在线观看| 亚洲一区中文字幕在线观看| 亚洲国产91在线| 亚洲色在线无码国产精品不卡| 亚洲日韩精品国产3区| 国产精品亚洲а∨无码播放不卡 | 亚洲乱妇熟女爽到高潮的片| 国产精品亚洲专区无码牛牛| 黄网站色视频免费看无下截 | 亚洲成a人不卡在线观看| 国产成人精品亚洲2020| 亚洲午夜无码毛片av久久京东热 | 亚洲人成影院在线| 亚洲三级在线播放| 亚洲JIZZJIZZ妇女| 无码日韩人妻AV一区免费l| 西西人体免费视频| 97碰公开在线观看免费视频| 成全视频在线观看免费高清动漫视频下载| 永久免费毛片手机版在线看| www国产亚洲精品久久久日本| 亚洲色爱图小说专区| 亚洲人成在久久综合网站| 狠狠入ady亚洲精品| a毛片免费播放全部完整| 日韩欧毛片免费视频| 全部免费国产潢色一级| 久久青青成人亚洲精品| 亚洲永久网址在线观看| 一级免费黄色大片| 日本h在线精品免费观看|