JUnit Rule簡述
Rule是JUnit 4.7之后新加入的特性,有點類似於攔截器,可以在測試類或測試方法執行前后添加額外的處理,本質上是對@BeforeClass, @AfterClass, @Before, @After等的另一種實現,只是功能上更靈活多變,易於擴展,且方便在類和項目之間共享。
JUnit的Rule特性提供了兩個注解@Rule和@RuleClass,大體上說@Rule可以與@Before及@After對應,@ClassRule可以與@BeforeClass及@AfterClass對應。自JUnit4.10起可以使用TestRule接口代替此前一直在用的MethodRule接口,實際項目中可以通過實現TestRule或繼承自JUnit內置Rule類進行擴展。
- 適用場景
在簡述中已經提到Rule特性本身也是對@BeforeClass, @AfterClass, @Before, @After功能的另外實現,所以基本上這四種注解的使用場景都適用於Rule,同時JUnit內置的Rule類還能夠提供這四種注解未提供的功能。總體上說Rule特性的適用場景包括但不限於如下需求:
- 在測試類或測試方法執行前后添加初始化或環境清理操作
- 在測試執行過程中收集錯誤信息且無需中斷測試
- 在測試結束后添加額外的測試結果校驗功能
- 在測試執行前后創建及刪除測試執行過程中產生的臨時文件或目錄
- 對測試過程中產生的異常進行靈活校驗
- 將多個Rules串接在一起執行
- 測試用例執行失敗時重試指定次數
從使用習慣上來說,對於簡單項目,@BeforeClass, @AfterClass, @Before, @After等注解已經能夠滿足測試需求;對於復雜點的項目,從易擴展、易維護和方便復用的角度考慮最好使用Rule特性,方便添加和移除Rule實例,靈活性大大提高。
- 注解分類
JUnit中通過兩個注解@Rule和@ClassRule來實現Rule擴展,這兩個注解使用時需要放在實現了TestRule接口的Rule變量或返回Rule的方法之上,且修飾符都必須為public。
二者具體區別如下:
- 被注解的變量或方法類型不同
- @Rule修飾的變量或方法的修飾符必須為public,非static
- @ClassRule修飾的變量或方法的修飾符必須為public static
- 注解的級別不同
- @Rule為變量或方法級注解,每個測試方法執行時都會調用被該注解修飾的Rule
- @ClassRule為類級注解,執行單個測試類時只會調用一次被該注解修飾的Rule
- 注解的對象限制不同
- @Rule無注解對象限制
- @ClassRule不能注解ErrorCollector(Verifier)
- TestRule接口
TestRule是測試類或測試方法執行過程及報告信息的接口,可以在TestRule中添加初始化及環境清理的操作、監控測試執行的日志打印或UI截圖操作、測試結果成功或失敗校驗操作等。TestRule僅定義了唯一的方法apply(),所以可以在TestRule實現類的apply()方法中加入測試項目需要的操作。
public interface TestRule {
//在實現類的apply()中加入測試需要的操作,本質上是對Statement實例base的進一步封裝 Statement apply(Statement base, Description description); }
JUnit內置Rule
除了Rule特性外,JUnit還新增了一些核心Rule,均實現了TestRule接口,包括Verifier抽象類,ErrorCollector實現類,ExternalResource抽象類,TemporaryFolder實現類,TestWatcher抽象類,TestName實現類,ExpectedException實現類,Timeout實現類及RuleChain實現類(deprecated)。各接口實現類及類圖參考如下:
- Verifier:所有測試結束后對測試執行結果添加額外的邏輯驗證測試最終成功與否。該抽象類為子類提供一個接口方法verify()供擴展
- ErrorCollector:是Verifier類的一個子類實現,用於在測試執行過程中收集錯誤信息,不會中斷測試,最后調用verify()方法處理
- ExternalResource:外部資源管理。該抽象類為子類提供了兩個接口方法before()和after(),可以根據項目實際需要覆寫擴展
- TemporaryFolder:是抽象類ExternalResource的一個子類實現,用於在JUnit測試執行前后,創建和刪除臨時目錄
- TestWatcher:監視測試方法生命周期的各個階段。該抽象類為子類提供了五個接口方法succeeded(), failed(), skipped(), starting()及finished()供擴展
- TestName:是抽象類TestWatcher的一個子類實現,用於在測試執行過程中獲取測試方法名稱。在starting()中記錄測試方法名,在getMethodName()中返回
- ExpectedException:與@Test中的expected相對應,提供更強大靈活的異常驗證功能,@Test只能修飾待測試方法,ExpectedException可以修飾待測試類
- Timeout:與@Test中的timeout相對應,@Test只能修飾待測試方法,Timeout可以修飾待測試類
- RuleChain:用於將多個Rules串在一起執行。RuleChain已經deprecated了,但是其源碼實現比較有趣,所以本篇沒有直接去掉。
篇幅原因此處僅簡要介紹這些Rules提供的功能,后續將在專門的Rule及TestRule實現類源碼分析中詳解其實現。
JUnit Rule源碼分析
以下過程是以典型的單個待測試類調用BlockJUnit4ClassRunner執行測試為例進行分析,如果對源碼分析無興趣可直接跳到JUnit Rule擴展示例部分。
分析Rule特性的源碼實現之前需要先梳理Statement的概念及執行過程,tStatement是對原子級測試的封裝,我們在JUnit Runner中看到的測試用例執行過程是順序執行不同注解修飾的測試方法,即@BeforeClass->@Before->@Test->@After->@Before->@Test->@After->@Before->@Test->@After->@AfterClass(此處以三個待測試方法為例)。那么JUnit是如何將這一系列串接在一起的呢?其設計思想就是通過Statement以責任鏈的模式將其層層封裝,責任鏈中上個節點的Statement中都存在對下一個節點的引用。Statement可以說是JUnit的核心設計之一,理清了Statement的執行過程就抓住了JUnit實現原理的主線。
Rule特性又是如何織入Statement的封裝與執行過程的呢?我們知道Rule特性中有兩個注解@ClassRule和@Rule用來修飾Rule變量或返回Rule的方法,這些變量或方法返回值的類型都需要實現TestRule接口,而TestRule中唯一定義的方法apply()的返回值類型就是Statement,所以JUnit中Rule特性的實現類同樣是Statement的一種。根據BlockJUnit4ClassRunner的父類ParentRunner中的classBlock()方法中的調用,以及BlockJUnit4ClassRunner中methodBlock()方法中的調用,我們基本上可以梳理出@ClassRule和@Rule在整個Statement責任鏈中的執行順序,以JUnit內置的Rule實現類ErrorCollector,TemporaryFolderr和TestName為例(含兩個@Test修飾的待測試方法):
- @ClassRule注解ErrorCollector類實例
執行順序為:
@BeforeClass->@Before->@Test->@After->@Before->@Test->@After->@AfterClass->@ClassRule(verify())
- @ClassRule注解TemporaryFolder類實例
執行順序為:
@ClassRule(before())->@BeforeClass->@Before->@Test->@After->@Before->@Test->@After->@AfterClass->@ClassRule(after())
- @Rule注解TestName類實例
執行順序為:
@BeforeClass->@Rule(starting())->@Before->@Test->@After->@Rule(starting())->@Before->@Test->@After->@AfterClass
如果測試用例中@ClassRule和@Rule兩個都存在,則按實際覆寫的接口方法所處測試階段順序織入測試執行過程。
上述部分是分析Statement的設計思想及Rule在Statement執行過程中的順序,文字描述通常都比較晦澀,還是特出關鍵源碼一步步解讀比較好,此處的簡析僅僅是希望對Statement和Rule有一些大致的概念,方便后續的源碼解讀。
- Statement封裝過程的關鍵代碼
Statement封裝過程中有兩個主要的方法classBlock()和methodBlock(),其中classBlock()是測試類級別的封裝,也就是說測試類級注解@BeforeRule, @AfterRule以及@ClassRule修飾的方法在此處鏈式封裝,methodBlock()是測試方法級別的封裝,也就是說測試方法級別注解@Test(expected=xxx) , @Test(timeout=xxx), @Before, @After()及@Rule修飾的方法在此處鏈式封裝。
先看一下classBlock()的調用過程:
//org.junit.runners.ParentRunner protected Statement classBlock(final RunNotifier notifier) { Statement statement = childrenInvoker(notifier); //構造出所有測試方法基本的Statement類對象 if (!areAllChildrenIgnored()) { statement = withBeforeClasses(statement); //對應@BeforeClass statement = withAfterClasses(statement); //對應@AfterClass statement = withClassRules(statement); //對應@ClassRule } return statement; //返回層層封裝后的Statement類對象 }
classBlock()中寫的很清楚,首先調用childrenInvoker()構造Statement的基本行為,如果所有的子測試都沒有被Ignore則通過withBeforeClasses(), withAfterClasses()及withClassRules()繼續封裝。先放一下,分析完childrenInvoker()的調用過程再從這里接入。
//org.junit.runners.ParentRunner protected Statement childrenInvoker(final RunNotifier notifier) { return new Statement() { @Override public void evaluate() { runChildren(notifier); } }; }
childrenInvoker()的作用是構造基本的Statement行為,即執行所有的子測試runChildren(),在runChildren()中循環調用每個子測試runChild()。
//org.junit.runners.ParentRunner private void runChildren(final RunNotifier notifier) { final RunnerScheduler currentScheduler = scheduler; try { for (final T each : getFilteredChildren()) { currentScheduler.schedule(new Runnable() { public void run() { ParentRunner.this.runChild(each, notifier); } }); } } finally { currentScheduler.finished(); } }
//org.junit.runners.ParentRunner protected abstract void runChild(T child, RunNotifier notifier);
//org.junit.runners.BlockJUnit4ClassRunner protected void runChild(final FrameworkMethod method, RunNotifier notifier) { Description description = describeChild(method); if (isIgnored(method)) { notifier.fireTestIgnored(description); } else { Statement statement = new Statement() { @Override public void evaluate() throws Throwable { methodBlock(method).evaluate(); } }; runLeaf(statement, description, notifier); } }
因為ParentRunner中只有runChild()的抽象方法,所以該方法的具體實現在其子類BlockJUnit4ClassRunner中,子類的runChild()中調用了測試方法級的層層封裝methodBlock()。
//org.junit.runners.BlockJUnit4ClassRunner protected Statement methodBlock(final FrameworkMethod method) { Object test; try { test = new ReflectiveCallable() { @Override protected Object runReflectiveCall() throws Throwable { return createTest(method); } }.run(); } catch (Throwable e) { return new Fail(e); } Statement statement = methodInvoker(method, test); statement = possiblyExpectingExceptions(method, test, statement); statement = withPotentialTimeout(method, test, statement); statement = withBefores(method, test, statement); statement = withAfters(method, test, statement); statement = withRules(method, test, statement); return statement; }
//org.junit.runners.BlockJUnit4ClassRunner protected Object createTest() throws Exception { return getTestClass().getOnlyConstructor().newInstance(); }
//org.junit.runners.BlockJUnit4ClassRunner protected Statement methodInvoker(FrameworkMethod method, Object test) { return new InvokeMethod(method, test); }
methodBlock()中首先在createTest()中通過反射構造實例,在將該實例及FrameworkMethod類對象method作為methodInvoker()的入參構造出基本的Statement類對象。
//org.junit.internal.runners.statements.InvokeMethod public class InvokeMethod extends Statement { private final FrameworkMethod testMethod; private final Object target; public InvokeMethod(FrameworkMethod testMethod, Object target) { this.testMethod = testMethod; this.target = target; } @Override public void evaluate() throws Throwable { testMethod.invokeExplosively(target); } }
構造出基本的Statement類對象后,在執行后續操作對該Statement類對象進行層層封裝。篇幅原因就不再對如下possiblyExpectingExceptions等五個方法的調用過程作進一步解析,這些方法調用和下面將要講解的classBlock()方法實現中的下半部分很相似,只是此處是測試方法級的封裝調用,classBlock()中是測試類級的封裝調用。
Statement statement = methodInvoker(method, test); //構造出測試方法基本的Statement類對象 statement = possiblyExpectingExceptions(method, test, statement); //對應@Test(expected=xxx) statement = withPotentialTimeout(method, test, statement); //對應@Test(timeout=xxx), deprecated statement = withBefores(method, test, statement); //對應@Before statement = withAfters(method, test, statement); //對應@After statement = withRules(method, test, statement); //對應@Rule return statement; //返回層層封裝后的待測試方法
再回到前面classBlock()中的分析過程,該方法的后半部分會對構造出的所有方法的基本statement類對象作進一步封裝,依次為withBeforeClasses(), withAfterClasses()及withClassRules()。
//org.junit.runners.ParentRunner
protected Statement classBlock(final RunNotifier notifier) { Statement statement = childrenInvoker(notifier); //構造出所有測試方法基本的Statement類對象 if (!areAllChildrenIgnored()) { statement = withBeforeClasses(statement); //對應@BeforeClass statement = withAfterClasses(statement); //對應@AfterClass statement = withClassRules(statement); //對應@ClassRule } return statement; //返回層層封裝后的Statement類對象 }
//org.junit.runners.ParentRunner protected Statement withBeforeClasses(Statement statement) { List<FrameworkMethod> befores = testClass .getAnnotatedMethods(BeforeClass.class); return befores.isEmpty() ? statement : new RunBefores(statement, befores, null); }
withBeforeClasses()調用過程:提取出待測試類中用@BeforeClass注解的所有方法,再把這些方法和childrenInvoker()中構造出的基本Statement類對象作為入參用Statement的子類RunBefores重新封裝並返回。
//org.junit.runners.ParentRunner protected Statement withAfterClasses(Statement statement) { List<FrameworkMethod> afters = testClass .getAnnotatedMethods(AfterClass.class); return afters.isEmpty() ? statement : new RunAfters(statement, afters, null); }
withAfterClasses()調用過程:提取出待測試類中用@AfterClass注解的所有方法,再把這些方法和withBeforeClasses()中返回的Statement類對象作為入參用Statement的子類RunAfters重新封裝並返回。
//org.junit.runners.ParentRunner private Statement withClassRules(Statement statement) { List<TestRule> classRules = classRules(); return classRules.isEmpty() ? statement : new RunRules(statement, classRules, getDescription()); }
withClassRules()調用過程:提取出待測試類中用@ClassRule注解的所有Rule類變量或返回值為Rule類的方法,再把這些變量和方法同withAfterClasses()中返回的Statement類對象作為入參用Statement的子類RunRules重新封裝並返回。
//org.junit.runners.ParentRunner protected List<TestRule> classRules() { ClassRuleCollector collector = new ClassRuleCollector(); testClass.collectAnnotatedMethodValues(null, ClassRule.class, TestRule.class, collector); testClass.collectAnnotatedFieldValues(null, ClassRule.class, TestRule.class, collector); return collector.getOrderedRules(); }
//org.junit.runners.ParentRunner private static class ClassRuleCollector implements MemberValueConsumer<TestRule> { final List<RuleContainer.RuleEntry> entries = new ArrayList<RuleContainer.RuleEntry>(); public void accept(FrameworkMember member, TestRule value) { ClassRule rule = member.getAnnotation(ClassRule.class); entries.add(new RuleContainer.RuleEntry(value, RuleContainer.RuleEntry.TYPE_TEST_RULE, rule != null ? rule.order() : null)); } public List<TestRule> getOrderedRules() { if (entries.isEmpty()) { return Collections.emptyList(); } Collections.sort(entries, RuleContainer.ENTRY_COMPARATOR); List<TestRule> result = new ArrayList<TestRule>(entries.size()); for (RuleContainer.RuleEntry entry : entries) { result.add((TestRule) entry.rule); } return result; } }
classRules()方法用於獲取@ClassRule修飾的所有TestRule實現類。
//org.junit.rules.RunRules
public class RunRules extends Statement { private final Statement statement; public RunRules(Statement base, Iterable<TestRule> rules, Description description) { statement = applyAll(base, rules, description); } @Override public void evaluate() throws Throwable { statement.evaluate(); } private static Statement applyAll(Statement result, Iterable<TestRule> rules, Description description) { for (TestRule each : rules) { result = each.apply(result, description); } return result; } }
RunRules類用於根據調用classRules()獲取到的所有TestRule實現類集合對withAfterRules()方法返回的Statement類實例進行重新封裝。
- Runner驗證Rule規則的關鍵代碼
本文開始提到過@ClassRule和@Rule修飾的Rule類變量或方法有一定的限制,比如public修飾符, 是或非static, 實現自TestRule接口等,所以在測試用例執行前需要進行相應的驗證,這個是由ParentRunner及其子類在其構造方法的初始化過程中完成的。Rule規則的校驗主要通過四個RuleMemberValidator實例CLASS_RULE_METHOD_VALIDATOR,CLASS_RULE_VALIDATOR,RULE_METHOD_VALIDATOR及RULE_VALIDATOR調用各自的validate()方法來實現的,具體調用過程解析如下:
//org.junit.runners.BlockJUnit4ClassRunner public BlockJUnit4ClassRunner(Class<?> testClass) throws InitializationError { super(testClass); }
BlockJUnit4ClassRunner構造方法初始化過程會調用父類ParentRunner的的構造方法。
//org.junit.runners.ParentRunner protected ParentRunner(Class<?> testClass) throws InitializationError { this.testClass = createTestClass(testClass); validate(); }
ParentRunner的構造方法中包含了validate()調用
//org.junit.runners.ParentRunner private void validate() throws InitializationError { List<Throwable> errors = new ArrayList<Throwable>(); collectInitializationErrors(errors); if (!errors.isEmpty()) { throw new InvalidTestClassError(testClass.getJavaClass(), errors); } }
該validate()實現中包含collectInitializationErrors()調用,子類BlockJUnit4ClassRunner覆寫了父類ParentRunner的collectInitializationErrors()方法。
//org.junit.runners.BlockJUnit4ClassRunner
@Override protected void collectInitializationErrors(List<Throwable> errors) { super.collectInitializationErrors(errors); validatePublicConstructor(errors); validateNoNonStaticInnerClass(errors); validateConstructor(errors); validateInstanceMethods(errors); validateFields(errors); validateMethods(errors); }
子類BlockJUnit4ClassRunner中的collectInitializationErrors()方法實現會先調用父類ParentRunner中的collectInitializationErrors()。
//org.junit.runners.ParentRunner protected void collectInitializationErrors(List<Throwable> errors) { validatePublicVoidNoArgMethods(BeforeClass.class, true, errors); validatePublicVoidNoArgMethods(AfterClass.class, true, errors); validateClassRules(errors); applyValidators(errors); }
父類ParentRunner中的collectInitializationErrors()方法實現中包含了validateClassRules()調用。
//org.junit.runners.ParentRunner private void validateClassRules(List<Throwable> errors) { CLASS_RULE_VALIDATOR.validate(getTestClass(), errors); CLASS_RULE_METHOD_VALIDATOR.validate(getTestClass(), errors); }
validateClassRules()方法中包含了兩個RuleMemberValidator實例CLASS_RULE_VALIDATOR及CLASS_RULE_METHOD_VALIDATOR各自的validate()方法調用,這兩個方法會對@ClassRule修飾的變量或方法進行Rule規則校驗。
//org.junit.runners.BlockJUnit4ClassRunner protected void validateFields(List<Throwable> errors) { RULE_VALIDATOR.validate(getTestClass(), errors); }
//org.junit.runners.BlockJUnit4ClassRunner private void validateMethods(List<Throwable> errors) { RULE_METHOD_VALIDATOR.validate(getTestClass(), errors); }
validateFields()方法中包含了RuleMemberValidator實例RULE_VALIDATOR的validate()方法調用,validateMethods()方法中包含了RuleMemberValidator實例RULE_METHOD_VALIDATOR的validate()方法調用,這兩個方法會對@Rule修飾的變量或方法進行Rule規則校驗。
//org.junit.internal.runners.rules.RuleMemberValidator 驗證@ClassRule修飾的方法是否復合Rule特性規則 public static final RuleMemberValidator CLASS_RULE_METHOD_VALIDATOR = classRuleValidatorBuilder() .forMethods() .withValidator(new DeclaringClassMustBePublic()) .withValidator(new MemberMustBeStatic()) .withValidator(new MemberMustBePublic()) .withValidator(new MethodMustBeATestRule()) .build();
//org.junit.internal.runners.rules.RuleMemberValidator 驗證@ClassRule修飾的作用域是否復合Rule特性規則 public static final RuleMemberValidator CLASS_RULE_VALIDATOR = classRuleValidatorBuilder() .withValidator(new DeclaringClassMustBePublic()) .withValidator(new MemberMustBeStatic()) .withValidator(new MemberMustBePublic()) .withValidator(new FieldMustBeATestRule()) .build();
//org.junit.internal.runners.rules.RuleMemberValidator 驗證@Rule修飾的方法是否復合Rule特性規則 public static final RuleMemberValidator RULE_METHOD_VALIDATOR = testRuleValidatorBuilder() .forMethods() .withValidator(new MemberMustBeNonStaticOrAlsoClassRule()) .withValidator(new MemberMustBePublic()) .withValidator(new MethodMustBeARule()) .build();
//org.junit.internal.runners.rules.RuleMemberValidator 驗證@Rule修飾的作用域是否復合Rule特性規則 public static final RuleMemberValidator RULE_VALIDATOR = testRuleValidatorBuilder() .withValidator(new MemberMustBeNonStaticOrAlsoClassRule()) .withValidator(new MemberMustBePublic()) .withValidator(new FieldMustBeARule()) .build();
以上是四個RuleMemberValidator實例CLASS_RULE_METHOD_VALIDATOR,CLASS_RULE_VALIDATOR,RULE_METHOD_VALIDATOR及RULE_VALIDATOR的具體定義,其實從類名定義上就可以直觀地看到這些實例具體的校驗內容,篇幅原因不再詳述。
- Rule特性織入Statement的關鍵代碼
Rule特性織入Statement的過程主要依賴兩個語句及其涉及到的嵌套調用,這兩個語句即是classBlock()方法中的statement = withClassRules(statement)和methodBlock()方法中的statement = withRules(statement)。下面依次對二者嵌套的調用過程進行解析。
//org.junit.runners.ParentRunner
protected Statement classBlock(final RunNotifier notifier) { Statement statement = childrenInvoker(notifier); //構造出所有測試方法基本的Statement類對象 if (!areAllChildrenIgnored()) { statement = withBeforeClasses(statement); //對應@BeforeClass statement = withAfterClasses(statement); //對應@AfterClass statement = withClassRules(statement); //對應@ClassRule } return statement; //返回層層封裝后的Statement類對象 }
//org.junit.runners.BlockJUnit4ClassRunner protected Statement methodBlock(final FrameworkMethod method) { Object test; try { test = new ReflectiveCallable() { @Override protected Object runReflectiveCall() throws Throwable { return createTest(method); } }.run(); } catch (Throwable e) { return new Fail(e); } Statement statement = methodInvoker(method, test); statement = possiblyExpectingExceptions(method, test, statement); statement = withPotentialTimeout(method, test, statement); statement = withBefores(method, test, statement); statement = withAfters(method, test, statement); statement = withRules(method, test, statement); return statement; }
先來看methodBlock()中的withRules()調用,MethodRule接口從JUnit4.10開始已經deprecated,該接口相關的代碼可以直接ignore。
//org.junit.runners.BlockJUnit4ClassRunner private Statement withRules(FrameworkMethod method, Object target, Statement statement) { RuleContainer ruleContainer = new RuleContainer(); CURRENT_RULE_CONTAINER.set(ruleContainer); try { List<TestRule> testRules = getTestRules(target); for (MethodRule each : rules(target)) { if (!(each instanceof TestRule && testRules.contains(each))) { ruleContainer.add(each); } } for (TestRule rule : testRules) { ruleContainer.add(rule); } } finally { CURRENT_RULE_CONTAINER.remove(); } return ruleContainer.apply(method, describeChild(method), target, statement); }
}
創建RunContainer實例,將其設置為當前線程局部變量的值,通過getTestRules()獲取注解target的所有@Rule規則,並將其加入到新建的RunContainer實例中 ,ThreadLocal的內在機制會保證這一過程在並發環境下的的線程安全。
//org.junit.runners.BlockJUnit4ClassRunner private static final ThreadLocal<RuleContainer> CURRENT_RULE_CONTAINER = new ThreadLocal<RuleContainer>();
//org.junit.runners.BlockJUnit4ClassRunner protected List<TestRule> getTestRules(Object target) { RuleCollector<TestRule> collector = new RuleCollector<TestRule>(); getTestClass().collectAnnotatedMethodValues(target, Rule.class, TestRule.class, collector); getTestClass().collectAnnotatedFieldValues(target, Rule.class, TestRule.class, collector); return collector.result; }
在apply()方法中通過調用getSortedEntries()對所有Rule進行排序處理,處理完成后返回封裝Rule之后的Statement實例。
//org.junit.runners.RuleContainer public Statement apply(FrameworkMethod method, Description description, Object target, Statement statement) { if (methodRules.isEmpty() && testRules.isEmpty()) { return statement; } Statement result = statement; for (RuleEntry ruleEntry : getSortedEntries()) { if (ruleEntry.type == RuleEntry.TYPE_TEST_RULE) { result = ((TestRule) ruleEntry.rule).apply(result, description); } else { result = ((MethodRule) ruleEntry.rule).apply(result, method, target); } } return result; }
}
//org.junit.runners.RuleContainer private List<RuleEntry> getSortedEntries() { List<RuleEntry> ruleEntries = new ArrayList<RuleEntry>( methodRules.size() + testRules.size()); for (MethodRule rule : methodRules) { ruleEntries.add(new RuleEntry(rule, RuleEntry.TYPE_METHOD_RULE, orderValues.get(rule))); } for (TestRule rule : testRules) { ruleEntries.add(new RuleEntry(rule, RuleEntry.TYPE_TEST_RULE, orderValues.get(rule))); } Collections.sort(ruleEntries, ENTRY_COMPARATOR); return ruleEntries; }
}
以上是對方法進行Rule規則的封裝,在classBlock()代碼塊中會上溯到childrenInvoker()中的調用,測試方法級的Statement封裝處理完之后,還需要繼續進行測試類級的Statement封裝。從開始貼出的classBlock()的代碼塊中可以看到childrenInvoker()返回對所有測試方法的Statement封裝之后,還會繼續調用withBeforeClasses(), withAfterClasses()及withClassRules()進一步處理,這里對於withBeforeClasses()和withAfterClasses()的方法調用就不詳細講解了,下面看看withClassRules()方法的執行流程,這個是與Rule規則直接相關的。
//org.junit.runners.ParentRunner
protected Statement classBlock(final RunNotifier notifier) { Statement statement = childrenInvoker(notifier); //構造出所有測試方法基本的Statement類對象 if (!areAllChildrenIgnored()) { statement = withBeforeClasses(statement); //對應@BeforeClass statement = withAfterClasses(statement); //對應@AfterClass statement = withClassRules(statement); //對應@ClassRule } return statement; //返回層層封裝后的Statement類對象 }
//org.junit.runners.ParentRunner private Statement withClassRules(Statement statement) { List<TestRule> classRules = classRules(); return classRules.isEmpty() ? statement : new RunRules(statement, classRules, getDescription()); }
classRules()方法先獲取所有@ClassRule規則,然后通過getOrderedRules()對所有@ClassRule規則進行排序。withClassRules()方法通過classRules()獲取注解待測試類的所有排序后的@ClassRule規則,並將其作為RunRule類構造方法的入參進一步封裝withAfterClasses()方法中返回的Statement實例。RunRule本身也是Statement的子類。
//org.junit.runners.ParentRunner protected List<TestRule> classRules() { ClassRuleCollector collector = new ClassRuleCollector(); testClass.collectAnnotatedMethodValues(null, ClassRule.class, TestRule.class, collector); testClass.collectAnnotatedFieldValues(null, ClassRule.class, TestRule.class, collector); return collector.getOrderedRules(); }
//org.junit.runners.ParentRunner private static class ClassRuleCollector implements MemberValueConsumer<TestRule> { final List<RuleContainer.RuleEntry> entries = new ArrayList<RuleContainer.RuleEntry>(); public void accept(FrameworkMember member, TestRule value) { ClassRule rule = member.getAnnotation(ClassRule.class); entries.add(new RuleContainer.RuleEntry(value, RuleContainer.RuleEntry.TYPE_TEST_RULE, rule != null ? rule.order() : null)); } public List<TestRule> getOrderedRules() { if (entries.isEmpty()) { return Collections.emptyList(); } Collections.sort(entries, RuleContainer.ENTRY_COMPARATOR); List<TestRule> result = new ArrayList<TestRule>(entries.size()); for (RuleContainer.RuleEntry entry : entries) { result.add((TestRule) entry.rule); } return result; }
//org.junit.rules.RunRule public class RunRules extends Statement { private final Statement statement; public RunRules(Statement base, Iterable<TestRule> rules, Description description) { statement = applyAll(base, rules, description); } @Override public void evaluate() throws Throwable { statement.evaluate(); } private static Statement applyAll(Statement result, Iterable<TestRule> rules, Description description) { for (TestRule each : rules) { result = each.apply(result, description); //各個Rule真正執行的地方 } return result; } }
JUnit Rule擴展示例
根據上述Rule特性的源碼分析可知,Rule的擴展主要有三種方式:
- 實現TestRule接口
- 繼承自內置Rule抽象類(Verifier,ExternalResource or TestWatcher)
- 繼承自內置Rule抽象類的子類(ErrorCollector,TemporaryFolder,TestName,ExpectedException,Timeout or RuleChain)
下面以第一種方式為例實現JUnit用例失敗重試功能說明擴展Rule的一般用法,主要步驟如下:
- 自定義Retry注解
- 自定義Rule類實現TestRule接口
- 在待測試類中使用自定義Rule
//自定義Retry注解 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Retry { int times(); }
//擴展Rule類 import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; public class RetryRule implements TestRule{ @Override public Statement apply(Statement statement, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { Throwable retryThrowable = null; Retry retry = description.getAnnotation(Retry.class); if(retry != null) { int times = retry.times(); for(int i=0; i<times; i++) { try { statement.evaluate(); return; } catch(final Throwable t) { retryThrowable = t; System.err.println("Run method " + description.getMethodName() + ": failed for " + (i+1) +
((i+1) == 1 ? " time" : " times ")); } } System.err.println("Run method " + description.getMethodName() + " : exited after " + times + " attempts"); } else { statement.evaluate(); } } }; } }
//待測試類 import static org.junit.Assert.fail; import org.junit.FixMethodOrder; import org.junit.Rule; import org.junit.Test; import org.junit.runners.MethodSorters; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class MyRetryRuleTest { @Rule public RetryRule retryRule = new RetryRule(); @Test public void testMethodA() throws Exception { System.out.println("test methodA..."); } @Test @Retry(times=3) public void testMethodB() throws Exception { fail(); System.out.println("test methodB..."); } @Retry(times=5) @Test(timeout=10) public void testMethodC() throws Exception { Thread.sleep(10); System.out.println("test methodC..."); } }
//測試執行結果 test methodA... Run method testMethodB: failed for 1 time Run method testMethodB: failed for 2 times Run method testMethodB: failed for 3 times Run method testMethodB : exited after 3 attempts test methodC...
當然擴展Rule除了以上三種方式外,還有其他的間接方式實現同樣的效果,因為本篇的主旨是自定義Rule,所以其他的擴展方式暫不涉及。
JUnit Rule總結
事實上Rule特性實現的功能也可以通過其他的擴展方式完成,這個需要根據項目平台及小組技能棧來確定哪種方式更靈活。多了解些測試工具內部的實現原理和執行流程可以讓我們更全面地評估同類開發或測試工具各自的優劣勢,在遇到不同類型問題的時候選擇成本更低、效果更好的解決方案。當然,對於優秀的開源框架,吸收其經典的設計思想也是自建高效框架的必由之路。