Junit源碼閱讀(六)之Junit中的設計模式

ChoucClyne 8年前發布 | 38K 次閱讀 設計模式 JUnit 單元測試

來自: http://suemi94.com/源碼閱讀/junit_designPattern.html

前言

在這次的博客中我們將著重于Junit的許多集成性功能來討論Junit中的種種設計模式。可以說Junit的實現本身就是GOF設計原則的范例教本,下面就讓我們開始吧。

裝飾器模式

裝飾器模式是為了在原有功能上加入新功能,在Junit中絕對屬于使用最頻繁架構中最核心的模式,Runner、Filter、Rule等都是通過裝飾器模式來完成擴展的。下就以Filter的實現機制來說明。

首先從命令行中解析出來的FilterSpec生成各種Filter,如下:

    private Request applyFilterSpecs(Request request) {
        try {
            for (String filterSpec : filterSpecs) {
                Filter filter = FilterFactories.createFilterFromFilterSpec(
                        request, filterSpec);
                request = request.filterWith(filter);
            }
            return request;
        } catch (FilterNotCreatedException e) {
            return errorReport(e);
        }
    }

request的Filter方法將會返回一個新的Request,不過是Request的子類FilterRequest,它依然保留原有的request,只不過在返回Runner的時候,再采用Filter過濾,如下:

public final class FilterRequest extends Request {
    private final Request request;

    private final Filter fFilter;


    public FilterRequest(Request request, Filter filter) {
        this.request = request;
        this.fFilter = filter;
    }

    @Override
    public Runner getRunner() {
        try {
            Runner runner = request.getRunner();
            fFilter.apply(runner);
            return runner;
        } catch (NoTestsRemainException e) {
            return new ErrorReportingRunner(Filter.class, new Exception(String
                    .format("No tests found matching %s from %s", fFilter
                            .describe(), request.toString())));
        }
    }
}

Filter的apply方法會調用runner自身實現的filter方法并以自己作為參數,以ParentRunner為例,我們給出filter方法的一般實現。

public void filter(Filter filter) throws NoTestsRemainException {
        synchronized (childrenLock) {
            List<T> children = new ArrayList<T>(getFilteredChildren());
            for (Iterator<T> iter = children.iterator(); iter.hasNext(); ) {
                T each = iter.next();
                if (shouldRun(filter, each)) {
                    try {
                        filter.apply(each);
                    } catch (NoTestsRemainException e) {
                        iter.remove();
                    }
                } else {
                    iter.remove();
                }
            }
            filteredChildren = Collections.unmodifiableCollection(children);
            if (filteredChildren.isEmpty()) {
                throw new NoTestsRemainException();
            }
        }
    }

顯然非原子的Runner通過維護一個filteredChildren列表來提供它所有通過過濾的child,每次有新的filter作用到之上,它都需要更新該列表。對于原子的測試,它會提供出它的description,由Filter實現的shouldRun方法來判斷是否會被過濾,這也是filteredChildren列表更新的原理。

當我們先后有多個Filter時,可以不停地包裝已有的FilterRequest,每個FilterRequest在getRunnere時都會先調用其內部的Request,然后執行相同的附加操作,也即更新內部Request返回的Runner的filteredChildren列表。使用實現同一接口繼承同一父類的各個過濾器相互嵌套就可以實現一個過濾鏈。

工廠模式

工廠模式也在Junit中被大量使用,主要用來生產各類Rule、Filter等。我們依然以Filter機制為例來介紹一次抽象工廠的使用。FilterFactory是一個工廠接口如下:

public interface FilterFactory {

    Filter createFilter(FilterFactoryParams params) throws FilterNotCreatedException;


    @SuppressWarnings("serial")
    class FilterNotCreatedException extends Exception {
        public FilterNotCreatedException(Exception exception) {
            super(exception.getMessage(), exception);
        }
    }
}

FilterFactories提供了各種由參數來生成不同工廠的方法,同時又使用生成的工廠來生成Filter,可以說在廣義上這就是一個抽血工廠模式,不過FilterFactories完成了生產產品的過程,又集成了提供各類工廠的方法。下面給出代碼大家感受一下:

class FilterFactories {

    public static Filter createFilterFromFilterSpec(Request request, String filterSpec)
            throws FilterFactory.FilterNotCreatedException {
        Description topLevelDescription = request.getRunner().getDescription();
        String[] tuple;

        if (filterSpec.contains("=")) {
            tuple = filterSpec.split("=", 2);
        } else {
            tuple = new String[]{ filterSpec, "" };
        }

        return createFilter(tuple[0], new FilterFactoryParams(topLevelDescription, tuple[1]));
    }


