JUnit Rule简述

Rule是JUnit 4.7之后新加入的特性,有点类似于拦截器,可以在测试类或测试方法执行前后添加额外的处理,本质上是对@BeforeClass, @AfterClass, @Before, @After等的另一种实现,只是功能上更灵活多变,易于扩展,且方便在类和项目之间共享。

JUnit的Rule特性提供了两个注解@Rule和@RuleClass,大体上说@Rule可以与@Before及@After对应,@ClassRule可以与@BeforeClass及@AfterClass对应。自JUnit4.10起可以使用TestRule接口代替此前一直在用的MethodRule接口,实际项目中可以通过实现TestRule或继承自JUnit内置Rule类进行扩展。

  • 适用场景

在简述中已经提到Rule特性本身也是对@BeforeClass, @AfterClass, @Before, @After功能的另外实现,所以基本上这四种注解的使用场景都适用于Rule,同时JUnit内置的Rule类还能够提供这四种注解未提供的功能。总体上说Rule特性的适用场景包括但不限于如下需求:

     - 在测试类或测试方法执行前后添加初始化或环境清理操作

     - 在测试执行过程中收集错误信息且无需中断测试

     - 在测试结束后添加额外的测试结果校验功能

     - 在测试执行前后创建及删除测试执行过程中产生的临时文件或目录

     - 对测试过程中产生的异常进行灵活校验

     - 将多个Rules串接在一起执行

     - 测试用例执行失败时重试指定次数

从使用习惯上来说,对于简单项目,@BeforeClass, @AfterClass, @Before, @After等注解已经能够满足测试需求;对于复杂点的项目,从易扩展、易维护和方便复用的角度考虑最好使用Rule特性,方便添加和移除Rule实例,灵活性大大提高。

  • 注解分类

JUnit中通过两个注解@Rule和@ClassRule来实现Rule扩展,这两个注解使用时需要放在实现了TestRule接口的Rule变量或返回Rule的方法之上,且修饰符都必须为public。

二者具体区别如下:

     - 被注解的变量或方法类型不同

        - @Rule修饰的变量或方法的修饰符必须为public,非static

        - @ClassRule修饰的变量或方法的修饰符必须为public static

     - 注解的级别不同

        - @Rule为变量或方法级注解,每个测试方法执行时都会调用被该注解修饰的Rule

        - @ClassRule为类级注解,执行单个测试类时只会调用一次被该注解修饰的Rule

      - 注解的对象限制不同

        - @Rule无注解对象限制

        - @ClassRule不能注解ErrorCollector(Verifier)

  • TestRule接口

TestRule是测试类或测试方法执行过程及报告信息的接口,可以在TestRule中添加初始化及环境清理的操作、监控测试执行的日志打印或UI截图操作、测试结果成功或失败校验操作等。TestRule仅定义了唯一的方法apply(),所以可以在TestRule实现类的apply()方法中加入测试项目需要的操作。

 

public interface TestRule {
//在实现类的apply()中加入测试需要的操作,本质上是对Statement实例base的进一步封装 Statement apply(Statement base, Description description); }

JUnit内置Rule

除了Rule特性外,JUnit还新增了一些核心Rule,均实现了TestRule接口,包括Verifier抽象类,ErrorCollector实现类,ExternalResource抽象类,TemporaryFolder实现类,TestWatcher抽象类,TestName实现类,ExpectedException实现类,Timeout实现类及RuleChain实现类(deprecated)。各接口实现类及类图参考如下:

JUnit源码分析 - 扩展 - 自定义Rule-LMLPHP

    - Verifier:所有测试结束后对测试执行结果添加额外的逻辑验证测试最终成功与否。该抽象类为子类提供一个接口方法verify()供扩展

    - ErrorCollector:是Verifier类的一个子类实现,用于在测试执行过程中收集错误信息,不会中断测试,最后调用verify()方法处理

    - ExternalResource:外部资源管理。该抽象类为子类提供了两个接口方法before()和after(),可以根据项目实际需要覆写扩展

    - TemporaryFolder:是抽象类ExternalResource的一个子类实现,用于在JUnit测试执行前后,创建和删除临时目录

    - TestWatcher:监视测试方法生命周期的各个阶段。该抽象类为子类提供了五个接口方法succeeded(), failed(), skipped(), starting()及finished()供扩展

