JUnit4.11 理论机制 @Theory 完整解读
最近在研究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