
本文探讨了在jqwik中使用@forall注解与@provide方法处理集合类型参数时常见的陷阱。核心内容包括:明确@domain注解的正确作用域(应应用于属性方法或测试类,而非@provide方法本身),以及当@provide方法需要生成集合类型的arbitrary时,应避免在参数中使用@forall,转而直接在方法体内构建集合arbitrary,以避免潜在的arbitrary查找失败和不必要的扁平化映射。通过遵循这些指导原则,可以有效避免cannotfindarbitraryexception,并编写出更健壮、意图更明确的基于属性的测试。
在jqwik中进行基于属性的测试时,我们经常需要自定义复杂类型的任意值生成器(Arbitrary)。@Provide方法是实现这一目标的关键,它允许我们定义如何生成特定类型的任意值。然而,当尝试在@Provide方法中使用@ForAll注解来接收集合类型参数时,开发者可能会遇到CannotFindArbitraryException,这通常是由于对@Domain注解的作用域和@Provide方法的预期用途存在误解。
考虑以下场景,我们有一个Name领域模型,并希望生成一个Set<String>,其中包含解析后的Name对象。一个常见的错误尝试是在@Provide方法中这样定义:
// 领域模型
public class Name {
public final String first;
public final String last;
public Name(String f, String l) {
this.first = f;
this.last = l;
}
}
// jqwik领域上下文
public class NameDomain extends DomainContextBase {
@Provide
public Arbitrary<Name> arbName() {
return Combinators.combine(
Arbitraries.strings().alpha(),
Arbitraries.strings().alpha()
).as(Name::new);
}
}
// 属性测试类中的错误尝试
public class NameProperties {
@Provide
@Domain(NameDomain.class) // 错误:@Domain不应在此处
public Arbitrary<Set<String>> namesToParse(
@ForAll @Size(min = 1, max = 4) Set<Name> names) {
// 假设此处将Set<Name>转换为Set<String>
// ... code here
return Arbitraries.just(new HashSet<>()); // 示例返回
}
@Property
public void namesAreParsed(@ForAll("namesToParse") Set<String> names) {
// ... code here
}
}当运行上述代码时,jqwik会抛出CannotFindArbitraryException,指出无法为namesToParse方法中Set<Name>类型的参数找到Arbitrary。这背后的原因是@Domain注解的错误放置,以及对@Provide方法中@ForAll参数处理机制的误解。
@Domain注解的目的是将一个DomainContext关联到属性方法或整个测试类,从而使该上下文中的@Provide方法生成的Arbitrary对这些属性方法可见。它不应直接应用于@Provide方法本身。
正确的@Domain注解放置方式有两种:
应用于属性方法:
public class NameProperties {
// ... 其他代码 ...
@Property
@Domain(NameDomain.class) // 正确:应用于属性方法
public void namesAreParsed(@ForAll("namesToParse") Set<String> names) {
// ... code here
}
}应用于整个测试类:
@Domain(NameDomain.class) // 正确:应用于测试类
class NameProperties {
// ... 其他代码 ...
@Property
public void namesAreParsed(@ForAll("namesToParse") Set<String> names) {
// ... code here
}
}通过将@Domain注解放置在正确的位置,NameDomain中提供的Arbitrary<Name>就能被NameProperties类中的属性方法识别和使用。然而,即使@Domain放置正确,原始的@Provide方法定义仍然存在问题。
在@Provide方法中,当您需要生成一个复杂类型(如集合)的Arbitrary时,通常不建议在其参数中使用@ForAll。这是因为在@Provide方法中使用@ForAll参数,jqwik会采用扁平化映射(flat mapping)的方式来处理这些参数,这使得逻辑变得复杂,并且可能不是您期望的行为。
对于生成集合类型的Arbitrary,最清晰和推荐的方法是在@Provide方法体内直接构建所需的Arbitrary。您可以使用Arbitraries.defaultFor(Type.class)来获取指定类型的默认Arbitrary,然后利用其链式调用方法来构建集合。
以下是重构后的namesToParse方法示例:
import net.jqwik.api.*;
import net.jqwik.api.arbitraries.SetArbitrary;
import net.jqwik.api.domains.DomainContextBase;
import net.jqwik.api.domains.Domain;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
// 领域模型
public class Name {
public final String first;
public final String last;
public Name(String f, String l) {
this.first = f;
this.last = l;
}
}
// jqwik领域上下文
public class NameDomain extends DomainContextBase {
@Provide
public Arbitrary<Name> arbName() {
return Combinators.combine(
Arbitraries.strings().alpha().ofMinLength(1).ofMaxLength(10), // 增加长度限制
Arbitraries.strings().alpha().ofMinLength(1).ofMaxLength(10)
).as(Name::new);
}
}
// 属性测试
@Domain(NameDomain.class) // @Domain应用于测试类
public class NameProperties {
@Provide
public Arbitrary<Set<String>> namesToParse() {
// 直接在方法体内构建Set<Name>的Arbitrary
SetArbitrary<Name> namesArbitrary = Arbitraries.defaultFor(Name.class)
.set().ofMinSize(1).ofMaxSize(4);
// 将Set<Name>映射为Set<String>
return namesArbitrary.map(nameSet ->
nameSet.stream()
.map(n -> n.first + " " + n.last) // 示例:将Name对象转换为字符串
.collect(Collectors.toSet())
);
}
@Property
public void namesAreParsed(@ForAll("namesToParse") Set<String> names) {
// 确保生成的集合不为空且大小在预期范围内
System.out.println("Generated names: " + names);
Assertions.assertThat(names).isNotEmpty();
Assertions.assertThat(names.size()).isBetween(1, 4);
// ... 实际的解析和验证逻辑
}
}在这个重构后的@Provide方法中:
这种方式清晰地表达了namesToParse方法的目标:它提供了一个生成Set<String>的Arbitrary,并且这个Set<String>是基于Name对象生成的。
在使用jqwik进行高级属性测试时,请牢记以下几点:
遵循这些指导原则,您将能够更有效地利用jqwik的强大功能,编写出健壮且可读性强的基于属性的测试。
以上就是jqwik中@Provide方法结合@ForAll处理集合类型参数的最佳实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号