资讯专栏INFORMATION COLUMN

Junit源码阅读(六)之Junit中的设计模式

jlanglang / 1306人阅读

摘要:前言在这次的博客中我们将着重于的许多集成性功能来讨论中的种种设计模式。装饰器模式装饰器模式是为了在原有功能上加入新功能,在中绝对属于使用最频繁架构中最核心的模式,等都是通过装饰器模式来完成扩展的。

前言

在这次的博客中我们将着重于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 children = new ArrayList(getFilteredChildren());
            for (Iterator 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 filterFactoryClass, FilterFactoryParams params)
            throws FilterFactory.FilterNotCreatedException {
        FilterFactory filterFactory = createFilterFactory(filterFactoryClass);

        return filterFactory.createFilter(params);
    }

    static FilterFactory createFilterFactory(String filterFactoryFqcn) throws FilterNotCreatedException {
        Class filterFactoryClass;

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

        return createFilterFactory(filterFactoryClass);
    }

    static FilterFactory createFilterFactory(Class 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 listeners = new CopyOnWriteArrayList();
    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 currentListeners;

        SafeNotifier() {
            this(listeners);
        }

        SafeNotifier(List currentListeners) {
            this.currentListeners = currentListeners;
        }

        void run() {
            int capacity = currentListeners.size();
            List safeListeners = new ArrayList(capacity);
            List failures = new ArrayList(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机制需要在三个层次——类、方法和域上进行验证,采用了职责链模式。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65448.html

相关文章

  • Junit源码阅读(一)

    摘要:是对测试样例的建模,用来组合多个测试样例,是中的核心内容。也是一个虚类,子类应该实现方法来决定对于是否运行。如下列代码所示组合了和,为运行时异常和断言错误屏蔽了不一致的方面,可以向上提供错误信息和样例信息。 Junit的工程结构 showImg(/img/bVsEeS); 从上图可以清楚的看出Junit大致分为几个版块,接下来一一简略介绍这些版块的作用。 runner:定义了Jun...

    Gilbertat 评论0 收藏0
  • Junit源码阅读(三)精致的Validator

    摘要:前言在建立的过程中,往往需要对当前的测试样例和注解进行验证,比如检查测试类是否含有非静态内部类,测试类是否是的。的验证机制非常精致而优美,在本次博客中我们就主要来谈一谈机制的实现。首先在中定义三个默认的类,如下。 前言 在建立Runner的过程中,往往需要对当前的测试样例和注解进行验证,比如检查测试类是否含有非静态内部类,测试类是否是Public的。Junit的验证机制非常精致而优美...

    李世赞 评论0 收藏0
  • Junit源码阅读(五)

    摘要:的作用是包装从生成的逻辑,提供两种方案生成和。最后从生成也异常简单,也就是实现其方法返回该。 前言 尽管在第二次博客中我们讲述了Runner的运行机制,但是许多其他特性比如Filter是如何与运行流程结合却并不清楚。这次我们来回顾整理一下Junit的执行流程,给出各种特性生效的机理,并分析一些代码中精妙的地方。 Junit的执行流程 JUnitCore的RunMain方法,使用jUn...

    vpants 评论0 收藏0
  • java9系列()HTTP/2 Client (Incubator)

    摘要:鉴于它还处在,如果不是着急使用,建议还是使用的,它是遵循规范的,使用起来更加方便。貌似要在版本才支持。揭秘让支持协议如何启用命令支持 序 本文主要研究下JEP 110: HTTP/2 Client (Incubator) 基本实例 sync get /** * --add-modules jdk.incubator.httpclient * @throws ...

    tomlingtm 评论0 收藏0
  • Junit源码阅读(四)自定义扩展

    摘要:前言上次的博客中我们着重介绍了的机制,这次我们将聚焦到自定义扩展上来。在很多情形下我们需要在测试过程中加入一些自定义的动作,这些就需要对进行包装,为此提供了以接口和为基础的扩展机制。 前言 上次的博客中我们着重介绍了Junit的Validator机制,这次我们将聚焦到自定义扩展Rule上来。在很多情形下我们需要在测试过程中加入一些自定义的动作,这些就需要对statement进行包装,...

    Little_XM 评论0 收藏0

发表评论

0条评论

jlanglang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<