随机测试

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 开销)。