
本文深入探讨了%ignore_a_1%qwik中`arbitrary`的组合与复用策略,旨在帮助开发者高效生成复杂测试数据。文章首先纠正了`@forall`注解在`@provide`方法和领域中的使用误区,随后详细介绍了在不同场景下共享`arbitrary`的几种方法,包括静态函数、基于类型解析的自定义值类型,以及通过自定义注解实现更精细化的数据生成。通过丰富的示例代码,本文为构建可维护、可扩展的属性测试提供了实践指导。
1. jqwik中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:
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 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 arbNumericIdString() { // 生成非负短整数,并转换为字符串 return Arbitraries.shorts().map(Math::abs).map(i -> "" + i); }}
然后,我们可以使用Builders.withBuilder来组合这些基础Arbitrary以生成MyComplexClass:
// 假设MyArbitraries是可访问的public class MyComplexClassDomain extends DomainContextBase { @Provide public Arbitrary 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); }}
2. 理解@ForAll与@Provide的协同作用
在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,其长度由另一个@Provide方法"lengths"提供 @Provide public Arbitrary strings(@ForAll("lengths") int length) { return Arbitraries.strings().alpha().ofLength(length); } // 提供一个Arbitrary用于生成字符串长度 @Provide public Arbitrary lengths() { return Arbitraries.integers().between(3, 10); } // 此方法不会被strings()方法使用,因为strings()明确引用了"lengths" @Provide public Arbitrary 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()); }}
3. Arbitrary的共享策略
当我们需要在不同的测试类或领域中复用Arbitrary定义时,有几种不同的策略可以选择。
3.1 简单共享:静态Arbitrary函数
在单个领域内,或者当多个相关领域继承自同一个父类时,将Arbitrary定义为静态方法并直接调用是一种简单有效的共享方式。
优点:
实现简单,易于理解。对于局部或紧密相关的Arbitrary共享非常方便。
缺点:
Melodio
Melodio是全球首款个性化AI流媒体音乐平台,能够根据用户场景或心情生成定制化音乐。
110 查看详情
当需要跨越不相关的领域共享时,可能导致类型不明确,或者需要手动传递大量参数。所有共享的Arbitrary都必须是静态的,这在某些设计中可能不理想。
在第1节的MyArbitraries示例中,arbUuidString()和arbNumericIdString()就是静态函数,可以在MyComplexClassDomain中直接调用。
// MyArbitraries类保持不变,提供静态Arbitrary方法// MyComplexClassDomain中直接调用静态方法public class MyComplexClassDomain extends DomainContextBase { @Provide public Arbitrary arbMyComplexClass() { return Builders.withBuilder(MyComplexClass::newBuilder) .use(MyArbitraries.arbNumericIdString()).in(MyComplexClass.Builder::setId) .use(MyArbitraries.arbUuidString()).in(MyComplexClass.Builder::setRecordId) // ... 其他字段 .build(MyComplexClass.Builder::build); }}
3.2 基于类型解析的共享:引入值类型
当需要在不相关的领域之间共享Arbitrary,并且这些Arbitrary生成的数据具有特定的业务含义时,引入自定义值类型(Value Type)是一种推荐的实践。通过为特定类型的字符串或整数创建包装类,jqwik可以根据类型自动解析并提供相应的Arbitrary。
优点:
类型安全:明确了数据的业务含义,避免了原始类型(如String)的滥用。自动解析:jqwik能够基于类型自动查找并使用匹配的ArbitraryProvider或@Provide方法。可读性强:代码意图更清晰。
缺点:
可能导致创建大量的简单包装类,增加代码量。如果原始类(如MyComplexClass)无法修改以接受这些值类型,则需要在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 idArbitrary() { return MyArbitraries.arbNumericIdString().map(MyId::new); } @Provide public Arbitrary recordIdArbitrary() { return MyArbitraries.arbUuidString().map(MyRecordId::new); } @Provide public Arbitrary creatorIdArbitrary() { return MyArbitraries.arbNumericIdString().map(MyCreatorId::new); } @Provide public Arbitrary 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组合与复用策略深度解析的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/938699.html
微信扫一扫
支付宝扫一扫