    - TestName:是抽象类TestWatcher的一个子类实现,用于在测试执行过程中获取测试方法名称。在starting()中记录测试方法名,在getMethodName()中返回

    - ExpectedException:与@Test中的expected相对应,提供更强大灵活的异常验证功能,@Test只能修饰待测试方法,ExpectedException可以修饰待测试类

    - Timeout:与@Test中的timeout相对应,@Test只能修饰待测试方法,Timeout可以修饰待测试类

    - RuleChain:用于将多个Rules串在一起执行。RuleChain已经deprecated了,但是其源码实现比较有趣,所以本篇没有直接去掉。

    

篇幅原因此处仅简要介绍这些Rules提供的功能,后续将在专门的Rule及TestRule实现类源码分析中详解其实现。

JUnit Rule源码分析

以下过程是以典型的单个待测试类调用BlockJUnit4ClassRunner执行测试为例进行分析,如果对源码分析无兴趣可直接跳到JUnit Rule扩展示例部分。

分析Rule特性的源码实现之前需要先梳理Statement的概念及执行过程,tStatement是对原子级测试的封装,我们在JUnit Runner中看到的测试用例执行过程是顺序执行不同注解修饰的测试方法,即@BeforeClass->@Before->@Test->@After->@Before->@Test->@After->@Before->@Test->@After->@AfterClass(此处以三个待测试方法为例)。那么JUnit是如何将这一系列串接在一起的呢?其设计思想就是通过Statement以责任链的模式将其层层封装,责任链中上个节点的Statement中都存在对下一个节点的引用。Statement可以说是JUnit的核心设计之一,理清了Statement的执行过程就抓住了JUnit实现原理的主线。

Rule特性又是如何织入Statement的封装与执行过程的呢?我们知道Rule特性中有两个注解@ClassRule和@Rule用来修饰Rule变量或返回Rule的方法,这些变量或方法返回值的类型都需要实现TestRule接口,而TestRule中唯一定义的方法apply()的返回值类型就是Statement,所以JUnit中Rule特性的实现类同样是Statement的一种。根据BlockJUnit4ClassRunner的父类ParentRunner中的classBlock()方法中的调用,以及BlockJUnit4ClassRunner中methodBlock()方法中的调用,我们基本上可以梳理出@ClassRule和@Rule在整个Statement责任链中的执行顺序,以JUnit内置的Rule实现类ErrorCollector,TemporaryFolderr和TestName为例(含两个@Test修饰的待测试方法):

  • @ClassRule注解ErrorCollector类实例

  执行顺序为:

  @BeforeClass->@Before->@Test->@After->@Before->@Test->@After->@AfterClass->@ClassRule(verify())

  • @ClassRule注解TemporaryFolder类实例

  执行顺序为:

  @ClassRule(before())->@BeforeClass->@Before->@Test->@After->@Before->@Test->@After->@AfterClass->@ClassRule(after())

  • @Rule注解TestName类实例

  执行顺序为:

  @BeforeClass->@Rule(starting())->@Before->@Test->@After->@Rule(starting())->@Before->@Test->@After->@AfterClass

如果测试用例中@ClassRule和@Rule两个都存在,则按实际覆写的接口方法所处测试阶段顺序织入测试执行过程。

上述部分是分析Statement的设计思想及Rule在Statement执行过程中的顺序,文字描述通常都比较晦涩,还是特出关键源码一步步解读比较好,此处的简析仅仅是希望对Statement和Rule有一些大致的概念,方便后续的源码解读。

  • Statement封装过程的关键代码

Statement封装过程中有两个主要的方法classBlock()和methodBlock(),其中classBlock()是测试类级别的封装,也就是说测试类级注解@BeforeRule, @AfterRule以及@ClassRule修饰的方法在此处链式封装,methodBlock()是测试方法级别的封装,也就是说测试方法级别注解@Test(expected=xxx) , @Test(timeout=xxx), @Before, @After()及@Rule修饰的方法在此处链式封装。

