在测试软件的过程中需要执行一个重要步骤,也就是开发有用并且全面的测试数据。使用数据分区技术(也称为域分析)可以圆满地完成此步骤。
对于选择有用的测试值以及获得最可能的测试覆盖率,数据分区是一种有效方式。本主题着重描述如何使用数据分区技术,这是因为它们适用于面向对象的系统,尤其适用于包含复杂对象类型参数以及外部输入或基于上下文的输入的那些系统。
通常来讲,可以执行下列步骤来将测试数据分区:
本主题中使用的示例是从 JUnit Money 应用程序中提取的,它着重描述 Money 类中的 equals 方法。下面列示的代码中显示了此方法的代码:
public class Money implements IMoney { private int fAmount private String fCurrency; public boolean equals(Object anObject) { if (isZero()) if (anObject instanceof IMoney) return ((IMoney)anObject).isZero(); if (anObject instanceof Money) { Money aMoney= (Money)anObject; return aMoney.currency().equals(currency()) && amount() == aMoney.amount(); } return false; }
在定义测试数据时,首先应了解被测组件(CUT)所使用的数据。以 equals 方法为例,您可以看到此方法带有类型为 Object 的单个自变量(anObject):
public boolean equals(Object anObject) {
因为 equals 方法未被定义为静态,所以它是一种实例方法。因此,除了 anObject 参数以外,CUT 还可以使用对其调用该方法的对象实例中的数据。
因此,有两个测试参数会影响您调用 equals 方法时所发生的情况:对其调用该方法的对象(this)和方法自变量(anObject)。如果将第一个参数称为 a,将方法自变量(anObject)称为 b,则用代码可将它表示为 a.equals(b)。从这里您就可以知道,要测试 equals 方法,就应该改变同时与 a 和 b 相关联的数据。
这些可能输入(a 和 b)都是 Object 数据类型。第一个输入(a)的类型始终为 Money,可以根据 fCurrency 和 fAmount 属性来定义它。这些定义属性是 Money 类的 public 构造函数的参数。因此,要改变第一个输入,就需要改变对象的定义属性(fCurrency 和 fAmount)。您可能将尝试使用负数、零和正数来提供一定范围内的整数。然后,将使用有效和无效货币标识来改变货币类型。
因为第二个输入(b)属于 Object 数据类型,所以实际上任何类实例都可以作为有效值传入。如果您传入 Money 对象,则可以执行有意义的比较;但是,您也可以尝试传入对比较毫无意义的其它数据类型的数据,看看该方法作出何种响应。
定义数据分区的原则是要尽可能揭示潜在的缺陷。在此示例中,有两个输入参数,它们都是 Object 数据类型。为对象参数定义分区的最佳方法是考虑它们的抽象状态。通常,将对象的抽象状态定义为对其属性值的约束。例如,第一个输入参数(a)可能具有的一些抽象状态包括:
但是,在此事例中,根据您对 equals 方法所执行操作的了解,一个分区应可以满足第一个参数(a)。这是因为此方法的返回值可能与此参数的任何特殊值不是直接相关。因此,可以选择任何测试值,但是只创建一个数据分区。稍后,当定义等价类时,可以通过尝试对此参数使用不同的值来进行多种多样的测试。
对于第二个输入参数(b),很难定义抽象状态,这是因为参数类型 Object 是所有类型的超类,所以实际上可以使用任何类。在此情况下,首先需要根据参数类型来定义数据分区。(对于类型为抽象类或接口的任何参数都是如此)。对于此参数,可以按如下所示标识类型:
因此,输入参数 b 可能具有的一些数据分区是“同一类型”、“其它”和“空”。
接下来,对于第二个输入参数(b)可以再划分数据分区。对于不兼容类型或空对象,可能就不需要再进一步划分数据分区:equals 应该有系统地返回 false。
对于“同一类型”,可能具有几个经过再划分的分区:
下一步将定义等价类和值。等价类是一组输入值,它们全部都应该调用同一行为。如果等价类中的任何单个值使测试失败,则该等价类中的所有其它值都会使测试失败。同样,如果等价类中的任何单个值使测试成功,则该等价类中的所有其它值都应该使测试成功。
借助 Rational® Developer 产品,在测试数据表中定义等价类,它们被称为数据集。使用刚定义的数据分区,可以首先创建等价类,用来处理四个“同一类型”分区与“任何属性值”分区之间的比较。以下屏幕捕获显示了测试数据表,它具有这四个等价类中的两个等价类:
对于第一个输入参数,每个等价类都复用同一数据分区(“任何属性值”)。但是,为了增加此测试的多样性,每个等价类将使用此同一数据分区中的不同值。
接下来,可以为空数据和不兼容数据类型分区创建等价类,如以下屏幕捕获中所示:
最后,可以使用集合和范围来为测试提供更多的数据组合,如以下屏幕捕获中所示。对于金额,可以使用负数、零和正数来提供一定范围内的整数。对于货币,可以尝试使用一组有效的和无效的货币标识。通过这样做,可以极大地增加测试中的数据组合数目,从而可以增大测试覆盖率和发现缺陷的可能性。
但是,请记住,使用集合和范围会产生大量的单个测试,需要花费很长时间来运行。
此示例已经讨论了定义数据分区和等价类来测试方法的多种方式。当定义将调用许多方法的方案时,或者当方法使用对象时,可以遵循相同的过程。但是,在这些情况下,需要减少输入参数的数目,以便将问题分解为容易管理的一些问题。
在这些情况下,使用抽象状态来管理对象就显得非常重要。在尝试划分对象的定义属性之前,始终都应该定义抽象状态。即使在您将对象认为是单个输入参数时,也很容易以许多输入参数来结束。
这意味着当您处理复杂方案时,必须非常谨慎地选择输入参数。通常,为了应付这种复杂情况,应尽量将问题减少到 10 个变量以下。