junit 学习(三)

上一篇 / 下一篇  2013-04-01 14:51:39 / 个人分类:java

理论机制(Theory

为什么要引用理论机制(Theory

当今软件开发中,测试驱动开发(TDD — Test-driven development)越发流行。为什么TDD会如此流行呢?因为它确实拥有很多优点,它允许开发人员通过简单的例子来指定和表明他们代码的行为意图。

TDD的优点:

使得开发人员对即将编写的软件任务具有更清晰的认识,使得他们在思考如何编写代码之前先仔细思考如何设计软件。

对测试开发人员所实现的代码提供了快速和自动化的支持。

提供了一系列可以重用的回归测试用例regression test case),这些测试用例可以用来检测未来添加的新代码是否改变了以前系统定义的行为(测试代码兼容性)。

然而,TDD也同样具有一定的局限性。对于开发人员来说,只用一些具体有限的简单例子来表达程序的行为往往远远不够。有很多代码行为可以很容易而且精确的用语言来描述,却很难用一些简单的例子来表达清楚,因为他们需要大量的甚至无限的具体例子才可以达到被描述清楚的目的,而且有时有限的例子根本不能覆盖所有的代码行为。

以下列出的代码行为反映了TDD的局限性:

将十进制整数转换成罗马数字,然后再将其转换回十进制数,并保持原有的数值。(需要大量的测试用例,有限的测试数据可能测不出所实现的代码的错误)。

对一个对象进行操作,希望结果仍然等于原来的对象。(需要考虑各种各样类型的对象)

在任何一个货币的collection中添加一个对象dollar,需要产生出另外一个新的与以前不同的collection。(需要考虑所有的collection类型的对象)。

理论(Theory)的出现就是为了解决TDD这个问题。TDD为组织规划开发流程提供了一个方法,先用一些具体的例子(测试用例test case)来描述系统代码的行为,然后再将这些行为用代码语句进行概括性的总的陈述(代码实现implementation)。而Theory就是对传统的TDD进行一个延伸和扩展,它使得开发人员从开始的定义测试用例的阶段就可以通过参数集(理论上是无限个参数)对代码行为进行概括性的总的陈述,我们叫这些陈述为理论。理论就是对那些需要无穷个测试用例才能正确描述的代码行为的概括性陈述。结合理论(Theory)和测试一起,可以轻松的描述代码的行为并发现BUG。开发人员都知道他们代码所想要实现的概括性的总的目的,理论使得他们只需要在一个地方就可以快速的指定这些目的,而不要将这些目的翻译成大量的独立的测试用例。

理论机制的优点

优点1:理论(Theory)使得开发完全抽象的接口(Interface)更加容易。

优点2:理论仍然可以重用以前的测试用例,因为以前的许多传统的具体的测试用例仍然可以被轻松的改写成理论(Theory)测试实例。

优点3:理论(Theory)可以测试出一些原本测试用例没测出来的bugs

优点4:理论允许配合自动化测试工具进行使用,自动化工具通过大量的数据点来测试一个理论,从而可以放大增强理论的效果。利用自动化工具来分析代码,找出可以证明理论错误的值。

下面通过一个简单的例子来逐步介绍理论的优点。

比如设计一个专门用来货币计算的计算器,首先需要给代码行为编写测试用例(这里以英镑Pound的乘法为例),如清单9所示:


清单9英镑Pound乘法的一个测试用例

                           

@Test

public void multiplyPoundsByInteger() {

   assertEquals( 10, new Pound(5).times(2).getAmount() );

}

 

这时很自然的就会想到一个测试用例可能不够,需要再多一个,如清单10所示:


清单10英镑Pound乘法的两个测试用例

                           

@Test

public void multiplyPoundsByInteger () {

   assertEquals( 10, new Pound(5).times(2).getAmount() );

   assertEquals( 15, new Pound(5).times(3).getAmount() );

}

 

但是此时您可能又会发现这两个测试用例还是很有限,您所希望的是测试所有的整数,而不只是235,这些只是您所想要的测试的数据的子集,两个测试用例并不能完全与您所想要测试的代码的行为相等价,您需要更多的测试用例,此时就会发现需要很多的额外工作来编写这些测试用例,更可怕的是,您会发现您需要测试用例的并不只是简单的几个,可能是成千上万个甚至无穷个测试用例才能满足等价您的代码行为的目的。

很自然的,您会想到用清单11所示的代码来表达您的测试思想。


清单11使用变量辅助编写测试用例

                           

//利用变量来代替具体数据表达测试思想

public void multiplyAnyAmountByInteger(int amount, int multiplier) {

   assertEquals( amount * multiplier,

       new Pound( amount ).times( multiplier ).getAmount() );

}

 

利用清单11 multiplyAnyAmountByInteger 方法,可以轻松将测试用例改写成如清单12所示:


清单12改写的英镑Pound乘法的测试用例

                           

@Test

public void multiplyPoundsByInteger () {

   multiplyAnyAmountByInteger(5, 2);

   multiplyAnyAmountByInteger(5, 3);

}

 

如清单12所示,以后若想增加测试用例,只要不停调用 multiplyAnyAmountByInteger方法并赋予参数值即可。

方法 multiplyAnyAmountByInteger 就是一个理论的简单例子,理论就是一个带有参数的方法,其行为就是对任何参数都是正常的返回,不会抛出断言错误和其它异常。理论就是对一组数据进行概括性的陈述,就像一个科学理论一样,如果没有对所有可能出现的情况都进行实验,是不能证明该理论是正确的,但是只要有一种错误情况出现,该理论就不成立。相反地,一个测试就是对一个单独数据的单独陈述,就像是一个科学理论的实验一样。

如何使用理论机制

JUnit 4.4的理论机制中,每个测试方法不再是由注释 @Test 指定的无参测试函数,而是由注释 @Theory 指定的带参数的测试函数,这些参数来自一个数据集(data sets),数据集通过注释 @DataPoint 指定。

JUnit 4.4会自动将数据集中定义的数据类型和理论测试方法定义的参数类型进行比较,如果类型相同,会将数据集中的数据通过参数一一传入到测试方法中。数据集中的每一个数据都会被传入到每个相同类型的参数中。这时有人会问了,如果参数有多个,而且类型都和数据集中定义的数据相同,怎么办?答案是,JUnit 4.4会将这些数据集中的数据进行一一配对组合(所有的组合情况都会被考虑到),然后将这些数据组合统统通过参数,一一传入到理论的测试方法中,但是用户可以通过假设机制(assumption)在断言函数(assertion)执行这些参数之前,对这些通过参数传进来的数据集中的数据进行限制和过滤,达到有目的地部分地将自己想要的参数传给断言函数(assertion)来测试。只有满足所有假设的数据才会执行接下来的测试用例,任何一个假设不满足的数据,都会自动跳过该理论测试函数(假设assumption不满足的数据会被忽略,不再执行接下来的断言测试),如果所有的假设都满足,测试用例断言函数不通过才代表着该理论测试不通过。




TAG:

 

评分:0

我来说两句

Open Toolbar