    public static Filter createFilter(String filterFactoryFqcn, FilterFactoryParams params)
            throws FilterFactory.FilterNotCreatedException {
        FilterFactory filterFactory = createFilterFactory(filterFactoryFqcn);

        return filterFactory.createFilter(params);
    }


    public static Filter createFilter(Class<? extends FilterFactory> filterFactoryClass, FilterFactoryParams params)
            throws FilterFactory.FilterNotCreatedException {
        FilterFactory filterFactory = createFilterFactory(filterFactoryClass);

        return filterFactory.createFilter(params);
    }

    static FilterFactory createFilterFactory(String filterFactoryFqcn) throws FilterNotCreatedException {
        Class<? extends FilterFactory> filterFactoryClass;

        try {
            filterFactoryClass = Classes.getClass(filterFactoryFqcn).asSubclass(FilterFactory.class);
        } catch (Exception e) {
            throw new FilterNotCreatedException(e);
        }

        return createFilterFactory(filterFactoryClass);
    }

    static FilterFactory createFilterFactory(Class<? extends FilterFactory> filterFactoryClass)
            throws FilterNotCreatedException {
        try {
            return filterFactoryClass.getConstructor().newInstance();
        } catch (Exception e) {
            throw new FilterNotCreatedException(e);
        }
    }
}

組合模式

在Junit中組合模式主要用于管理Runner和Description的組合。由于測試的需求十分多樣,有時需要測試單個方法,有時需要測試單個類的所有方法,有時又需要測試多個類的組合方法,所以對于測試的表達必須是強大的。組合模式顯然符合這個要求,根部的runner會調動子節點的run,向上只暴露出根節點的run方法。由于之前幾篇中對這一部分論述較多,此處就不再贅述了。

觀察者模式

最典型的就是Notifier和Listener,當測試開始、結束、出現錯誤時,Notifier將通知它管理的Listener執行相應的操作,但有趣之處就在于為了能夠處理在通知過程中出現的異常,Notifer使用了一個內部類SafeNotifier,所有的對應事件(測試開始等)覆寫SafeNotifier里的notifyListener函數,在其中寫調用Listener的具體哪一個函數。相關的詳細內容請參考上一篇博客,下面給出RunNotifier的源碼(刪去部分內容)。

public class RunNotifier {
    private final List<RunListener> listeners = new CopyOnWriteArrayList<RunListener>();
    private volatile boolean pleaseStop = false;

    public void addListener(RunListener listener) {
        if (listener == null) {
            throw new NullPointerException("Cannot add a null listener");
        }
        listeners.add(wrapIfNotThreadSafe(listener));
    }


    public void removeListener(RunListener listener) {
        if (listener == null) {
            throw new NullPointerException("Cannot remove a null listener");
        }
        listeners.remove(wrapIfNotThreadSafe(listener));
    }

    RunListener wrapIfNotThreadSafe(RunListener listener) {
        return listener.getClass().isAnnotationPresent(RunListener.ThreadSafe.class) ?
                listener : new SynchronizedRunListener(listener, this);
    }


    private abstract class SafeNotifier {
        private final List<RunListener> currentListeners;

        SafeNotifier() {
            this(listeners);
        }

        SafeNotifier(List<RunListener> currentListeners) {
            this.currentListeners = currentListeners;
        }

        void run() {
            int capacity = currentListeners.size();
            List<RunListener> safeListeners = new ArrayList<RunListener>(capacity);
            List<Failure> failures = new ArrayList<Failure>(capacity);
            for (RunListener listener : currentListeners) {
                try {
                    notifyListener(listener);
                    safeListeners.add(listener);
                } catch (Exception e) {
                    failures.add(new Failure(Description.TEST_MECHANISM, e));
                }
            }
            fireTestFailures(safeListeners, failures);
        }

        abstract protected void notifyListener(RunListener each) throws Exception;
    }




    public void fireTestRunFinished(final Result result) {
        new SafeNotifier() {
            @Override
            protected void notifyListener(RunListener each) throws Exception {
                each.testRunFinished(result);
            }
        }.run();
    }

    public void fireTestFinished(final Description description) {
        new SafeNotifier() {
            @Override
            protected void notifyListener(RunListener each) throws Exception {
                each.testFinished(description);
            }
        }.run();
    }


}

職責鏈模式

可以參考之前的第三篇博客,Junit的Validator機制需要在三個層次——類、方法和域上進行驗證,采用了職責鏈模式。

 本文由用戶 ChoucClyne 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!