理论机制(Theory)
一.为什么要引用理论机制(Theory)
当今软件开发中,测试驱动开发(TDD — Test-driven development)越发流行。为什么 TDD 会如此流行呢?因为它确实拥有很多优点,它允许开发人员通过简单的例子来指定和表明他们代码的行为意图。
TDD 的优点:
1.使得开发人员对即将编写的软件任务具有更清晰的认识,使得他们在思考如何编写代码之前先仔细思考如何设计软件。
2.对测试开发人员所实现的代码提供了快速和自动化的支持。
3.提供了一系列可以重用的回归测试用例(regression test case),这些测试用例可以用来检测未来添加的新代码是否改变了以前系统定义的行为(测试代码兼容性)。
然而,TDD 也同样具有一定的局限性。对于开发人员来说,只用一些具体有限的简单例子来表达程序的行为往往远远不够。有很多代码行为可以很容易而且精确的用语言来描述,却很难用一些简单的例子来表达清楚,因为他们需要大量的甚至无限的具体例子才可以达到被描述清楚的目的,而且有时有限的例子根本不能覆盖所有的代码行为。
以下列出的代码行为反映了 TDD 的局限性:
1.将十进制整数转换成罗马数字,然后再将其转换回十进制数,并保持原有的数值。(需要大量的测试用例,有限的测试数据可能测不出所实现的代码的错误)。
2.对一个对象进行操作,希望结果仍然等于原来的对象。(需要考虑各种各样类型的对象)
3.在任何一个货币的 collection 中添加一个对象 dollar,需要产生出另外一个新的与以前不同的 collection 。(需要考虑所有的 collection 类型的对象)。
理论(Theory)的出现就是为了解决 TDD 这个问题。 TDD 为组织规划开发流程提供了一个方法,先用一些具体的例子(测试用例 test case)来描述系统代码的行为,然后再将这些行为用代码语句进行概括性的总的陈述(代码实现 implementation)。而 Theory 就是对传统的 TDD 进行一个延伸和扩展,它使得开发人员从开始的定义测试用例的阶段就可以通过参数集(理论上是无限个参数)对代码行为进行概括性的总的陈述,我们叫这些陈述为理论。理论就是对那些需要无穷个测试用例才能正确描述的代码行为的概括性陈述。结合理论(Theory)和测试一起,可以轻松的描述代码的行为并发现 BUG 。开发人员都知道他们代码所想要实现的概括性的总的目的,理论使得他们只需要在一个地方就可以快速的指定这些目的,而不要将这些目的翻译成大量的独立的测试用例。
二.理论机制的优点
优点 1:理论(Theory)使得开发完全抽象的接口(Interface)更加容易。
优点 2:理论仍然可以重用以前的测试用例,因为以前的许多传统的具体的测试用例仍然可以被轻松的改写成理论(Theory)测试实例。
优点 3:理论(Theory)可以测试出一些原本测试用例没测出来的 bugs 。
优点 4:理论允许配合自动化测试工具进行使用,自动化工具通过大量的数据点来测试一个理论,从而可以放大增强理论的效果。利用自动化工具来分析代码,找出可以证明理论错误的值。
下面通过一个简单的例子来逐步介绍理论的优点。
比如设计一个专门用来货币计算的计算器,首先需要给代码行为编写测试用例(这里以英镑 Pound 的乘法为例),如下所示:
@Test public void multiplyPoundsByInteger() { assertEquals( 10, new Pound(5).times(2).getAmount() ); }
这时很自然的就会想到一个测试用例可能不够,需要再多一个,如下所示:
@Test public void multiplyPoundsByInteger () { assertEquals( 10, new Pound(5).times(2).getAmount() ); assertEquals( 15, new Pound(5).times(3).getAmount() ); }
但是此时您可能又会发现这两个测试用例还是很有限,您所希望的是测试所有的整数,而不只是 2,3 和 5,这些只是您所想要的测试的数据的子集,两个测试用例并不能完全与您所想要测试的代码的行为相等价,您需要更多的测试用例,此时就会发现需要很多的额外工作来编写这些测试用例,更可怕的是,您会发现您需要测试用例的并不只是简单的几个,可能是成千上万个甚至无穷个测试用例才能满足等价您的代码行为的目的。
很自然的,您会想到用如下所示的代码来表达您的测试思想。
//利用变量来代替具体数据表达测试思想 public void multiplyAnyAmountByInteger(int amount, int multiplier) { assertEquals( amount * multiplier, new Pound( amount ).times( multiplier ).getAmount() ); }
利用上面的multiplyAnyAmountByInteger 方法,可以轻松将测试用例改写成如下所示:
@Test public void multiplyPoundsByInteger () { multiplyAnyAmountByInteger(5, 2); multiplyAnyAmountByInteger(5, 3); }
如上所示,以后若想增加测试用例,只要不停调用 multiplyAnyAmountByInteger 方法并赋予参数值即可。
方法 multiplyAnyAmountByInteger 就是一个理论的简单例子,理论就是一个带有参数的方法,其行为就是对任何参数都是正常的返回,不会抛出断言错误和其它异常。理论就是对一组数据进行概括性的陈述,就像一个科学理论一样,如果没有对所有可能出现的情况都进行实验,是不能证明该理论是正确的,但是只要有一种错误情况出现,该理论就不成立。相反地,一个测试就是对一个单独数据的单独陈述,就像是一个科学理论的实验一样。
三.如何使用理论机制
在 JUnit 4.4 的理论机制中,每个测试方法不再是由注释 @Test 指定的无参测试函数,而是由注释 @Theory 指定的带参数的测试函数,这些参数来自一个数据集(data sets),数据集通过注释 @DataPoint 指定。
JUnit 4.4 会自动将数据集中定义的数据类型和理论测试方法定义的参数类型进行比较,如果类型相同,会将数据集中的数据通过参数一一传入到测试方法中。数据集中的每一个数据都会被传入到每个相同类型的参数中。这时有人会问了,如果参数有多个,而且类型都和数据集中定义的数据相同,怎么办?答案是,JUnit 4.4 会将这些数据集中的数据进行一一配对组合(所有的组合情况都会被考虑到),然后将这些数据组合统统通过参数,一一传入到理论的测试方法中,但是用户可以通过假设机制(assumption)在断言函数(assertion)执行这些参数之前,对这些通过参数传进来的数据集中的数据进行限制和过滤,达到有目的地部分地将自己想要的参数传给断言函数(assertion)来测试。只有满足所有假设的数据才会执行接下来的测试用例,任何一个假设不满足的数据,都会自动跳过该理论测试函数(假设 assumption 不满足的数据会被忽略,不再执行接下来的断言测试),如果所有的假设都满足,测试用例断言函数不通过才代表着该理论测试不通过。
实例:
package com.bijian.study; import static org.hamcrest.Matchers.*; //指定接下来要使用的Matcher匹配符 import static org.junit.Assume.*; //指定需要使用假设assume*来辅助理论Theory import static org.junit.Assert.*; //指定需要使用断言assert*来判断测试是否通过 import org.junit.experimental.theories.DataPoint; //需要使用注释@DataPoint来指定数据集 import org.junit.experimental.theories.Theories; //接下来@RunWith要指定Theories.class import org.junit.experimental.theories.Theory; //注释@Theory指定理论的测试函数 import org.junit.runner.RunWith; //需要使用@RunWith指定接下来运行测试的类 //注意:必须得使用@RunWith指定Theories.class @RunWith(Theories.class) public class TheoryTest { //利用注释@DataPoint来指定一组数据集,这些数据集中的数据用来证明或反驳接下来定义的Theory理论, //testNames1和testNames2这两个理论Theory测试函数的参数都是String,所以Junit4.4会将这5个 //@DataPoint定义的String进行两两组合,统统一一传入到testNames1和testNames2中,所以参数名year //和name是不起任何作用的,"2007"同样有机会会传给参数name,"Works"也同样有机会传给参数year @DataPoint public static String YEAR_2007 = "2007"; @DataPoint public static String YEAR_2008 = "2008"; @DataPoint public static String NAME1 = "developer"; @DataPoint public static String NAME2 = "Works"; @DataPoint public static String NAME3 = "developerWorks"; //注意:使用@Theory来指定测试函数,而不是@Test @Theory public void testNames1( String year, String name ) { assumeThat( year, is("2007") ); //year必须是"2007",否则跳过该测试函数 System.out.println( year + "-" + name ); assertThat( year, is("2007") ); //这里的断言语句没有实际意义,这里举此例只是为了不中断测试 } //注意:使用@Theory来指定测试函数,而不是@Test @Theory public void testNames2( String year, String name ) { assumeThat(year, is("2007")); //year必须是"2007",否则跳过该测试函数 //name必须既不是"2007"也不是"2008",否则跳过该测试函数 assumeThat(name, allOf( not(is("2007")), not(is("2008")))); System.out.println( year + "-" + name ); assertThat( year, is("2007") ); //这里的断言语句没有实际意义,这里举此例只是为了不中断测试 } }
结果输出:
第一个Theory打印出:
2007-2007 2007-2008 2007-developer 2007-Works 2007-developerWorks
第二个Theory打印出:
2007-developer 2007-Works 2007-developerWorks
文章来源:http://www.ibm.com/developerworks/cn/java/j-lo-junit44/
相关推荐
标题“探索JUnit4扩展:使用Rule”涉及到的是Java单元测试框架JUnit的一个高级特性,即`@Rule`。在Java开发中,单元测试是确保代码质量、可维护性和可靠性的重要手段,而JUnit作为最流行的Java单元测试框架之一,...
《探索JUnit4扩展:深入Rule》 JUnit是Java开发者最常用的单元测试框架,它极大地简化了测试代码的编写。在JUnit4中,引入了一个强大的特性——Rule,这使得测试更加灵活且可定制化。本文将深入探讨Rule的概念、...
在本文中,我们将深入探讨Junit4.12的核心概念、功能以及如何在项目中引入和使用。 首先,Junit4.12引入了注解(Annotation)的概念,这是它的一大亮点。注解允许开发者直接在测试方法上标记元数据,如`@Test`表示...
赠送jar包:junit-jupiter-api-5.4.2.jar; 赠送原API文档:junit-jupiter-api-5.4.2-javadoc.jar; 赠送源代码:junit-jupiter-api-5.4.2-sources.jar; 赠送Maven依赖信息文件:junit-jupiter-api-5.4.2.pom; ...
本文将深入探讨关于"junit-4.12.rar"包及其依赖包,以及如何解决在使用JUnit 4进行单元测试时遇到的"method initializationerror not found"错误。 首先,我们来了解JUnit 4.12版本。这是JUnit的一个稳定版本,发布...
在本文中,我们将深入探讨JUnit的核心概念、如何使用以及它在软件开发中的重要性。 JUnit测试框架由Ernst Leiss和Kent Beck在1997年发起,其后不断演进,目前主要由JUnit团队维护,最新的版本是JUnit 5,它分为...
本文将详细介绍如何在无法访问官方网站的情况下,获取并使用JUnit 4.12所需的相关包。 首先,JUnit 4.12是JUnit系列的一个版本,发布于2013年,提供了丰富的断言方法、测试注解和参数化测试等功能,极大地简化了...
赠送jar包:junit-platform-launcher-1.8.0-M1.jar; 赠送原API文档:junit-platform-launcher-1.8.0-M1-javadoc.jar; 赠送源代码:junit-platform-launcher-1.8.0-M1-sources.jar; 赠送Maven依赖信息文件:junit-...
赠送jar包:junit-jupiter-engine-5.8.2.jar; 赠送原API文档:junit-jupiter-engine-5.8.2-javadoc.jar; 赠送源代码:junit-jupiter-engine-5.8.2-sources.jar; 赠送Maven依赖信息文件:junit-jupiter-engine-...
JUnit 是一个 Java 编程语言的单元测试框架。JUnit 在测试驱动的开发方面有很重要的发展,是起源于 JUnit 的一个统称为 xUnit 的单元测试框架之一。
赠送jar包:junit-jupiter-api-5.8.0-M1.jar; 赠送原API文档:junit-jupiter-api-5.8.0-M1-javadoc.jar; 赠送源代码:junit-jupiter-api-5.8.0-M1-sources.jar; 赠送Maven依赖信息文件:junit-jupiter-api-5.8.0-...
JUnit4是Java编程语言中广泛使用的单元测试框架,它是Java开发者进行软件质量保证的重要工具。本教程将深入浅出地介绍JUnit4的基本概念、核心特性以及如何在实际项目中应用它来编写有效的单元测试。 ## 一、JUnit4...
赠送jar包:junit-jupiter-api-5.8.2.jar; 赠送原API文档:junit-jupiter-api-5.8.2-javadoc.jar; 赠送源代码:junit-jupiter-api-5.8.2-sources.jar; 赠送Maven依赖信息文件:junit-jupiter-api-5.8.2.pom; ...
JUnit Vintage则是为了兼容JUnit 4而存在的。 使用JUnit 5,开发者可以编写更加灵活和可读的测试代码,例如通过参数化测试来运行同一测试用例的不同数据组合,或者使用条件注解来控制测试执行的条件。另外,JUnit 5...
为了在项目中使用JUnit,你需要将`junit-4.11.jar`添加到项目的类路径中,并在测试类上使用`@RunWith(JUnit4.class)`注解来指定使用JUnit 4作为测试运行器。然后,你可以创建测试方法,这些方法通常以`test`开头,并...
4. **可扩展性**:JUnit 4.11允许用户自定义规则(Rules),通过@Rule注解,可以创建复杂的测试行为,如临时文件管理、超时控制等。 5. **分类(Categories)**:新增了测试分类功能,开发者可以将测试分为不同的...
JUnit4是Java编程语言中最广泛使用的单元测试框架之一,它为开发者提供了强大的工具来编写和运行测试用例,确保代码的正确性和稳定性。这个“Junit4练习:使用JUnit4测试进行简单练习”旨在帮助初学者熟悉JUnit4的...
赠送jar包:junit-4.12.jar; 赠送原API文档:junit-4.12-javadoc.jar; 赠送源代码:junit-4.12-sources.jar; 包含翻译后的API文档:junit-4.12-javadoc-API文档-中文(简体)版.zip 对应Maven信息:groupId:...
JUnit4在JUnit3的基础上进行了大量改进,特别是通过使用Java 5的注解特性来简化测试用例的编写。 1. **无需继承TestCase**:JUnit4不再要求测试类必须继承`TestCase`类。 2. **测试方法标识**:使用`@Test`注解来...
junit-vintage-engine-5.6.2.jarjunit-vintage-engine-5.6.2.jarjunit-vintage-engine-5.6.2.jar