TestRule是一个工厂方法模式中的Creator角色——声明工厂方法。
package org.junit.rules; import org.junit.runner.Description; import org.junit.runners.model.Statement; public interface TestRule { Statement apply(Statement base, Description description); }
由于工厂方法apply有参数base,因而TestRule将创建装饰模式中的装饰对象。
【抽象类Statement声明操作evaluate()的接口,它作为一个回调接口,上层模块可以定义各种Statement的子类,提供evaluate()的方法体。而这一基本的技术与Rule结合,成为JUnit一个非常重要的手段——可以说它是一个通用型的复合命令的构造方式,完全可以取代Statement的一些复合命令的子类如ExpectException等。】
测试程序员要使用Rule,必须编写代码。下面先介绍使用Rule的例子。1.MyStatement——Statement的一个新的装饰对象。( JUnit之Rule的使用中的例子),地位等同于ExpectException。
package rule; import static tool.Print.*;//pln(Object) import org.junit.runners.model.Statement; /** * * @author yqj2065 */ public class MyStatement extends Statement { private final Statement base; public MyStatement( Statement base ) { this.base = base; } @Override public void evaluate() throws Throwable { pln( "before...sth..sth" ); try { base.evaluate(); } finally { pln( "after...sth..sth" ); } } }2.一个具体的工厂,也就是定义工厂方法的 TestRule的子类。MyRule将创建一个Statement的 新的装饰对象MyStatement。如同工厂方法模式的一般用法,具体工厂与具体产品一一对应。
package rule; import org.junit.rules.TestRule; import org.junit.runners.model.Statement; import org.junit.runner.Description; public class MyRule implements TestRule { @Override public Statement apply(Statement base, Description description) { return new MyStatement( base ); } }3.使用新的装饰对象
用户类BlockJUnit4ClassRunner使用的是ExpectException等JUnit内建的Statement装饰对象;测试程序员编写的Statement装饰对象,则需要通过@Rule,嵌入到单元测试类中。
package rule; import org.junit.Rule; import org.junit.Test; public class MyTest { @Rule public MyRule myRule = new MyRule(); @Test() public void xx() { System.out.println( "xx()..." ); } }在开发环境中运行MyTest,输出:
after...sth..sth
例程 8 17标注Rule package org.junit; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface Rule {}注意:JUnit4.8中Rule标注并没有指定@Target(ElementType.FIELD),而4.10版本中明确定义出来。Rule用于修饰测试单元类中一个特殊的域, 该域必须是public、非static而且是org.junit.rules.TestRule子类型的引用变量(取代4.8版本的org.junit.rules.MethodRule)。JUnit4.10版本中另一个标注 @ClassRule,用于修饰测试单元类中static的TestRule子类型的引用变量。
然而,装饰对象仍然是一个Statement。TestRule的各种实现类创建许多装饰对象,但是没有特别的名字,反正是一个Statement。
从JUnit框架设计的角度看,TestRule是工厂方法;而从测试程序员思考问题的角度,TestRule及其实现类,为单元测试制订了各种附加的规则/Rule。在org.junit.rules包中的TestRule的实现类有:
例程 8-4 TestWatchman package org.junit.rules; import org.junit.internal.AssumptionViolatedException; import org.junit.runner.Description; import org.junit.runners.model.Statement; public abstract class TestWatcher implements TestRule { public Statement apply(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { starting(description); try { base.evaluate(); succeeded(description); } catch (AssumptionViolatedException e) { throw e; } catch (Throwable t) { failed(t, description); throw t; } finally { finished(description); } } }; } protected void succeeded(Description description) { } protected void failed(Throwable e, Description description) { } protected void starting(Description description) { } protected void finished(Description description) { } }
TestWatcher的子类TestName是模板方法模式中的具体类角色。
publicvoid starting(FrameworkMethod method) {
fName=method.getName();
}使用了模板方法模式,以匿名类的方式创建Statement装饰对象,在base. evaluate()之后添加一个verify()操作。Verifier的子类ErrorCollector收集测试中的错误。
ExpectedException作为工厂创建装饰对象,其私有内部类ExpectedExceptionStatement完全等价于org.junit.internal.runners.statements.ExpectException的地位。但是,ExpectedException作为规则将作用于各种测试方法,而ExpectException作用于@Test(expected=xxx)修饰的方法。
Timeout则是典型的工厂,返回一个 装饰对象FailOnTimeout。package org.junit.rules; import org.junit.internal.runners.statements.FailOnTimeout; import org.junit.runner.Description; import org.junit.runners.model.Statement; public class Timeout implements TestRule { private final int fMillis; /** * @param millis the millisecond timeout */ public Timeout(int millis) { fMillis= millis; } public Statement apply(Statement base, Description description) { return new FailOnTimeout(base, fMillis); } }