JUnit原始碼分析 - 擴充套件 - 自定義Rule
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()和
//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特性實現的功能也可以通過其他的擴充套件方式完成,這個需要根據專案平臺及小組技能棧來確定哪種方式更靈活。多瞭解些測試工具內部的實現原理和執行流程可以讓我們更全面地評估同類開發或測試工具各自的優劣勢,在遇到不同型別問題的時候選擇成本更低、效果更好的解決方案。當然,對於優秀的開源框架,吸收其經典的設計思想也是自建高效框架的必由之路。