随机测试
Random Testing (RT)
Randoop: Automatic unit test generation for Java
Pacheco C, Lahiri S K, Ernst M D, et al. Feedback-directed random test generation. International Conference on Software Engineering (ICSE), 2007: 75-84.
随机测试是一个原理非常简单、但在很多领域有广泛应用的软件测试方法。Randoop 是一个非常有代表性的随机测试工具,其在随机构造对象方法调用的基础上,进一步利用程序执行反馈指导调用方法序列的增量生成,从而高效探索面向对象程序的状态空间。
例如,假设有如下一个购物车模拟程序,其包含 Product.java 和 ShoppingCart.java 两个类。Product 表示价格可变的商品,ShoppingCart 用列表保存商品,并支持添加商品、查询数量、修改指定商品价格和结账等操作。ShoppingCart 中的 refreshPrice() 存在一个 “修改商品价格后没有更新缓存” 的故障,该故障在最终执行 checkout() 时会导致商品总价和缓存价格不符,并抛出 IllegalStateException 异常。
// Product.java
public final class Product {
private int price;
public Product(int price) {
this.price = Math.max(0, price);
}
public int price() {
return price;
}
public void changePrice(int newPrice) {
price = Math.max(0, newPrice);
}
}
// ShoppingCart.java
import java.util.ArrayList;
import java.util.List;
public final class ShoppingCart {
private final List<Product> products = new ArrayList<>();
private long cachedTotal;
public boolean add(Product product) {
if (product == null) {
return false;
}
products.add(product);
cachedTotal += product.price();
return true;
}
public int size() {
return products.size();
}
/** Changes one product's price but intentionally forgets to update the cache. */
public boolean refreshPrice(int index, int newPrice) {
if (index < 0 || index >= products.size()) {
return false;
}
products.get(index).changePrice(newPrice);
return true;
}
public long checkout() {
long currentTotal = 0;
for (Product product : products) {
currentTotal += product.price();
}
if (cachedTotal != currentTotal) {
throw new IllegalStateException("cached total is stale");
}
return cachedTotal;
}
}
Makefile
RANDOOP_JAR := randoop-4.3.4/randoop-all-4.3.4.jar
CLASSPATH := $(RANDOOP_JAR):build/classes
.PHONY: generate minimize run clean
generate:
rm -rf build generated-tests
mkdir -p build/classes generated-tests
javac -d build/classes Product.java ShoppingCart.java
java -classpath "$(CLASSPATH)" randoop.main.Main gentests \
--testclass=Product --testclass=ShoppingCart \
--time-limit=5 \
--unchecked-exception=ERROR \
--junit-output-dir=generated-tests
minimize:
java -classpath "$(RANDOOP_JAR)" randoop.main.Main minimize \
--suitepath=generated-tests/ErrorTest0.java \
--suiteclasspath="$(CLASSPATH)" \
--minimizetimeout=30
run: generate
mkdir -p build/test-classes
javac -classpath "$(CLASSPATH)" \
-d build/test-classes generated-tests/*.java
java -classpath "$(CLASSPATH):build/test-classes" \
org.junit.runner.JUnitCore ErrorTest
clean:
rm -rf build generated-tests
Randoop 是专门为 Java 程序设计的测试用例生成工具,因此需要首先安装和配置好 Java 运行环境。 然后,可以运行如下命令来编译待测程序并使用 Randoop 生成测试用例:
make generate
该步骤会同时生成 Error-Revealing Test (检测当前程序中的错误) 和 Regression Test (检测后续变更过程中引入的错误),所生成的 JUnit 测试用例代码文件将保存在 generated-tests 目录中。
运行生成的 JUnit 测试用例:
make run
在生成的 Error-Revealing Test (ErrorTest0.java) 触发故障时,Randoop 可进一步对其进行约简以获得 “最小故障触发序列”:
make minimize
例如,对于上述待测程序,触发故障的一个最小执行序列为:
ShoppingCart cart = new ShoppingCart();
Product product = new Product(0);
cart.add(product);
cart.refreshPrice(0, (int) '4');
cart.checkout();
自适应随机测试
Adaptive Random Testing (ART)
Chen T Y, Kuo F C, Merkel R G, et al. Adaptive random testing: The art of test case diversity. Journal of Systems and Software, 2010, 83(1): 60-66.
自适应随机测试是一种通过增加测试用例间多样性 (Diversity),即使得测试用例在整个测试输入空间中更加均匀分布 (Evenly Spread),的方式来改进随机测试的方法。
这里提供了一个在二维测试输入空间中模拟 RT 和 FSCS-ART 测试用例生成过程的网页,可用于观察和比较不同测试用例生成方法的多样性 (Diversity 度量) 和测试开销 (报告 Test Generation 和 Execution 开销)。