最近在研究JUnit4,大部分基礎技術都是通過百度和JUnit的官方wiki學習的,目前最新的發布版本是4.11,結合代碼實踐,發現官方wiki的內容或多或少沒有更新,Theory理論機制章節情況尤為嚴重,不知道這章wiki對應的是第幾版,筆主在4.11版本中是完全跑不通的,因為接口結構已經改變了,而百度出來的博客文檔更是只有Theory的基礎部分,更具實際應用價值的擴展部分完全不見蹤影,本文根據筆主實際編碼總結經驗,詳細講述如何使用4.11版JUnit的Theory理論機制。
ps. 最近發現網上有一小撮別有用心的國人轉載筆主文章時,順手丟羊把筆主文章頭部標注的原文地址惡意馬賽克掉,也沒有在文后貼出轉載地址,為了普及技術筆主就不追究了,這次故意在貼在這里,本文地址:JUnit4.11 理論機制 @Theory 完整解讀
筆主下面所使用代碼,僅依賴於 junit-4.11.jar,建議同時導入 hamcrest-core-1.3.jar、hamcrest-library-1.3.jar 以提供完整的assertThat支持
簡單介紹Theory
使用注解@Theory取代@Test標記測試方法,可以支持帶形參的測試方法簽名,並使用指定的數據集自動代入進行連續多次測試,雖然暫未看到官方或其他個人的文字表述,但筆主覺得這個機制是參數化測試 Parameterized tests 的優化擴展版(Parameterized tests需要獨占一個測試類,兼容性靈活性都太差了)。
使用@Theory測試方法需要在測試類頭部聲明@RunWith(Theories.class)
傳統Theory
Theory的基礎部分,通過顯式預定義各種Class類型變量,在@Theory的帶參測試方法中自動逐次傳入相同Class類型的變量值,運行多次測試(依據預定義變量數量而定),復數形參情況使用預定義變量排列組合出實參對進行代入測試。
1、使用@DataPoint標記待用實參數據
1 @DataPoint 2 public static String **1 = “####”; 3 @DataPoint 4 public static String **2 = “####”; 5 @DataPoint 6 public static String **3 = “####”;
2.1、使用@Theory標記單形參測試方法
1 @Theory 2 // 僅輸入與形參相同類型(String)的預定義@DataPoint數據,此處會自動輪流代入上面的 **1 - **3 作為實參測試數據,運行3次test1測試 3 public void test1(String ***) { 4 // 使用assume設置過濾 5 assumeThat(***); 6 assertThat(***); 7 }
2.2、復數形參測試方法
1 @Theory 2 // 使用排列組合與形參同類型(String)@DataPoint自動化輸入,與命名、順序無關,此處s1、s2將輪流使用 **1 - **3 的排列組合,如s1=**1,s2=**2或s1=**2,s2=**1,運行次數依據排列組合方案數量而定 3 public void test2(String s1, String s2) { 4 assumeThat(***); 5 assertThat(***); 6 }
Theory擴展(Popper project)
這是本文的核心部分,傳統Theory指定實參變量的方式存在非常明顯的局限性,無法精確控制每個形參的可用變量值范圍,因此JUnit引入Popper 項目的技術,提供一種完全自定義指定實參數據集的方法。
定制@Theory測試方法實參變量值范圍,主要使用 Parameter Supplier 結構
系統自帶默認實現
JUnit中自帶一個默認的 Parameter Supplier 實現:@TestedOn(ints = int[])
使用方式示例:
1 @Theory 2 public final void test(@TestedOn(ints = { 0, 1, 2 }) int i) { 3 assertTrue(i >= 0); 4 }
在這個例子中,可以很直觀的看到形參 i 在實際運行測試中,會依次自動代入取值為 ints 指定的數組{0, 1, 2}中每個元素
完全自定義實現
JUnit默認只提供了一個int型的簡單 Parameter Supplier 實現,而Theory機制真正的價值在於,能參考@TestedOn的做法,相對簡單的完全自定義出可重用 Parameter Supplier,適應於各種復雜要求的限定范圍參數值測試場景,滿足開發者所需的高度動態自定義范圍取值自動化測試,同時保留與一般@Test相同的強大兼容性,作為本文核心中的戰斗機,下面將通過兩個栗子,詳細描述如何一步步實現自定義 Parameter Supplier。
自定義實現I(動態實參值表)
本方式以一個自定義注解接口@Between為例,展示如何通過讀取注解屬性變量值,動態創建相應的實參數據集。
1、定義annotation注解接口 Between:
1 @Retention(RetentionPolicy.RUNTIME) 2 // 聲明注解接口所使用的委托處理類 3 @ParametersSuppliedBy(BetweenSupplier.class) 4 public @interface Between{ 5 // 聲明所有可用參數,效果為 @Between([first = int,] last = int) 6 int first() default 0; // 聲明默認值,成為非必須提供項 7 int last(); 8 }
2、定義委托處理類 BetweenSupplier:
1 public class BetweenSupplier extends ParameterSupplier{ 2 3 @Override 4 public List<PotentialAssignment> getValueSources(ParameterSignature sig) { 5 6 // 自定義實參值列表 7 List<PotentialAssignment> list = new ArrayList<PotentialAssignment>(); 8 9 // 獲取注解變量 10 Between between = sig.getAnnotation(Between.class); 11 12 // 獲取通過注解@Between傳入的first值 13 int first = between.first(); 14 15 // 獲取通過注解@Between傳入的last值 16 int last = between.last(); 17 18 for (int i = first; i <= last; i++){ 19 // PotentialAssignment.forValue(String name, Object value) 20 // name為value的描述標記,沒實際作用 21 // value為實參可選值 22 list.add(PotentialAssignment.forValue("name", i)); 23 } 24 return list; 25 } 26 }
3、調用方式:
1 @Theory 2 public final void test(@Between(last = 0) int i, @Between(first = 3, last= 10) int j) { 3 // i 取值為 0(first默認=0,last=0),j 取值為 3-10 4 assertTrue(i + j >= 0); 5 }
自定義實現II(靜態實參值表):
本方式以一個自定義注解接口@AllValue為例,展示如何靜態地內部創建固定的實參數據集。
1、定義annotation注解接口 AllValue(按“使用II”方式調用可省略此步驟):
1 @Retention(RetentionPolicy.RUNTIME) 2 // 聲明注解接口所使用的委托處理類 3 @ParametersSuppliedBy(AllValueSupplier.class) 4 // 空接口,不需要接受任何參數變量 5 public @interface AllValue{ }
2、定義委托處理類 AllValueSupplier:
1 public class AllValueSupplier extends ParameterSupplier{ 2 3 @Override 4 public List<PotentialAssignment> getValueSources(ParameterSignature sig) { 5 6 List<PotentialAssignment> list = new ArrayList<PotentialAssignment>(); 7 8 // 內定提供固定的可用實參值表 9 for (int i = 0; i <= 100; i++){ 10 list.add(PotentialAssignment.forValue("name", i)); 11 } 12 return list; 13 } 14 }
3.1、使用I(使用自定義注解):
1 @Theory 2 public final void test(@AllValue int i) { 3 // i 取值為 0-100 4 assertTrue(i >= 0); 5 }
3.2、使用II(可省略第1步注解接口AllValue的定義):
1 @Theory 2 public final void test(@ParametersSuppliedBy(AllValueSupplier.class) int i) { 3 // i 取值為 0-100 4 assertTrue(i >= 0); 5 }
JUnit官方wiki及下載地址:https://github.com/junit-team/junit/wiki
Theory理論機制英文原文wiki(注:已過期,不適用於4.11):https://github.com/junit-team/junit/wiki/Theories