  先看一下classBlock()的调用过程:

//org.junit.runners.ParentRunner
protected Statement classBlock(final RunNotifier notifier) {
        Statement statement = childrenInvoker(notifier);       //构造出所有测试方法基本的Statement类对象
        if (!areAllChildrenIgnored()) {
            statement = withBeforeClasses(statement);         //对应@BeforeClass
            statement = withAfterClasses(statement);         //对应@AfterClass
            statement = withClassRules(statement);          //对应@ClassRule
        }
        return statement;                         //返回层层封装后的Statement类对象
}

classBlock()中写的很清楚,首先调用childrenInvoker()构造Statement的基本行为,如果所有的子测试都没有被Ignore则通过withBeforeClasses(), withAfterClasses()及withClassRules()继续封装。先放一下,分析完childrenInvoker()的调用过程再从这里接入。

//org.junit.runners.ParentRunner
protected Statement childrenInvoker(final RunNotifier notifier) {
        return new Statement() {
            @Override
            public void evaluate() {
                runChildren(notifier);
            }
        };
}

childrenInvoker()的作用是构造基本的Statement行为,即执行所有的子测试runChildren(),在runChildren()中循环调用每个子测试runChild()。

//org.junit.runners.ParentRunner
private void runChildren(final RunNotifier notifier) {
        final RunnerScheduler currentScheduler = scheduler;
        try {
            for (final T each : getFilteredChildren()) {
                currentScheduler.schedule(new Runnable() {
                    public void run() {
                        ParentRunner.this.runChild(each, notifier);
                    }
                });
            }
        } finally {
            currentScheduler.finished();
        }
}
//org.junit.runners.ParentRunner
protected abstract void runChild(T child, RunNotifier notifier);
//org.junit.runners.BlockJUnit4ClassRunner
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
        Description description = describeChild(method);
        if (isIgnored(method)) {
            notifier.fireTestIgnored(description);
        } else {
            Statement statement = new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    methodBlock(method).evaluate();
                }
            };
            runLeaf(statement, description, notifier);
        }
}

因为ParentRunner中只有runChild()的抽象方法,所以该方法的具体实现在其子类BlockJUnit4ClassRunner中,子类的runChild()中调用了测试方法级的层层封装methodBlock()。

//org.junit.runners.BlockJUnit4ClassRunner
protected Statement methodBlock(final FrameworkMethod method) {
        Object test;
        try {
            test = new ReflectiveCallable() {
                @Override
                protected Object runReflectiveCall() throws Throwable {
                    return createTest(method);
                }
            }.run();
        } catch (Throwable e) {
            return new Fail(e);
        }

        Statement statement = methodInvoker(method, test);
        statement = possiblyExpectingExceptions(method, test, statement);
        statement = withPotentialTimeout(method, test, statement);
        statement = withBefores(method, test, statement);
        statement = withAfters(method, test, statement);
        statement = withRules(method, test, statement);
        return statement;
}
//org.junit.runners.BlockJUnit4ClassRunner
protected Object createTest() throws Exception {
        return getTestClass().getOnlyConstructor().newInstance();
}
//org.junit.runners.BlockJUnit4ClassRunner
protected Statement methodInvoker(FrameworkMethod method, Object test) {
        return new InvokeMethod(method, test);
}

   methodBlock()中首先在createTest()中通过反射构造实例,在将该实例及FrameworkMethod类对象method作为methodInvoker()的入参构造出基本的Statement类对象。

//org.junit.internal.runners.statements.InvokeMethod
public class InvokeMethod extends Statement {
    private final FrameworkMethod testMethod;
    private final Object target;

    public InvokeMethod(FrameworkMethod testMethod, Object target) {
        this.testMethod = testMethod;
        this.target = target;
    }

    @Override
    public void evaluate() throws Throwable {
        testMethod.invokeExplosively(target);
    }
}

构造出基本的Statement类对象后,在执行后续操作对该Statement类对象进行层层封装。篇幅原因就不再对如下possiblyExpectingExceptions等五个方法的调用过程作进一步解析,这些方法调用和下面将要讲解的classBlock()方法实现中的下半部分很相似,只是此处是测试方法级的封装调用,classBlock()中是测试类级的封装调用。

Statement statement = methodInvoker(method, test);            //构造出测试方法基本的Statement类对象
statement = possiblyExpectingExceptions(method, test, statement);   //对应@Test(expected=xxx)
statement = withPotentialTimeout(method, test, statement);       //对应@Test(timeout=xxx), deprecated
statement = withBefores(method, test, statement);            //对应@Before
statement = withAfters(method, test, statement);             //对应@After
statement = withRules(method, test, statement);             //对应@Rule
return statement;                               //返回层层封装后的待测试方法

