深入JUnit源碼之Rule
JUnit中的Rule是對(duì)@BeforeClass、@AfterClass、@Before、@After等注解的另一種實(shí)現(xiàn),其中@ClassRule實(shí)現(xiàn)的功能和@BeforeClass、@AfterClass類似;@Rule實(shí)現(xiàn)的功能和@Before、@after類似。JUnit引入@ClassRule和@Rule注解的關(guān)鍵是想讓以前在@BeforeClass、@AfterClass、@Before、@After中的邏輯能更加方便的實(shí)現(xiàn)重用,因?yàn)?/span>@BeforeClass、@AfterClass、@Before、@After是將邏輯封裝在一個(gè)測(cè)試類的方法中的,如果實(shí)現(xiàn)重用,需要自己將這些邏輯提取到一個(gè)單獨(dú)的類中,再在這些方法中調(diào)用,而@ClassRule、@Rule則是將邏輯封裝在一個(gè)類中,當(dāng)需要使用時(shí),直接賦值即可,對(duì)不需要重用的邏輯則可用匿名類實(shí)現(xiàn),也因此,JUnit在接下來的版本中更傾向于多用@ClassRule和@Rule,雖然就我自己來說,感覺還是用@BeforeClass、@AfterClass、@Before、@After這些注解更加熟悉一些,也可能是我測(cè)試代碼寫的還不夠多的原因吧L。同時(shí)由于Statement鏈構(gòu)造的特殊性@ClassRule或@Rule也保證了類似父類@BeforeClass或@Before注解的方法要比子類的注解方法執(zhí)行早,而父類的@AfterClass或@After注解的方法執(zhí)行要比子類要早的特點(diǎn)。
@ClassRule、@Rule注解字段的驗(yàn)證
@ClassRule和@Rule只能注解在字段中,并且該字段的類型必須實(shí)現(xiàn)了TestRule接口,對(duì)@ClassRule注解的字段還必須是public,static,并且@ClassRule注解的字段在運(yùn)行時(shí)不可以拋異常,不然JUnit的行為是未定義的,這個(gè)是注釋文檔中這樣描述的,實(shí)際情況則一般是直接觸發(fā)testFailure事件,至于其他結(jié)果,則要看不同的TestRule實(shí)現(xiàn)不同,這個(gè)特征將在下面詳細(xì)講解;而對(duì)@Rule注解的字段必須是public,非static,關(guān)于@ClassRule注解字段和@Rule注解字段的驗(yàn)證是在RuleFieldValidator中做的(具體可以參考Runner小節(jié)):
2 CLASS_RULE_VALIDATOR(ClassRule.class, true), RULE_VALIDATOR(Rule.class, false);
3

4 public void validate(TestClass target, List<Throwable> errors) {
5 List<FrameworkField> fields= target.getAnnotatedFields(fAnnotation);
6 for (FrameworkField each : fields)
7 validateField(each, errors);
8 }
9 private void validateField(FrameworkField field, List<Throwable> errors) {
10 optionallyValidateStatic(field, errors);
11 validatePublic(field, errors);
12 validateTestRuleOrMethodRule(field, errors);
13 }
14 private void optionallyValidateStatic(FrameworkField field,
15 List<Throwable> errors) {
16 if (fOnlyStaticFields && !field.isStatic())
17 addError(errors, field, "must be static.");
18 }
19 private void validatePublic(FrameworkField field, List<Throwable> errors) {
20 if (!field.isPublic())
21 addError(errors, field, "must be public.");
22 }
23 private void validateTestRuleOrMethodRule(FrameworkField field,
24 List<Throwable> errors) {
25 if (!isMethodRule(field) && !isTestRule(field))
26 addError(errors, field, "must implement MethodRule or TestRule.");
27 }
28 private boolean isTestRule(FrameworkField target) {
29 return TestRule.class.isAssignableFrom(target.getType());
30 }
31 private boolean isMethodRule(FrameworkField target) {
32 return org.junit.rules.MethodRule.class.isAssignableFrom(target
33 .getType());
34 }
35 private void addError(List<Throwable> errors, FrameworkField field,
36 String suffix) {
37 String message= "The @" + fAnnotation.getSimpleName() + " '"
38 + field.getName() + "' " + suffix;
39 errors.add(new Exception(message));
40 }
41 }
JUnit默認(rèn)實(shí)現(xiàn)的TestRule
本節(jié)將重點(diǎn)介紹當(dāng)前JUnit默認(rèn)實(shí)現(xiàn)的幾個(gè)TestRule,先給出類圖,然后介紹源碼實(shí)現(xiàn)以及用途,最后還將簡(jiǎn)單的介紹RunRules這個(gè)Statement的運(yùn)行信息,雖然這個(gè)類非常簡(jiǎn)單,在Statement那節(jié)中也已經(jīng)簡(jiǎn)單的做過介紹了。
在學(xué)一個(gè)新的框架的時(shí)候,我一直比較喜歡先看一下框架的類圖,這樣自己總體上就有個(gè)概念了。這里也先給一張JUnit中TestRule的類圖吧:

