JUnit Rule 原理分析

为了加深对JUnit Rule的理解,将其拆分出来单独作为一篇文章讲述.

JUnit Rule原理分析

在写自定义Rule之前先对之前说到的系统实现的Rule做一个简单的原理分析,这样更能加深我们对自定义Rule的理解.强烈建议配合源码查看, 否则可能不知所云.

JUnit4的默认TestRunner 为org.junit.runners.BlockJUnit4ClassRunner,其中有一个methodBlock方法,该方法是运行测试的核心方法:

protected Statement methodBlock(FrameworkMethod method) {
    Object test;
    try {
        test = (new ReflectiveCallable() {
            protected Object runReflectiveCall() throws Throwable {
                return BlockJUnit4ClassRunner.this.createTest();
            }
        }).run();
    } catch (Throwable var4) {
        return new Fail(var4);
    }

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

Note : 解释一下, Runner只是一个抽象类,表示用于运行Junit测试用例的工具,通过它可以运行测试并通知给Notifier运行的结果。

在JUnit执行每个测试方法之前,methodBlock方法都会被调用,它用来把这个测试包装成一个Statement。Statement表示一个具体的动作,例如测试方法的执行,Before方法的执行和Rule的调用。它使用责任链模式,将Statement层层包裹,就能形成一个完整的测试,JUnit最后执行这个Statement。从上面的代码中可以看到,以下内容被包装进了Statement中:

  1. 测试方法的执行;
  2. 异常测试,对应@Test(expected=XXX.class);
  3. 超时测试,对应@Test(timeout=XXX);
  4. Before方法,对应@Before注解的方法;
  5. After方法,对应@After注解的方法;
  6. Rule的执行

在Statement中使用evaluate方法控制Statement执行的先后顺序,比如Before方法对应的Statement的RunBefores:

public class RunBefores extends Statement {
    private final Statement next;

    private final Object target;

    private final List befores;

    public RunBefores(Statement next, List befores, Object target) {
        this.next = next;
        this.befores = befores;
        this.target = target;
    }

    @Override
    public void evaluate() throws Throwable {
        for (FrameworkMethod before : befores) {
            before.invokeExplosively(target);
        }
        next.evaluate();
    }
}

在evaluate中,所有的Before方法会被最先调用,因为Before方法必须要在测试执行之前调用,然后再执行next的evaluate去调用下一个Statement。

同理RunAfter是在执行测试之后再去执行After方法的内容。

public class RunAfters extends Statement {
    private final Statement next;

    private final Object target;

    private final List afters;

    public RunAfters(Statement next, List afters, Object target) {
        this.next = next;
        this.afters = afters;
        this.target = target;
    }

    @Override
    public void evaluate() throws Throwable {
        List errors = new ArrayList();
        try {
            next.evaluate();
        } catch (Throwable e) {
            errors.add(e);
        } finally {
            for (FrameworkMethod each : afters) {
                try {
                    each.invokeExplosively(target);
                } catch (Throwable e) {
                    errors.add(e);
                }
            }
        }
        MultipleFailureException.assertEmpty(errors);
    }
}

在理解上面的Statement之后,再回过头看Rule的接口org.junit.rules.TestRule:

public interface TestRule {
    /**
     * Modifies the method-running {@link Statement} to implement this
     * test-running rule.
     *
     * @param base The {@link Statement} to be modified
     * @param description A {@link Description} of the test implemented in {@code base}
     * @return a new statement, which may be the same as {@code base},
     *         a wrapper around {@code base}, or a completely new Statement.
     */
    Statement apply(Statement base, Description description);
}

内部只有一个applay方法,用于包裹上一级的Statement并返回一个新的Statement。所以如果实现一个Rule主要就是实现一个Statement。

自定义Rule

通过上面分析我们就可以知道如何实现一个Rule,我们举个例子:


举个栗子

下面这个例子是可以根据自己输入的Count来决定测试方法循环执行,并在每次执行前和执行后做了相应的打印。

package com.lulu.androidtestdemo.junit.rule;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

/**
 * Created by zhanglulu on 2018/1/24.
 */
public class LoopRule implements TestRule {
    private Statement base;
    private Description description;
    private int loopCount;

    public LoopRule(int loopCount) {
        this.loopCount = loopCount;
    }

    @Override
    public Statement apply(Statement base, Description description) {
        this.base = base;
        this.description = description;
        System.out.println("apply");
        return new LoopStatement(base);
    }

    public class LoopStatement extends Statement {
        private final Statement base;
        public LoopStatement(Statement base) {
            this.base = base;
        }
        @Override
        public void evaluate() throws Throwable {
            for (int i = 0; i < loopCount; i++) {
                System.out.println("Loop " + i + " Started");
                base.evaluate();
                System.out.println("Loop " + i + " Finished");
            }
        }
    }
}

测试我们的LoopRule

public class TestLoopRule {
    @Rule
    public LoopRule customRule = new LoopRule(3);

    @Test
    public void testMyCustomRule() {
        System.out.println("execute testMyCustomRule");
    }
}

执行结果:

执行结果

轻松搞定 ()

敲行代码再睡觉

你可能感兴趣的:(JUnit Rule 原理分析)