探索JUnit4擴(kuò)展:深入Rule
本文是"探索JUnit4擴(kuò)展"系列中的第三篇,將進(jìn)一步探究Rule的應(yīng)用,展示如何使用Rule來替代@BeforeClass,@AfterClass,@Before和@After的功能。(2012.01.04最后更新)在本系列的第二篇《探索JUnit4擴(kuò)展:應(yīng)用Rule》中提到,可以使用Rule替代現(xiàn)有的大部分Runner擴(kuò)展,而且也不提倡對(duì)Runner中的withBefores(),withAfters()等方法進(jìn)行擴(kuò)展。本文將介紹如何使用Rule去實(shí)現(xiàn)@Before,@After和@BeforeClass的相同功能。
1. BaseRule
首先要?jiǎng)?chuàng)建一個(gè)較通用的TestRule實(shí)現(xiàn)BaseRule,它會(huì)釋放出兩個(gè)擴(kuò)展點(diǎn),一個(gè)在執(zhí)行測(cè)試方法之前,before();另一個(gè)在執(zhí)行測(cè)試方法之后after()。下面是該類的代碼,
public abstract class BaseRule implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
return new RuleStatement(base, description);
}
private class RuleStatement extends Statement {
private Statement base = null;
private Description description = null;
private RuleStatement(Statement base, Description description) {
this.base = base;
this.description = description;
}
@Override
public void evaluate() throws Throwable {
before(base, description);
try {
base.evaluate();
} finally {
after(base, description);
}
}
}
protected void before(Statement base, Description description) throws Throwable {
}
protected void after(Statement base, Description description) {
}
}
如果對(duì)JUnit4的源代碼略有認(rèn)知,可能會(huì)發(fā)現(xiàn)BaseRule與JUnit4提供的TestRule實(shí)現(xiàn)ExternalResource代碼相似。關(guān)鍵的不同之處是,BaseRule中的before()與after()方法都提供了Statement與Description類型的參數(shù),這使得它能夠完成更復(fù)雜的工作。@Override
public Statement apply(Statement base, Description description) {
return new RuleStatement(base, description);
}
private class RuleStatement extends Statement {
private Statement base = null;
private Description description = null;
private RuleStatement(Statement base, Description description) {
this.base = base;
this.description = description;
}
@Override
public void evaluate() throws Throwable {
before(base, description);
try {
base.evaluate();
} finally {
after(base, description);
}
}
}
protected void before(Statement base, Description description) throws Throwable {
}
protected void after(Statement base, Description description) {
}
}
2. CalculatorTest
本文使用的CalculatorTest將不使用@BeforeClass,@Before和@After,而會(huì)創(chuàng)建兩個(gè)BaseRule的實(shí)例:一個(gè)用于替代@BeforeClass和@AfterClass(本系列目前還未使用過@AfterClass),另一個(gè)則替代@Before和@After。
public class CalculatorTest {
private static final DateFormat format = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss_SSS");
private static Calculator calculator = null;
@ClassRule
public static BaseRule classRule = new BaseRule() {
protected void before(Statement base, Description description) throws Throwable {
calculator = new Calculator();
};
};
@Rule
public BaseRule rule = new BaseRule() {
protected void before(Statement base, Description description) throws Throwable {
printBeforeLog(description);
};
protected void after(Statement base, Description description) {
printAfterLog(description);
};
private void printBeforeLog(Description description) {
TestLogger testLogger = description.getAnnotation(TestLogger.class);
if (testLogger != null) {
StringBuilder log = new StringBuilder(format.format(new Date()));
log.append(" ").append(description.getClassName()).append("#")
.append(description.getMethodName()).append(": ")
.append(testLogger.log());
System.out.println(log.toString());
}
}
private void printAfterLog(Description description) {
StringBuilder log = new StringBuilder(format.format(new Date()));
log.append(" ").append(description.getClassName()).append("#")
.append(description.getMethodName()).append(" end
");
System.out.println(log.toString());
}
};
@Test
@TestLogger(log = "a simple division")
public void simpleDivide() {
int value = calculator.divide(8, 2);
Assert.assertTrue(value == 4);
}
@Test(expected = ArithmeticException.class)
@TestLogger(log = "divided by zero, and an ArithmeticException thrown.")
public void dividedByZero() {
calculator.divide(8, 0);
}
}
值得注意的是,classRule是靜態(tài)變量,它使用@ClassRule Annotation,將替代@BeforeClass和@AfterClass;而rule是成員變量,它使用@Rule Annotation,將替代@Before和@After。與之前文章不同的是,此處不僅會(huì)在執(zhí)行測(cè)試方法之前打印指定內(nèi)容的日志(printBeforeLog()),還會(huì)在執(zhí)行測(cè)試方法之后打印一條固定格式的日志(printAfterLog()),用于指示該測(cè)試方法已經(jīng)執(zhí)行完畢了。private static final DateFormat format = new SimpleDateFormat("yyyy-MM-dd_HH:mm:ss_SSS");
private static Calculator calculator = null;
@ClassRule
public static BaseRule classRule = new BaseRule() {
protected void before(Statement base, Description description) throws Throwable {
calculator = new Calculator();
};
};
@Rule
public BaseRule rule = new BaseRule() {
protected void before(Statement base, Description description) throws Throwable {
printBeforeLog(description);
};
protected void after(Statement base, Description description) {
printAfterLog(description);
};
private void printBeforeLog(Description description) {
TestLogger testLogger = description.getAnnotation(TestLogger.class);
if (testLogger != null) {
StringBuilder log = new StringBuilder(format.format(new Date()));
log.append(" ").append(description.getClassName()).append("#")
.append(description.getMethodName()).append(": ")
.append(testLogger.log());
System.out.println(log.toString());
}
}
private void printAfterLog(Description description) {
StringBuilder log = new StringBuilder(format.format(new Date()));
log.append(" ").append(description.getClassName()).append("#")
.append(description.getMethodName()).append(" end

System.out.println(log.toString());
}
};
@Test
@TestLogger(log = "a simple division")
public void simpleDivide() {
int value = calculator.divide(8, 2);
Assert.assertTrue(value == 4);
}
@Test(expected = ArithmeticException.class)
@TestLogger(log = "divided by zero, and an ArithmeticException thrown.")
public void dividedByZero() {
calculator.divide(8, 0);
}
}
3. 小結(jié)
使用Rule可以替代絕大部分的Runner擴(kuò)展,而且特定的Rule實(shí)現(xiàn)可以被復(fù)用,也易于添加或移除Rule實(shí)例,這些都大大地提高了靈活性。值得注意地是,本文雖然使用Rule代替了@BeforeClass,@AfterClass,@Before和@After的功能,但并不意味著就應(yīng)當(dāng)這么做。就我個(gè)人所想,將傳統(tǒng)的Fixture功能交由@BeforeClass,@AfterClass,@Before和@After實(shí)現(xiàn),仍然是一種不錯(cuò)的選擇。
posted on 2012-01-04 00:13 John Jiang 閱讀(2364) 評(píng)論(3) 編輯 收藏 所屬分類: Java 、UnitTest 、JUnit 、原創(chuàng)