TestRule的類結(jié)構(gòu)圖還是比較簡(jiǎn)單的,只是將它置于JUnit的Statement框架中,有些問題分析起來就比較復(fù)雜了。為了保持問題的簡(jiǎn)單,我們先來看一下每個(gè)單獨(dú)的類各自實(shí)現(xiàn)了什么功能和怎么實(shí)現(xiàn)吧。
TestWatcher和TestName
先來看兩個(gè)簡(jiǎn)單的吧,TestWatcher為子類提供了四個(gè)事件方法以監(jiān)控測(cè)試方法在運(yùn)行過程中的狀態(tài),一般它可以作為信息記錄使用。如果TestWatcher作為@ClassRule注解字段,則該測(cè)試類在運(yùn)行之前(調(diào)用所有的@BeforeClass注解方法之前)會(huì)調(diào)用starting()方法;當(dāng)所有@AfterClass注解方法調(diào)用結(jié)束后,succeeded()方法會(huì)被調(diào)用;若@AfterClass注解方法中出現(xiàn)異常,則failed()方法會(huì)被調(diào)用;最后,finished()方法會(huì)被調(diào)用;所有這些方法的Description是Runner對(duì)應(yīng)的Description。如果TestWatcher作為@Rule注解字段,則在每個(gè)測(cè)試方法運(yùn)行前(所有的@Before注解方法運(yùn)行前)會(huì)調(diào)用starting()方法;當(dāng)所有@After注解方法調(diào)用結(jié)束后,succeeded()方法會(huì)被調(diào)用;若@After注解方法中跑出異常,則failed()方法會(huì)被調(diào)用;最后,finished()方法會(huì)被調(diào)用;所有Description的實(shí)例是測(cè)試方法的Description實(shí)例。
TestName是對(duì)TestWatcher的一個(gè)簡(jiǎn)單實(shí)現(xiàn),它會(huì)在starting()方法中記錄每次運(yùn)行的名字。如果TestName作為@Rule注解字段,則starting()中傳入的Description是對(duì)每個(gè)測(cè)試方法的Description,因而getMethodName()方法返回的是測(cè)試方法的名字。一般TestName不作為@ClassRule注解字段,如果真有人這樣用了,則starting()中Description的參數(shù)是Runner的Description實(shí)例,一般getMethodName()返回值為null。
2 public Statement apply(final Statement base, final Description description) {
3 return new Statement() {
4 @Override
5 public void evaluate() throws Throwable {
6 starting(description);
7 try {
8 base.evaluate();
9 succeeded(description);
10 } catch (AssumptionViolatedException e) {
11 throw e;
12 } catch (Throwable t) {
13 failed(t, description);
14 throw t;
15 } finally {
16 finished(description);
17 }
18 }
19 };
20 }
21 protected void succeeded(Description description) {
22 }
23 protected void failed(Throwable e, Description description) {
24 }
25 protected void starting(Description description) {
26 }
27 protected void finished(Description description) {
28 }
29 }
30 public class TestName extends TestWatcher {
31 private String fName;
32 @Override
33 protected void starting(Description d) {
34 fName= d.getMethodName();
35 }
36 public String getMethodName() {
37 return fName;
38 }
39 }
ExternalResource與TemporaryFolder
ExternalResource為子類提供了兩個(gè)接口,分別是進(jìn)入測(cè)試之前和退出測(cè)試之后,一般它是作為對(duì)一些資源在測(cè)試前后的控制,如Socket的開啟與關(guān)閉、Connection的開始與斷開、臨時(shí)文件的創(chuàng)建與刪除等。如果ExternalResource用在@ClassRule注解字段中,before()方法會(huì)在所有@BeforeClass注解方法之前調(diào)用;after()方法會(huì)在所有@AfterClass注解方法之后調(diào)用,不管在執(zhí)行@AfterClass注解方法時(shí)是否拋出異常。如果ExternalResource用在@Rule注解字段中,before()方法會(huì)在所有@Before注解方法之前調(diào)用;after()方法會(huì)在所有@After注解方法之后調(diào)用。
TemporaryFolder是對(duì)ExternalResource的一個(gè)實(shí)現(xiàn),它在before()方法中在臨時(shí)文件夾中創(chuàng)建一個(gè)隨機(jī)的文件夾,以junit開頭;并在after()方法將創(chuàng)建的臨時(shí)文件夾清空,并刪除該臨時(shí)文件夾。另外TemporaryFolder還提供了幾個(gè)方法以在新創(chuàng)建的臨時(shí)文件夾中創(chuàng)建新的文件、文件夾。
2 public Statement apply(Statement base, Description description) {
3 return statement(base);
4 }
5 private Statement statement(final Statement base) {
6 return new Statement() {
7 @Override
8 public void evaluate() throws Throwable {
9 before();
10 try {
11 base.evaluate();
12 } finally {
13 after();
14 }
15 }
16 };
17 }
18 protected void before() throws Throwable {
19 }
20 protected void after() {
21 }
22 }
23 public class TemporaryFolder extends ExternalResource {
24 private File folder;
25 @Override
26 protected void before() throws Throwable {
27 create();
28 }
29 @Override
30 protected void after() {
31 delete();
32 }
33 public void create() throws IOException {
34 folder= newFolder();
35 }
36 public File newFile(String fileName) throws IOException {
37 File file= new File(getRoot(), fileName);
38 file.createNewFile();
39 return file;
40 }
41 public File newFile() throws IOException {
42 return File.createTempFile("junit", null, folder);
43 }
44 public File newFolder(String

45 File file = getRoot();
46 for (String folderName : folderNames) {
47 file = new File(file, folderName);
48 file.mkdir();
49 }
50 return file;
51 }
52 public File newFolder() throws IOException {
53 File createdFolder= File.createTempFile("junit", "", folder);
54 createdFolder.delete();
55 createdFolder.mkdir();
56 return createdFolder;
57 }
58 public File getRoot() {
59 if (folder == null) {
60 throw new IllegalStateException("the temporary folder has not yet been created");
61 }
62 return folder;
63 }
64 public void delete() {
65 recursiveDelete(folder);
66 }
67 private void recursiveDelete(File file) {
68 File[] files= file.listFiles();
69 if (files != null)
70 for (File each : files)
71 recursiveDelete(each);
72 file.delete();
73 }
74 }
Verifier和ErrorCollector
Verifier是在所有測(cè)試已經(jīng)結(jié)束的時(shí)候,再加入一些額外的邏輯,如果額外的邏輯通過,才表示測(cè)試成功,否則,測(cè)試依舊失敗,即使在之前的運(yùn)行中都是成功的。Verify可以為一些很多測(cè)試方法加入一些公共的驗(yàn)證邏輯。當(dāng)Verifier應(yīng)用在@Rule注解字段中,它在所偶@After注解方法運(yùn)行完后,會(huì)調(diào)用verify()方法,如果verifier()方法驗(yàn)證失敗拋出異常,則該測(cè)試方法的testFailure事件將會(huì)被觸發(fā),導(dǎo)致該測(cè)試方法失敗;當(dāng)Verifier應(yīng)用在@ClassRule時(shí),它在所有的@AfterClass注解的方法執(zhí)行完后,會(huì)執(zhí)行verify()方法,如果verify失敗拋出異常,將會(huì)觸發(fā)關(guān)于該測(cè)試類的testFailure,此時(shí)測(cè)試類中的所有測(cè)試方法都已經(jīng)運(yùn)行成功了,卻在最后收到一個(gè)關(guān)于測(cè)試類的testFailure事件,這確實(shí)是一個(gè)比較詭異的事情,因而@ClassRule中提到ErrorCollector(Verifier)不可以用在@ClassRule注解中,否則其行為為定義;更一般的@ClassRule注解的字段運(yùn)行時(shí)不能拋異常,不然其行為是未定義的。
ErrorCollector是對(duì)Verifier的一個(gè)實(shí)現(xiàn),它可以在運(yùn)行測(cè)試方法的過程中收集錯(cuò)誤信息,而這些錯(cuò)誤信息知道最后調(diào)用ErrorCollector的verify()方法時(shí)再處理。其實(shí)就目前來看,我很難想象這個(gè)需求存在的意義,因?yàn)榧词顾鼘⑺械腻e(cuò)誤信息收集在一起了,在事件發(fā)布是,它還是會(huì)為每個(gè)錯(cuò)誤發(fā)布一次testFailure事件(參考EachTestNotifier的實(shí)現(xiàn)),除非有一種需求是即使測(cè)試方法在運(yùn)行過程的某個(gè)點(diǎn)運(yùn)行出錯(cuò),也只是先記錄這個(gè)錯(cuò)誤,等到所有邏輯運(yùn)行結(jié)束后才去將這個(gè)測(cè)試方法運(yùn)行過程中存在的錯(cuò)誤發(fā)布出去,這樣一次運(yùn)行就可以知道測(cè)試代碼中存在出錯(cuò)的地方。ErrorCollector中還提供了幾個(gè)收集錯(cuò)誤的方法:如addError()、checkThat()、checkSucceeds()等。這里的checkThat()方法用到了hamcrest框架中的Matcher,這部分的內(nèi)容將在Assert小節(jié)中詳細(xì)介紹。
2 public Statement apply(final Statement base, Description description) {
3 return new Statement() {
4 @Override
5 public void evaluate() throws Throwable {
6 base.evaluate();
7 verify();
8 }
9 };
10 }
11 protected void verify() throws Throwable {
12 }
13 }
14 public class ErrorCollector extends Verifier {
15 private List<Throwable> errors= new ArrayList<Throwable>();
16 @Override
17 protected void verify() throws Throwable {
18 MultipleFailureException.assertEmpty(errors);
19 }
20 public void addError(Throwable error) {
21 errors.add(error);
22 }
23 public <T> void checkThat(final T value, final Matcher<T> matcher) {
24 checkThat("", value, matcher);
25 }
26 public <T> void checkThat(final String reason, final T value, final Matcher<T> matcher) {
27 checkSucceeds(new Callable<Object>() {
28 public Object call() throws Exception {
29 assertThat(reason, value, matcher);
30 return value;
31 }
32 });
33 }
34 public Object checkSucceeds(Callable<Object> callable) {
35 try {
36 return callable.call();
37 } catch (Throwable e) {
38 addError(e);
39 return null;
40 }
41 }
42 }
Timeout與ExpectedException
Timeout與ExpectedException都是對(duì)@Test注解中timeout和expected字段的部分替代實(shí)現(xiàn)。而且不同于@Test中的注解只適用于單個(gè)測(cè)試方法,這兩個(gè)實(shí)現(xiàn)適用于全局測(cè)試類。對(duì)Timeout來說,如果不是在測(cè)試類中所有的測(cè)試方法都需要有時(shí)間限制,我并不推薦適用Timeout;對(duì)ExpectedException,它使用了hamcrest中的Matcher來匹配,因而提供了更強(qiáng)大的控制能力,但是一般的使用,感覺@Test中的expected字段就夠了,它多次調(diào)用expected表達(dá)是and的關(guān)系,即如果我有兩個(gè)Exception,則拋出的Exception必須同時(shí)是這兩個(gè)類型的,感覺沒有什么大的意義,因而我不怎么推薦使用這個(gè)Rule,關(guān)于hamcrest的Mather框架將在Assert小節(jié)中詳細(xì)介紹。這兩個(gè)Rule原本就是基于測(cè)試方法設(shè)計(jì)的,因而如果應(yīng)用在@ClassRule上好像沒有什么大的意義,不過Timeout感覺是可以應(yīng)用在@ClassRule中的,如果要測(cè)試一個(gè)測(cè)試類整體運(yùn)行時(shí)間的話,當(dāng)然如果存在這種需求的話。
2 private final int fMillis;
3 public Timeout(int millis) {
4 fMillis= millis;
5 }
6 public Statement apply(Statement base, Description description) {
7 return new FailOnTimeout(base, fMillis);
8 }
9 }
10 public class ExpectedException implements TestRule {
11 public static ExpectedException none() {
12 return new ExpectedException();
13 }
14 private Matcher<Object> fMatcher= null;
15 private ExpectedException() {
16 }
17 public Statement apply(Statement base,
18 org.junit.runner.Description description) {
19 return new ExpectedExceptionStatement(base);
20 }
21 public void expect(Matcher<?> matcher) {
22 if (fMatcher == null)
23 fMatcher= (Matcher<Object>) matcher;
24 else
25 fMatcher= both(fMatcher).and(matcher);
26 }
27 public void expect(Class<? extends Throwable> type) {
28 expect(instanceOf(type));
29 }
30 public void expectMessage(String substring) {
31 expectMessage(containsString(substring));
32 }
33 public void expectMessage(Matcher<String> matcher) {
34 expect(hasMessage(matcher));
35 }
36 private class ExpectedExceptionStatement extends Statement {
37 private final Statement fNext;
38 public ExpectedExceptionStatement(Statement base) {
39 fNext= base;
40 }
41 @Override
42 public void evaluate() throws Throwable {
43 try {
44 fNext.evaluate();
45 } catch (Throwable e) {
46 if (fMatcher == null)
47 throw e;
48 Assert.assertThat(e, fMatcher);
49 return;
50 }
51 if (fMatcher != null)
52 throw new AssertionError("Expected test to throw "
53 + StringDescription.toString(fMatcher));
54 }
55 }
56 private Matcher<Throwable> hasMessage(final Matcher<String> matcher) {
57 return new TypeSafeMatcher<Throwable>() {
58 public void describeTo(Description description) {
59 description.appendText("exception with message ");
60 description.appendDescriptionOf(matcher);
61 }
62 @Override
63 public boolean matchesSafely(Throwable item) {
64 return matcher.matches(item.getMessage());
65 }
66 };
67 }
68 }
RuleChain
RuleChain提供一種將多個(gè)TestRule串在一起執(zhí)行的機(jī)制,它首先從outChain()方法開始創(chuàng)建一個(gè)最外層的TestRule創(chuàng)建的Statement,而后調(diào)用round()方法,不斷向內(nèi)層添加TestRule創(chuàng)建的Statement。如其注釋文檔中給出的一個(gè)例子:
2 public TestRule chain= RuleChain
3 .outerRule(new LoggingRule("outer rule"))
4 .around(new LoggingRule("middle rule"))
5 .around(new LoggingRule("inner rule"));
如果LoggingRule只是類似ExternalResource中的實(shí)現(xiàn),并且在before()方法中打印starting…,在after()方法中打印finished…,那么這條鏈的執(zhí)行結(jié)果為:
starting middle rule
starting inner rule
finished inner rule
finished middle rule
finished outer rule
由于TestRule的apply()方法是根據(jù)的當(dāng)前傳入的Statement,創(chuàng)建一個(gè)新的Statement,以決定當(dāng)前TestRule邏輯的執(zhí)行位置,因而第一個(gè)調(diào)用apply()的TestRule產(chǎn)生的Statement將在Statement鏈的最里面,也正是有這樣的邏輯,所以around()方法實(shí)現(xiàn)的時(shí)候,都是把新加入的TestRule放在第一個(gè)位置,然后才保持其他已存在的TestRule位置不變。
2 private static final RuleChain EMPTY_CHAIN= new RuleChain(
3 Collections.<TestRule> emptyList());
4 private List<TestRule> rulesStartingWithInnerMost;
5 public static RuleChain emptyRuleChain() {
6 return EMPTY_CHAIN;
7 }
8 public static RuleChain outerRule(TestRule outerRule) {
9 return emptyRuleChain().around(outerRule);
10 }
11 private RuleChain(List<TestRule> rules) {
12 this.rulesStartingWithInnerMost= rules;
13 }
14 public RuleChain around(TestRule enclosedRule) {
15 List<TestRule> rulesOfNewChain= new ArrayList<TestRule>();
16 rulesOfNewChain.add(enclosedRule);
17 rulesOfNewChain.addAll(rulesStartingWithInnerMost);
18 return new RuleChain(rulesOfNewChain);
19 }
20 public Statement apply(Statement base, Description description) {
21 for (TestRule each : rulesStartingWithInnerMost)
22 base= each.apply(base, description);
23 return base;
24 }
25 }
TestRule在Statement的運(yùn)行
TestRule實(shí)例的運(yùn)行都是被封裝在一個(gè)叫RunRules的Statement中運(yùn)行的。在構(gòu)造RunRules實(shí)例是,傳入TestRule實(shí)例的集合,然后遍歷所有的TestRule實(shí)例,為每個(gè)TestRule實(shí)例調(diào)用一遍apply()方法以構(gòu)造出要執(zhí)行TestRule的Statement鏈。類似上小節(jié)的RuleChain,這里在前面的TestRule構(gòu)造的Statement被是最終構(gòu)造出的Statement的最里層,結(jié)合TestClass在獲取注解字段的順序時(shí),先查找子類,再查找父類,因而子類的TestRule實(shí)例產(chǎn)生的Statement是在Statement鏈的最里層,從而保證了類似ExternalResource實(shí)現(xiàn)中,before()方法的執(zhí)行父類要比子類要早,而after()方法的執(zhí)行子類要比父類要早的特性。
2 private final Statement statement;
3 public RunRules(Statement base, Iterable<TestRule> rules, Description description) {
4 statement= applyAll(base, rules, description);
5 }
6 @Override
7 public void evaluate() throws Throwable {
8 statement.evaluate();
9 }
10 private static Statement applyAll(Statement result, Iterable<TestRule> rules,
11 Description description) {
12 for (TestRule each : rules)
13 result= each.apply(result, description);
14 return result;
15 }
16 }