再回到前面classBlock()中的分析过程,该方法的后半部分会对构造出的所有方法的基本statement类对象作进一步封装,依次为withBeforeClasses(), withAfterClasses()及withClassRules()。

//org.junit.runners.ParentRunner
protected Statement classBlock(final RunNotifier notifier) {
        Statement statement = childrenInvoker(notifier);       //构造出所有测试方法基本的Statement类对象
        if (!areAllChildrenIgnored()) {
            statement = withBeforeClasses(statement);         //对应@BeforeClass
            statement = withAfterClasses(statement);         //对应@AfterClass
            statement = withClassRules(statement);          //对应@ClassRule
        }
        return statement;                         //返回层层封装后的Statement类对象
}
 
//org.junit.runners.ParentRunner
protected Statement withBeforeClasses(Statement statement) {
        List<FrameworkMethod> befores = testClass
                .getAnnotatedMethods(BeforeClass.class);
        return befores.isEmpty() ? statement :
                new RunBefores(statement, befores, null);
}

withBeforeClasses()调用过程:提取出待测试类中用@BeforeClass注解的所有方法,再把这些方法和childrenInvoker()中构造出的基本Statement类对象作为入参用Statement的子类RunBefores重新封装并返回。

//org.junit.runners.ParentRunner
protected Statement withAfterClasses(Statement statement) {
        List<FrameworkMethod> afters = testClass
                .getAnnotatedMethods(AfterClass.class);
        return afters.isEmpty() ? statement :
                new RunAfters(statement, afters, null);
}

withAfterClasses()调用过程:提取出待测试类中用@AfterClass注解的所有方法,再把这些方法和withBeforeClasses()中返回的Statement类对象作为入参用Statement的子类RunAfters重新封装并返回。

//org.junit.runners.ParentRunner
private Statement withClassRules(Statement statement) {
        List<TestRule> classRules = classRules();
        return classRules.isEmpty() ? statement :
                new RunRules(statement, classRules, getDescription());
}

withClassRules()调用过程:提取出待测试类中用@ClassRule注解的所有Rule类变量或返回值为Rule类的方法,再把这些变量和方法同withAfterClasses()中返回的Statement类对象作为入参用Statement的子类RunRules重新封装并返回。

//org.junit.runners.ParentRunner
protected List<TestRule> classRules() {
        ClassRuleCollector collector = new ClassRuleCollector();
        testClass.collectAnnotatedMethodValues(null, ClassRule.class, TestRule.class, collector);
        testClass.collectAnnotatedFieldValues(null, ClassRule.class, TestRule.class, collector);
        return collector.getOrderedRules();
}
//org.junit.runners.ParentRunner
private static class ClassRuleCollector implements MemberValueConsumer<TestRule> {
        final List<RuleContainer.RuleEntry> entries = new ArrayList<RuleContainer.RuleEntry>();

        public void accept(FrameworkMember member, TestRule value) {
            ClassRule rule = member.getAnnotation(ClassRule.class);
            entries.add(new RuleContainer.RuleEntry(value, RuleContainer.RuleEntry.TYPE_TEST_RULE,
                    rule != null ? rule.order() : null));
        }

        public List<TestRule> getOrderedRules() {
            if (entries.isEmpty()) {
                return Collections.emptyList();
            }
            Collections.sort(entries, RuleContainer.ENTRY_COMPARATOR);
            List<TestRule> result = new ArrayList<TestRule>(entries.size());
            for (RuleContainer.RuleEntry entry : entries) {
                result.add((TestRule) entry.rule);
            }
            return result;
        }
}

    classRules()方法用于获取@ClassRule修饰的所有TestRule实现类。

//org.junit.rules.RunRules
public
class RunRules extends Statement { private final Statement statement; public RunRules(Statement base, Iterable<TestRule> rules, Description description) { statement = applyAll(base, rules, description); } @Override public void evaluate() throws Throwable { statement.evaluate(); } private static Statement applyAll(Statement result, Iterable<TestRule> rules, Description description) { for (TestRule each : rules) { result = each.apply(result, description); } return result; } }

    RunRules类用于根据调用classRules()获取到的所有TestRule实现类集合对withAfterRules()方法返回的Statement类实例进行重新封装。

  • Runner验证Rule规则的关键代码

