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<FrameworkMethod> befores;

    public RunBefores(Statement next, List<FrameworkMethod> 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<FrameworkMethod> afters;

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

    @Override
    public void evaluate() throws Throwable {
        List<Throwable> errors = new ArrayList<Throwable>();
        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");
    }
}

执行结果:

执行结果

轻松搞定 ()

敲行代码再睡觉
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容