
本文深入探讨了jqwik中`arbitrary`的组合与复用策略,旨在帮助开发者高效生成复杂测试数据。文章首先纠正了`@forall`注解在`@provide`方法和领域中的使用误区,随后详细介绍了在不同场景下共享`arbitrary`的几种方法,包括静态函数、基于类型解析的自定义值类型,以及通过自定义注解实现更精细化的数据生成。通过丰富的示例代码,本文为构建可维护、可扩展的属性测试提供了实践指导。
在属性测试中,我们经常需要为复杂对象生成测试数据。jqwik提供了强大的Arbitrary机制来定义数据生成逻辑。例如,我们可能有一个包含多个字符串字段的复杂类MyComplexClass:
public class MyComplexClass {
  private final String id;        // 正整数形式
  private final String recordId;  // UUID形式
  private final String creatorId; // 正整数形式
  private final String editorId;  // 正整数形式
  private final String nonce;     // UUID形式
  private final String payload;   // 随机字符串
  // 假设存在一个Builder模式或全参数构造函数
  public static Builder newBuilder() { return new Builder(); }
  public static class Builder {
    private String id;
    private String recordId;
    private String creatorId;
    private String editorId;
    private String nonce;
    private String payload;
    public Builder setId(String id) { this.id = id; return this; }
    public Builder setRecordId(String recordId) { this.recordId = recordId; return this; }
    public Builder setCreatorId(String creatorId) { this.creatorId = creatorId; return this; }
    public Builder setEditorId(String editorId) { this.editorId = editorId; return this; }
    public Builder setNonce(String nonce) { this.nonce = nonce; return this; }
    public Builder setPayload(String payload) { this.payload = payload; return this; }
    public MyComplexClass build() {
      return new MyComplexClass(id, recordId, creatorId, editorId, nonce, payload);
    }
  }
  private MyComplexClass(String id, String recordId, String creatorId, String editorId, String nonce, String payload) {
    this.id = id;
    this.recordId = recordId;
    this.creatorId = creatorId;
    this.editorId = editorId;
    this.nonce = nonce;
    this.payload = payload;
  }
  // Getter方法省略
  @Override
  public String toString() {
    return "MyComplexClass{" +
           "id='" + id + '\'' +
           ", recordId='" + recordId + '\'' +
           ", creatorId='" + creatorId + '\'' +
           ", editorId='" + editorId + '\'' +
           ", nonce='" + nonce + '\'' +
           ", payload='" + payload + '\'' +
           '}';
  }
}为了生成MyComplexClass的实例,我们首先需要定义其各个字段的Arbitrary。例如,可以创建生成UUID风格字符串和正整数风格字符串的Arbitrary<String>:
import net.jqwik.api.*;
import net.jqwik.api.arbitraries.StringArbitrary;
import net.jqwik.api.domains.DomainContextBase;
import java.util.Set;
import java.util.UUID;
public class MyArbitraries {
    public static Arbitrary<String> arbUuidString() {
        return Combinators.combine(
                        Arbitraries.longs(), Arbitraries.longs(), Arbitraries.of(Set.of('8', '9', 'a', 'b')))
                .as((l1, l2, y) -> {
                    // 模拟UUID V4的格式,第14位为'4',第19位为'8','9','a','b'之一
                    StringBuilder b = new StringBuilder(new UUID(l1, l2).toString());
                    b.setCharAt(14, '4');
                    b.setCharAt(19, y);
                    return b.toString(); // 返回String,而不是UUID对象
                });
    }
    public static Arbitrary<String> arbNumericIdString() {
        // 生成非负短整数,并转换为字符串
        return Arbitraries.shorts().map(Math::abs).map(i -> "" + i);
    }
}然后,我们可以使用Builders.withBuilder来组合这些基础Arbitrary以生成MyComplexClass:
// 假设MyArbitraries是可访问的
public class MyComplexClassDomain extends DomainContextBase {
    @Provide
    public Arbitrary<MyComplexClass> arbMyComplexClass() {
        return Builders.withBuilder(MyComplexClass::newBuilder)
                .use(MyArbitraries.arbNumericIdString()).in(MyComplexClass.Builder::setId)
                .use(MyArbitraries.arbUuidString()).in(MyComplexClass.Builder::setRecordId)
                .use(MyArbitraries.arbNumericIdString()).in(MyComplexClass.Builder::setCreatorId)
                .use(MyArbitraries.arbNumericIdString()).in(MyComplexClass.Builder::setEditorId)
                .use(MyArbitraries.arbUuidString()).in(MyComplexClass.Builder::setNonce)
                .use(Arbitraries.strings().alpha().ofLength(10, 20)).in(MyComplexClass.Builder::setPayload)
                .build(MyComplexClass.Builder::build);
    }
}在jqwik中,@ForAll注解不仅仅局限于@Property测试方法。它同样可以在@Provide注解的方法以及@Domain中发挥作用,允许Arbitrary的提供者方法之间相互依赖,从而构建更复杂的生成逻辑。
需要注意的是,@ForAll("name")中的字符串引用是局部解析的,仅限于当前类、其父类和包含类。这是为了避免全局字符串引用可能带来的歧义和维护困难。
以下是一个示例,展示了@Provide方法如何通过@ForAll相互协作:
import net.jqwik.api.*;
import net.jqwik.api.domains.Domain;
import net.jqwik.api.domains.DomainContextBase;
class MyDomain extends DomainContextBase {
    // 提供一个Arbitrary<String>,其长度由另一个@Provide方法"lengths"提供
    @Provide
    public Arbitrary<String> strings(@ForAll("lengths") int length) {
        return Arbitraries.strings().alpha().ofLength(length);
    }
    // 提供一个Arbitrary<Integer>用于生成字符串长度
    @Provide
    public Arbitrary<Integer> lengths() {
        return Arbitraries.integers().between(3, 10);
    }
    // 此方法不会被strings()方法使用,因为strings()明确引用了"lengths"
    @Provide
    public Arbitrary<Integer> negatives() {
        return Arbitraries.integers().between(-100, -10);
    }
}
class MyProperties {
    @Property(tries = 5)
    @Domain(MyDomain.class) // 指定使用MyDomain
    public void printOutAlphaStringsWithLength3to10(@ForAll String stringsFromDomain) {
        // stringsFromDomain将由MyDomain中的strings()方法提供,其长度介于3到10之间
        System.out.println("Generated String: " + stringsFromDomain + ", Length: " + stringsFromDomain.length());
    }
}当我们需要在不同的测试类或领域中复用Arbitrary定义时,有几种不同的策略可以选择。
在单个领域内,或者当多个相关领域继承自同一个父类时,将Arbitrary定义为静态方法并直接调用是一种简单有效的共享方式。
优点:
缺点:
在第1节的MyArbitraries示例中,arbUuidString()和arbNumericIdString()就是静态函数,可以在MyComplexClassDomain中直接调用。
// MyArbitraries类保持不变,提供静态Arbitrary方法
// MyComplexClassDomain中直接调用静态方法
public class MyComplexClassDomain extends DomainContextBase {
    @Provide
    public Arbitrary<MyComplexClass> arbMyComplexClass() {
        return Builders.withBuilder(MyComplexClass::newBuilder)
                .use(MyArbitraries.arbNumericIdString()).in(MyComplexClass.Builder::setId)
                .use(MyArbitraries.arbUuidString()).in(MyComplexClass.Builder::setRecordId)
                // ... 其他字段
                .build(MyComplexClass.Builder::build);
    }
}当需要在不相关的领域之间共享Arbitrary,并且这些Arbitrary生成的数据具有特定的业务含义时,引入自定义值类型(Value Type)是一种推荐的实践。通过为特定类型的字符串或整数创建包装类,jqwik可以根据类型自动解析并提供相应的Arbitrary。
优点:
缺点:
示例:
定义值类型:
// 定义表示特定业务含义的字符串类型
public record MyId(String value) {}
public record MyRecordId(String value) {}
public record MyCreatorId(String value) {}
// ... 其他需要区分的类型在领域中提供这些值类型对应的Arbitrary:
public class MyDomainWithTypes extends DomainContextBase {
    @Provide
    public Arbitrary<MyId> idArbitrary() {
        return MyArbitraries.arbNumericIdString().map(MyId::new);
    }
    @Provide
    public Arbitrary<MyRecordId> recordIdArbitrary() {
        return MyArbitraries.arbUuidString().map(MyRecordId::new);
    }
    @Provide
    public Arbitrary<MyCreatorId> creatorIdArbitrary() {
        return MyArbitraries.arbNumericIdString().map(MyCreatorId::new);
    }
    @Provide
    public Arbitrary<MyComplexClass> arbMyComplexClassWithTypeFields() {
        return Builders.withBuilder(MyComplexClass::newBuilder)
                // jqwik会根据类型自动找到对应的Arbitrary,然后通过lambda进行转换
                .use(Arbitraries.defaultFor(MyId.class)).in((builder, id) -> builder.setId(id.value()))
                .use(Arbitraries.defaultFor(MyRecordId.class)).in((builder, recordId) -> builder.setRecordId(recordId.value()))
                .use(Arbitraries.defaultFor(MyCreatorId.class)).in((builder, creatorId) -> builder.setCreatorId(creatorId.value()))
                // ... 其他字段
                .use(Arbitraries.strings().alpha().ofLength(10, 20)).in(MyComplexClass.Builder::setPayload)
                .build(MyComplexClass.Builder::build);
    }
}在测试中使用:
class MyPropertiesWithTypes {
    @Property(tries = 5)
    @Domain(MyDomainWithTypes.class)
    public void testMyComplexClassGeneration(@ForAll MyComplexClass complexClass) {
        System.out.println("Generated Complex以上就是jqwik中Arbitrary组合与复用策略深度解析的详细内容,更多请关注php中文网其它相关文章!
                        
                        每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
                Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号