本文开始提到过@ClassRule和@Rule修饰的Rule类变量或方法有一定的限制,比如public修饰符, 是或非static, 实现自TestRule接口等,所以在测试用例执行前需要进行相应的验证,这个是由ParentRunner及其子类在其构造方法的初始化过程中完成的。Rule规则的校验主要通过四个RuleMemberValidator实例CLASS_RULE_METHOD_VALIDATOR,CLASS_RULE_VALIDATOR,RULE_METHOD_VALIDATOR及RULE_VALIDATOR调用各自的validate()方法来实现的,具体调用过程解析如下:

   

//org.junit.runners.BlockJUnit4ClassRunner
public BlockJUnit4ClassRunner(Class<?> testClass) throws InitializationError {
        super(testClass);
}

    BlockJUnit4ClassRunner构造方法初始化过程会调用父类ParentRunner的的构造方法。

//org.junit.runners.ParentRunner
protected ParentRunner(Class<?> testClass) throws InitializationError {
        this.testClass = createTestClass(testClass);
        validate();
}

    ParentRunner的构造方法中包含了validate()调用

//org.junit.runners.ParentRunner
private void validate() throws InitializationError {
        List<Throwable> errors = new ArrayList<Throwable>();
        collectInitializationErrors(errors);
        if (!errors.isEmpty()) {
            throw new InvalidTestClassError(testClass.getJavaClass(), errors);
        }
}

    该validate()实现中包含collectInitializationErrors()调用,子类BlockJUnit4ClassRunner覆写了父类ParentRunner的collectInitializationErrors()方法。

//org.junit.runners.BlockJUnit4ClassRunner
@Override
protected void collectInitializationErrors(List<Throwable> errors) {
        super.collectInitializationErrors(errors);

        validatePublicConstructor(errors);
        validateNoNonStaticInnerClass(errors);
        validateConstructor(errors);
        validateInstanceMethods(errors);
        validateFields(errors);
        validateMethods(errors);
}

    子类BlockJUnit4ClassRunner中的collectInitializationErrors()方法实现会先调用父类ParentRunner中的collectInitializationErrors()。

//org.junit.runners.ParentRunner
protected void collectInitializationErrors(List<Throwable> errors) {
        validatePublicVoidNoArgMethods(BeforeClass.class, true, errors);
        validatePublicVoidNoArgMethods(AfterClass.class, true, errors);
        validateClassRules(errors);
        applyValidators(errors);
}

     父类ParentRunner中的collectInitializationErrors()方法实现中包含了validateClassRules()调用。

//org.junit.runners.ParentRunner
private void validateClassRules(List<Throwable> errors) {
        CLASS_RULE_VALIDATOR.validate(getTestClass(), errors);
        CLASS_RULE_METHOD_VALIDATOR.validate(getTestClass(), errors);
}

validateClassRules()方法中包含了两个RuleMemberValidator实例CLASS_RULE_VALIDATORCLASS_RULE_METHOD_VALIDATOR各自的validate()方法调用,这两个方法会对@ClassRule修饰的变量或方法进行Rule规则校验。

//org.junit.runners.BlockJUnit4ClassRunner
protected void validateFields(List<Throwable> errors) {
        RULE_VALIDATOR.validate(getTestClass(), errors);
}
//org.junit.runners.BlockJUnit4ClassRunner
private void validateMethods(List<Throwable> errors) {
        RULE_METHOD_VALIDATOR.validate(getTestClass(), errors);
}

validateFields()方法中包含了RuleMemberValidator实例RULE_VALIDATOR的validate()方法调用,validateMethods()方法中包含了RuleMemberValidator实例RULE_METHOD_VALIDATOR的validate()方法调用,这两个方法会对@Rule修饰的变量或方法进行Rule规则校验。


//org.junit.internal.runners.rules.RuleMemberValidator 验证@ClassRule修饰的方法是否复合Rule特性规则
public static final RuleMemberValidator CLASS_RULE_METHOD_VALIDATOR = classRuleValidatorBuilder() .forMethods() .withValidator(new DeclaringClassMustBePublic()) .withValidator(new MemberMustBeStatic()) .withValidator(new MemberMustBePublic()) .withValidator(new MethodMustBeATestRule())        .build();
//org.junit.internal.runners.rules.RuleMemberValidator 验证@ClassRule修饰的作用域是否复合Rule特性规则
public static final RuleMemberValidator CLASS_RULE_VALIDATOR =
            classRuleValidatorBuilder()
            .withValidator(new DeclaringClassMustBePublic())
            .withValidator(new MemberMustBeStatic())
            .withValidator(new MemberMustBePublic())
            .withValidator(new FieldMustBeATestRule())
       .build();
