zoukankan      html  css  js  c++  java
  • junit4X系列--Statement

    原文出处:http://www.blogjava.net/DLevin/archive/2012/05/11/377954.html。感谢作者的无私分享。

    初次用文字的方式记录读源码的过程,不知道怎么写,感觉有点贴代码的嫌疑。不过中间还是加入了一些自己的理解和心得,希望以后能够慢慢的改进,感兴趣的童鞋凑合着看吧,感觉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将会在下一节中详细讲解):

     

     


  • 相关阅读:
    Python学习Day2笔记(字符编码和函数)
    Python学习Day2笔记(集合和文件操作)
    PyCharm3.0默认快捷键(翻译的)
    C# 读取EXCEL文件的三种经典方法
    DataGridView列的宽度、行的高度自动调整
    禁用datagridview中的自动排序功能
    如何删除datatable中的一行数据
    NPOI导出Excel合并表头写入公式
    C# SaveFileDialog的用法(转载)
    linux操作
  • 原文地址:https://www.cnblogs.com/LinkinPark/p/5232875.html
Copyright © 2011-2022 走看看