1. 概述

本文将探讨属性测试(Property Testing)的概念及其在 vavr-test 库中的实现。属性测试允许我们通过定义程序必须遵守的不变式(invariants)来描述其高层行为。

2. 什么是属性测试?

属性是不变式输入值生成器的组合。对于每个生成的值,不变式会作为谓词进行验证,判断该值是否满足条件。

一旦出现使不变式为假的值,属性即被证伪(falsified),测试立即终止。若在指定数量的样本数据后属性仍未被证伪,则认为该属性满足条件。

这种机制确保测试在条件不满足时快速失败,避免执行不必要的检查。

3. Maven 依赖

首先添加 vavr-test 库的 Maven 依赖:

<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr-test</artifactId>
    <version>${vavr.test.version}</version>
</dependency>

<properties>
    <vavr.test.version>2.0.5</vavr.test.version> 
</properties>

⚠️ 注意:原文中的 artifactId 存在笔误(jvavr-test),已修正为正确值 vavr-test

4. 编写属性测试

考虑一个返回字符串流的函数。这是一个从 0 开始的无限流,通过简单规则将数字映射为字符串。这里使用了 Vavr 的模式匹配(Pattern Matching)特性:

private static Predicate<Integer> divisibleByTwo = i -> i % 2 == 0;
private static Predicate<Integer> divisibleByFive = i -> i % 5 == 0;

private Stream<String> stringsSupplier() {
    return Stream.from(0).map(i -> Match(i).of(
      Case($(divisibleByFive.and(divisibleByTwo)), "DividedByTwoAndFiveWithoutRemainder"),
      Case($(divisibleByFive), "DividedByFiveWithoutRemainder"),
      Case($(divisibleByTwo), "DividedByTwoWithoutRemainder"),
      Case($(), "")));
}

为这类方法编写单元测试容易出错,因为很可能遗漏边界条件,无法覆盖所有场景。幸运的是,我们可以用属性测试自动覆盖所有边界情况。

4.1 定义输入生成器

首先定义测试所需的输入类型:

Arbitrary<Integer> multiplesOf2 = Arbitrary.integer()
  .filter(i -> i > 0)
  .filter(i -> i % 2 == 0 && i % 5 != 0);

这里指定输入必须满足两个条件:

  • 大于零
  • 能被 2 整除但不能被 5 整除

4.2 定义验证条件

接下来定义验证函数,检查被测方法对给定参数是否返回正确值:

CheckedFunction1<Integer, Boolean> mustEquals
  = i -> stringsSupplier().get(i).equals("DividedByTwoWithoutRemainder");

4.3 执行属性测试

使用 Property 类启动测试:

CheckResult result = Property
  .def("Every second element must equal to DividedByTwoWithoutRemainder")
  .forAll(multiplesOf2)
  .suchThat(mustEquals)
  .check(10_000, 100);

result.assertIsSatisfied();

关键参数说明:

  • forAll(multiplesOf2):对所有满足条件的整数
  • suchThat(mustEquals):必须满足验证条件
  • check(10_000, 100):生成 10,000 个输入值,运行 100 次测试

4.4 扩展测试用例

快速编写另一个测试:验证当输入能被 2 和 5 整除时,函数应返回 "DividedByTwoAndFiveWithoutRemainder"

调整生成器和验证函数:

Arbitrary<Integer> multiplesOf5 = Arbitrary.integer()
  .filter(i -> i > 0)
  .filter(i -> i % 5 == 0 && i % 2 == 0);

CheckedFunction1<Integer, Boolean> mustEquals
  = i -> stringsSupplier().get(i).endsWith("DividedByTwoAndFiveWithoutRemainder");

执行 1,000 次迭代测试:

Property.def("Every fifth element must equal to DividedByTwoAndFiveWithoutRemainder")
  .forAll(multiplesOf5)
  .suchThat(mustEquals)
  .check(10_000, 1_000)
  .assertIsSatisfied();

5. 总结

本文快速介绍了属性测试的核心概念。我们使用 vavr-test 库创建了测试,通过 ArbitraryCheckedFunctionProperty 类实现属性测试。

所有示例代码可在 GitHub 项目 中获取(Maven 项目,可直接导入运行)。

关键优势

✅ 自动覆盖边界条件
✅ 快速失败机制
✅ 减少手工测试用例编写负担
✅ 适合复杂逻辑验证

适用场景

  • 复杂算法验证
  • 数据转换逻辑测试
  • 状态机行为检查
  • 需要覆盖大量输入组合的情况

原始标题:Property Testing Example With Vavr | Baeldung

» 下一篇: JSONAssert 介绍