//org.junit.internal.runners.rules.RuleMemberValidator 验证@Rule修饰的方法是否复合Rule特性规则
public static final RuleMemberValidator RULE_METHOD_VALIDATOR =
            testRuleValidatorBuilder()
            .forMethods()
            .withValidator(new MemberMustBeNonStaticOrAlsoClassRule())
            .withValidator(new MemberMustBePublic())
            .withValidator(new MethodMustBeARule())
       .build();
//org.junit.internal.runners.rules.RuleMemberValidator 验证@Rule修饰的作用域是否复合Rule特性规则
public static final RuleMemberValidator RULE_VALIDATOR =
            testRuleValidatorBuilder()
            .withValidator(new MemberMustBeNonStaticOrAlsoClassRule())
            .withValidator(new MemberMustBePublic())
            .withValidator(new FieldMustBeARule())
       .build();

以上是四个RuleMemberValidator实例CLASS_RULE_METHOD_VALIDATOR,CLASS_RULE_VALIDATOR,RULE_METHOD_VALIDATOR及RULE_VALIDATOR的具体定义,其实从类名定义上就可以直观地看到这些实例具体的校验内容,篇幅原因不再详述。

  •  Rule特性织入Statement的关键代码

Rule特性织入Statement的过程主要依赖两个语句及其涉及到的嵌套调用,这两个语句即是classBlock()方法中的statement = withClassRules(statement)和methodBlock()方法中的statement = withRules(statement)。下面依次对二者嵌套的调用过程进行解析。

//org.junit.runners.ParentRunner
protected Statement classBlock(final RunNotifier notifier) {
        Statement statement = childrenInvoker(notifier);       //构造出所有测试方法基本的Statement类对象
        if (!areAllChildrenIgnored()) {
            statement = withBeforeClasses(statement);         //对应@BeforeClass
            statement = withAfterClasses(statement);         //对应@AfterClass
            statement = withClassRules(statement);          //对应@ClassRule
        }
        return statement;                         //返回层层封装后的Statement类对象
}
//org.junit.runners.BlockJUnit4ClassRunner
protected Statement methodBlock(final FrameworkMethod method) {
        Object test;
        try {
            test = new ReflectiveCallable() {
                @Override
                protected Object runReflectiveCall() throws Throwable {
                    return createTest(method);
                }
            }.run();
        } catch (Throwable e) {
            return new Fail(e);
        }

        Statement statement = methodInvoker(method, test);
        statement = possiblyExpectingExceptions(method, test, statement);
        statement = withPotentialTimeout(method, test, statement);
        statement = withBefores(method, test, statement);
        statement = withAfters(method, test, statement);
        statement = withRules(method, test, statement);
        return statement;
}

    先来看methodBlock()中的withRules()调用,MethodRule接口从JUnit4.10开始已经deprecated,该接口相关的代码可以直接ignore。

    

//org.junit.runners.BlockJUnit4ClassRunner
private Statement withRules(FrameworkMethod method, Object target, Statement statement) {
        RuleContainer ruleContainer = new RuleContainer();
        CURRENT_RULE_CONTAINER.set(ruleContainer);
        try {
            List<TestRule> testRules = getTestRules(target);
            for (MethodRule each : rules(target)) {
                if (!(each instanceof TestRule && testRules.contains(each))) {
                    ruleContainer.add(each);
                }
            }
            for (TestRule rule : testRules) {
                ruleContainer.add(rule);
            }
        } finally {
            CURRENT_RULE_CONTAINER.remove();
        }
        return ruleContainer.apply(method, describeChild(method), target, statement);
    }
}

创建RunContainer实例,将其设置为当前线程局部变量的值,通过getTestRules()获取注解target的所有@Rule规则,并将其加入到新建的RunContainer实例中  ,ThreadLocal的内在机制会保证这一过程在并发环境下的的线程安全。

