一种常用的测试技术是分析一个类可采用的不同抽象状态。通常,将对象的状态定义为对其属性值的约束。根据对象的状态不同,调用某些方法时可能有效也可能无效,或者方法的行为可能会改变。
通常来讲,使用基于状态的测试技术的过程如下所示:
为了了解这些过程,我们以 JUnit 中的 MoneyBag 类为例。
class MoneyBag implements IMoney { private Vector fMonies= new Vector(5); public IMoney add(IMoney m) { return m.addMoneyBag(this); } public IMoney addMoney(Money m) { return MoneyBag.create(m, this); } public IMoney addMoneyBag(MoneyBag s) { return MoneyBag.create(s, this); } void appendMoney(Money aMoney) { if (aMoney.isZero()) return; IMoney old= findMoney(aMoney.currency()); if (old == null) { fMonies.addElement(aMoney); return; } fMonies.removeElement(old); IMoney sum= old.add(aMoney); if (sum.isZero()) return; fMonies.addElement(sum); } private Money findMoney(String currency) { for (Enumeration e=fMonies.elements();e.hasMoreElements();) { Money m= (Money) e.nextElement(); if (m.currency().equals(currency)) return m; } return null; } private boolean contains(Money m) { Money found= findMoney(m.currency()); if (found == null) return false; return found.amount() == m.amount(); } }
在使用基于状态的测试技术时,第一步是定义状态。从第二行代码中,您可以了解到 MoneyBag 类可以容纳 0 到 5 个 Money 对象。
private Vector fMonies= new Vector(5);
根据此分析,可以创建具有下列状态的状态模型:
在此示例中,可以对 fMonies 属性定义下列约束,如下表中所示:
状态 | 约束 |
---|---|
EmptyBag | fMonies.size()==0 |
PartiallyFullBag | (fMonies.size()>0) && (fMonies.size()<5) |
FullBag | fMonies.size()==5 |
尽管不是始终都需要正式定义这些状态,但当您定义测试数据时,或者您想在特定方案中检查对象状态时,定义这些状态还是很有用的。
下一步是定义状态之间可能存在的转换,并确定触发从一种状态转换为另一种状态的事件。通常,当您测试一个类时,在调用方法时就会触发转换。例如,调用 appendMoney 时就会触发从 EmptyBag 状态转换为 PartiallyFullBag 状态。
因此,可以将某些可能存在的转换定义为如下所示:
总的来说,对于每种已标识的状态,您应该列示:
测试通常由这样一些方案组成,这些方案沿着状态机中给定的路径来使用对象。由于状态机中可能具有的路径数目通常是无穷大的,因此,要测试每个可能存在的路径是不切实际的。但是,您务必执行下列任务:
只要有可能,在整个方案中检查您正在测试的对象的状态,以确保您已经定义的理论状态模型实际上就是由正在测试的类实现的状态模型。完成这些转换之后,可以通过按随机顺序调用方法并检查是否从未违反类不变量来测试健壮程度。例如,MoneyBag 类应该始终是一组从来不属于同一货币的 Money 对象。
可以使用与产品一起提供的基于方案的测试模式来创建测试方案。
最后,需要为每种单个的状态选择测试值。选择唯一的测试值,并且不要复用您先前在其它测试的上下文中已经使用的值。此策略使您的测试套件更加多样化,并且更有可能检测出错误。有关定义适当的测试值的详细信息,请参阅数据分区技术。