【JUnit4.10源代码分析】5 Statement

如果要评选JUnit中最最重要的类型,或者说核心,无疑是org.junit.runners.model.Statement。Runner等类型看起来热闹而已。

package org.junit.runners.model;
/**
 * Represents one or more actions to be taken at runtime in the course
 * of running a JUnit test suite.
 */
public abstract class Statement {
	/**
	 * Run the action, throwing a {@code Throwable} if anything goes wrong.
	 */
	public abstract void evaluate() throws Throwable;
}
Statement是 命令模式中的Command、装饰模式中的Component。它是 Rule发挥作用的关键。

写完JUnit4.8.2源代码分析-5.1 Statement之复合命令之后,yqj2065就对JUnit的源代码的研读缺乏激情鸟。JUnit4.10的源代码在此BlockJUnit4ClassRunner没有明显的变化。

读完最精致的设计,其他还有什么好玩的呢?可能就是并行。但是并行是一种Java技术,和反射、注解、泛型一样的技术问题。不是精致的设计。准备花点时间好好写点Statement的东西补充本文,先挖坑。JUnit4.8.2源代码分析-5Statement JUnitRule的使用先凑活着看。

命令模式的Command角色

抽象类Statement声明操作evaluate()的接口,而由其子类——具体命令或复合命令负责绑定命令的Recelver/接收者或命令的执行者。

Statement/语句是对运行JUnit测试组过程中的一个或多个动作的封装。如果说Runner.run()表示运行JUnit测试组的整个过程,则Statement表示其中或大或小的步骤。针对方法的标注如@Test 、@Before、@After、@BeforeClass、@AfterClass和各种测试场景,Statement的子类封装一个或多个动作。


最基础的具体命令有Fail和InvokeMethod。

Fail表示JUnit测试过程中遇到了问题而失败,因而其evaluate()简单地抛出一个异常。源代码如下:

package org.junit.internal.runners.statements;
import org.junit.runners.model.Statement;
public class Fail extends Statement {
	private final Throwable fError;
	public Fail(Throwable e) {		fError= e;	}

	@Override	public void evaluate() throws Throwable {
		throw fError;
	}
}

InvokeMethod表示运行一个@Test方法的命令,这是JUnit测试的最小单元。

package org.junit.internal.runners.statements;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;

public class InvokeMethod extends Statement {
	private final FrameworkMethod fTestMethod;
	private Object fTarget;	
	public InvokeMethod(FrameworkMethod testMethod, Object target) {
		fTestMethod= testMethod;
		fTarget= target;
	}
	
	@Override
	public void evaluate() throws Throwable {
		fTestMethod.invokeExplosively(fTarget);
	}
}
InvokeMethod提供 构造器注入的方式,获得测试单元类的对象target和FrameworkMethod。命令的执行者为FrameworkMethod对象,参数是由外界注入的Object target。
这两个例子,说明了命令模式中选择命令的执行者的极大自由。

其他的子类,属于复合命令

BlockJUnit4ClassRunner实际上是命令模式中的Client,直接调用具体的命令。按照JUnit4.8.2源代码分析-5Statement所言,将BlockJUnit4ClassRunner相关代码放在MethodBlock中后,BlockJUnit4ClassRunner中只需要把 methodBlock(method).evaluate();

修改为 new MethodBlock(method,test).evaluate();


抽象类Statement声明操作evaluate()的接口,它作为一个回调接口,上层模块可以定义各种Statement的子类,提供evaluate()的方法体。而这一基本的技术与Rule结合,成为JUnit一个非常重要的手段——可以说它是一个通用型的复合命令的构造方式,完全可以取代Statement的一些复合命令的子类如ExpectException等。复合命令子类如ExpectException由JUnit框架的设计者提供,而使用Rule,则将复合命令子类的构造任务交给测试程序员。所以,一般的经常使用的复合命令,还是应该作为默认实现在框架中存在


装饰模式中的Component角色

复合命令是对基本命令的拓展装饰模式的目的就是给一个对象增添一些其他的功能。简单而典型的ExpectException和FailOnTimeout,针对@Test注解中的参数,例如@Test(expected=IndexOutOfBoundsException.class)或@Test(timeout=100)。以ExpectException为例(源代码中的Ststement fNext,我改成了base

package org.junit.internal.runners.statements;
import org.junit.internal.AssumptionViolatedException;//意料之中的异常
import org.junit.runners.model.Statement;

public class ExpectException extends Statement {
	private Statement base;
	private final Class<? extends Throwable> fExpected;
	
	public ExpectException(Statement base, Class<? extends Throwable> expected) {
		this.base= base;
		fExpected= expected;
	}
	
	@Override
	public void evaluate() throws Exception {
		boolean complete = false;
		try {
			base.evaluate();
			complete = true;
		} catch (AssumptionViolatedException e) {
			throw e;
		} catch (Throwable e) {
			if (!fExpected.isAssignableFrom(e.getClass())) {
				String message= "Unexpected exception, expected<"
							+ fExpected.getName() + "> but was<"
							+ e.getClass().getName() + ">";
				throw new Exception(message, e);
			}
		}
		if (complete)
			throw new AssertionError("Expected exception: "
					+ fExpected.getName());
	}
}
ExpectException依赖注入Statement对象——通常为@Test注解的方法的Statement,和预期的异常expected。ExpectException在base的基础上增添了对预期的异常的处理。

base.evaluate(),如果抛出了意料之中的异常,转手抛出;如果不是意料之中的异常,说明这是一个"Unexpected exception,……";如果顺利执行没有异常,则抛出断言错误——我预期的异常在哪里?

在BlockJUnit4ClassRunner中使用代码对@Test(expected=xxx)进行的处理:

package org.junit.runners;
public class MethodBlock extends Statement{//从BlockJUnit4ClassRunner中提取出来    
    @Deprecated
    protected Statement possiblyExpectingExceptions(FrameworkMethod method,
            Object test, Statement next) {
        Test annotation= method.getAnnotation(Test.class);
        return expectsException(annotation) ? new ExpectException(next,
                getExpectedException(annotation)) : next;
    }
    private Class<? extends Throwable> getExpectedException(Test annotation) {
        if (annotation == null || annotation.expected() == None.class)
            return null;
        else
            return annotation.expected();
    }

    private boolean expectsException(Test annotation) {
        return getExpectedException(annotation) != null;
    }
}

由于使用了反射机制,装饰对象的创建与常见的方式Person b = new T2(newT2(new T1(newLawyer())))不同,需要代码处理:

new ExpectException(next, getExpectedException(annotation)

注意,如果@Test没有指定(expected=xxx),possiblyExpectingExceptions()返回一个基本的Statement。


由于使用了反射机制,装饰模式中用户类BlockJUnit4ClassRunner需要某个Builder(在JUnit中仅仅是一个方法)创建装饰对象ExpectException。

而这就是TestRule的作用,一个工厂方法模式中的Creator角色。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值