//org.junit.runners.BlockJUnit4ClassRunner
private static final ThreadLocal<RuleContainer> CURRENT_RULE_CONTAINER = new ThreadLocal<RuleContainer>();
//org.junit.runners.BlockJUnit4ClassRunner
protected List<TestRule> getTestRules(Object target) {
        RuleCollector<TestRule> collector = new RuleCollector<TestRule>();
        getTestClass().collectAnnotatedMethodValues(target, Rule.class, TestRule.class, collector);
        getTestClass().collectAnnotatedFieldValues(target, Rule.class, TestRule.class, collector);
        return collector.result;
}

    在apply()方法中通过调用getSortedEntries()对所有Rule进行排序处理,处理完成后返回封装Rule之后的Statement实例。

//org.junit.runners.RuleContainer
public Statement apply(FrameworkMethod method, Description description, Object target,
            Statement statement) {
        if (methodRules.isEmpty() && testRules.isEmpty()) {
            return statement;
        }
        Statement result = statement;
        for (RuleEntry ruleEntry : getSortedEntries()) {
            if (ruleEntry.type == RuleEntry.TYPE_TEST_RULE) {
                result = ((TestRule) ruleEntry.rule).apply(result, description);
            } else {
                result = ((MethodRule) ruleEntry.rule).apply(result, method, target);
            }
        }
        return result;
    }
}
//org.junit.runners.RuleContainer
private List<RuleEntry> getSortedEntries() {
        List<RuleEntry> ruleEntries = new ArrayList<RuleEntry>(
                methodRules.size() + testRules.size());
        for (MethodRule rule : methodRules) {
            ruleEntries.add(new RuleEntry(rule, RuleEntry.TYPE_METHOD_RULE, orderValues.get(rule)));
        }
        for (TestRule rule : testRules) {
            ruleEntries.add(new RuleEntry(rule, RuleEntry.TYPE_TEST_RULE, orderValues.get(rule)));
        }
        Collections.sort(ruleEntries, ENTRY_COMPARATOR);
        return ruleEntries;
    }
}

以上是对方法进行Rule规则的封装,在classBlock()代码块中会上溯到childrenInvoker()中的调用,测试方法级的Statement封装处理完之后,还需要继续进行测试类级的Statement封装。从开始贴出的classBlock()的代码块中可以看到childrenInvoker()返回对所有测试方法的Statement封装之后,还会继续调用withBeforeClasses(), withAfterClasses()及withClassRules()进一步处理,这里对于withBeforeClasses()和withAfterClasses()的方法调用就不详细讲解了,下面看看withClassRules()方法的执行流程,这个是与Rule规则直接相关的。

 

 

//org.junit.runners.ParentRunner
protected Statement classBlock(final RunNotifier notifier) {
        Statement statement = childrenInvoker(notifier);       //构造出所有测试方法基本的Statement类对象
        if (!areAllChildrenIgnored()) {
            statement = withBeforeClasses(statement);         //对应@BeforeClass
            statement = withAfterClasses(statement);         //对应@AfterClass
            statement = withClassRules(statement);          //对应@ClassRule
        }
        return statement;                         //返回层层封装后的Statement类对象
}

 

//org.junit.runners.ParentRunner
private Statement withClassRules(Statement statement) {
        List<TestRule> classRules = classRules();
        return classRules.isEmpty() ? statement :
                new RunRules(statement, classRules, getDescription());
}

classRules()方法先获取所有@ClassRule规则,然后通过getOrderedRules()对所有@ClassRule规则进行排序。withClassRules()方法通过classRules()获取注解待测试类的所有排序后的@ClassRule规则,并将其作为RunRule类构造方法的入参进一步封装withAfterClasses()方法中返回的Statement实例。RunRule本身也是Statement的子类。

//org.junit.runners.ParentRunner
protected List<TestRule> classRules() {
        ClassRuleCollector collector = new ClassRuleCollector();
        testClass.collectAnnotatedMethodValues(null, ClassRule.class, TestRule.class, collector);
        testClass.collectAnnotatedFieldValues(null, ClassRule.class, TestRule.class, collector);
        return collector.getOrderedRules();
}
//org.junit.runners.ParentRunner
private static class ClassRuleCollector implements MemberValueConsumer<TestRule> {
        final List<RuleContainer.RuleEntry> entries = new ArrayList<RuleContainer.RuleEntry>();

        public void accept(FrameworkMember member, TestRule value) {
            ClassRule rule = member.getAnnotation(ClassRule.class);
            entries.add(new RuleContainer.RuleEntry(value, RuleContainer.RuleEntry.TYPE_TEST_RULE,
                    rule != null ? rule.order() : null));
        }

        public List<TestRule> getOrderedRules() {
            if (entries.isEmpty()) {
                return Collections.emptyList();
            }
            Collections.sort(entries, RuleContainer.ENTRY_COMPARATOR);
            List<TestRule> result = new ArrayList<TestRule>(entries.size());
            for (RuleContainer.RuleEntry entry : entries) {
                result.add((TestRule) entry.rule);
            }
            return result;
}
//org.junit.rules.RunRule
public class RunRules extends Statement {
    private final Statement statement;

public RunRules(Statement base, Iterable<TestRule> rules, Description description) {
        statement = applyAll(base, rules, description);
    }

    @Override
    public void evaluate() throws Throwable {
        statement.evaluate();
    }

    private static Statement applyAll(Statement result, Iterable<TestRule> rules,
            Description description) {
        for (TestRule each : rules) {
            result = each.apply(result, description);                  //各个Rule真正执行的地方
        }
        return result;
    }
}

JUnit Rule扩展示例

根据上述Rule特性的源码分析可知,Rule的扩展主要有三种方式:

  • 实现TestRule接口
  • 继承自内置Rule抽象类(Verifier,ExternalResource or TestWatcher)
  • 继承自内置Rule抽象类的子类(ErrorCollector,TemporaryFolder,TestName,ExpectedException,Timeout or RuleChain)

下面以第一种方式为例实现JUnit用例失败重试功能说明扩展Rule的一般用法,主要步骤如下:

  - 自定义Retry注解

  - 自定义Rule类实现TestRule接口

  - 在待测试类中使用自定义Rule

//自定义Retry注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Retry {

    int times();
}
//扩展Rule类
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class RetryRule implements TestRule{

    @Override
    public Statement apply(Statement statement, Description description) 
    {
        
        return new Statement() {

            @Override
            public void evaluate() throws Throwable 
            {
                Throwable retryThrowable = null;

                Retry retry = description.getAnnotation(Retry.class);
                
                if(retry != null) 
                {
                    
                    int times = retry.times();
                    for(int i=0; i<times; i++)
                    {
                        try
                        {
                            statement.evaluate();
                            return;
                        }
                        catch(final Throwable t)
                        {
                            retryThrowable = t;
                            System.err.println("Run method " + description.getMethodName() + ": failed for " + (i+1) + 
                      ((i+1) == 1 ? " time" : " times ")); } } System.err.println("Run method " + description.getMethodName() + " : exited after " + times + " attempts"); } else { statement.evaluate(); } } }; } }
//待测试类
import static org.junit.Assert.fail;

import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class MyRetryRuleTest 
{
    @Rule
    public RetryRule retryRule = new RetryRule();
    
    @Test
    public void testMethodA() throws Exception
    {
        System.out.println("test methodA...");
    }
    
    @Test
    @Retry(times=3)
    public void testMethodB() throws Exception
    {
        fail();
        System.out.println("test methodB...");
    }
    
    @Retry(times=5)
    @Test(timeout=10)
    public void testMethodC() throws Exception
    {
        Thread.sleep(10);
        System.out.println("test methodC...");
    }
}
//测试执行结果
test methodA...
Run method testMethodB: failed for 1 time
Run method testMethodB: failed for 2 times 
Run method testMethodB: failed for 3 times 
Run method testMethodB : exited after 3 attempts
test methodC...

 当然扩展Rule除了以上三种方式外,还有其他的间接方式实现同样的效果,因为本篇的主旨是自定义Rule,所以其他的扩展方式暂不涉及。

JUnit Rule总结

事实上Rule特性实现的功能也可以通过其他的扩展方式完成,这个需要根据项目平台及小组技能栈来确定哪种方式更灵活。多了解些测试工具内部的实现原理和执行流程可以让我们更全面地评估同类开发或测试工具各自的优劣势,在遇到不同类型问题的时候选择成本更低、效果更好的解决方案。当然,对于优秀的开源框架,吸收其经典的设计思想也是自建高效框架的必由之路。

10-15 20:03