属性 基于 Java 中自定义排序列表的测试
Property based testing for a custom ordered list in Java
鉴于以下订购要求:
所有以“foo”开头的字符串都应该排在第一位。
所有以“bar”开头的字符串都应该放在最后。
不以“foo”或“bar”开头的字符串也可以出现在列表中。
如何使用基于 属性 的测试来测试上述要求的实现而不头疼?
有没有比下面更优雅的东西:
List<String> strings = Arrays.asList("foo", "bar", "bar1", "jar");
Collections.shuffle(strings);
assertListStartWith(strings, "foo");
assertListEndsWith(strings, "bar", "bar1");
assertThat(strings, hasItem( "jar"));
我假设你有一些带签名的排序器函数
List<String> sortFooBar(List<String> list)
我看到至少 sortFooBar(list)
应该满足的五个属性:
- 保留列表中的所有项目,并且只保留那些项目
- 第一个“foo”之前没有项目
- 第一个和最后一个“foo”之间没有其他项目
- 最后一个“栏”之后没有项目
- 第一个和最后一个“栏”之间没有其他项目
在真正的函数式语言中,这些属性都很容易在 Java 中表达,它需要一些代码。所以这是我对使用 jqwik 作为 PBT 框架和 AssertJ 进行断言的问题的看法:
import java.util.*;
import java.util.function.*;
import org.assertj.core.api.*;
import net.jqwik.api.*;
class MySorterProperties {
@Property
void allItemsAreKept(@ForAll List<@From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
Assertions.assertThat(sorted).containsExactlyInAnyOrderElementsOf(list);
}
@Property
void noItemBeforeFoo(@ForAll List<@From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
int firstFoo = findFirst(sorted, item -> item.startsWith("foo"));
if (firstFoo < 0) return;
Assertions.assertThat(sorted.stream().limit(firstFoo)).isEmpty();
}
@Property
void noItemBetweenFoos(@ForAll List<@From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
int firstFoo = findFirst(sorted, item -> item.startsWith("foo"));
int lastFoo = findLast(sorted, item -> item.startsWith("foo"));
if (firstFoo < 0 && lastFoo < 0) return;
List<String> allFoos = sorted.subList(
Math.max(firstFoo, 0),
lastFoo >= 0 ? lastFoo + 1 : sorted.size()
);
Assertions.assertThat(allFoos).allMatch(item -> item.startsWith("foo"));
}
@Property
void noItemAfterBar(@ForAll List<@From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
int lastBar = findLast(sorted, item -> item.startsWith("bar"));
if (lastBar < 0) return;
Assertions.assertThat(sorted.stream().skip(lastBar + 1)).isEmpty();
}
@Property
void noItemBetweenBars(@ForAll List<@From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
int firstBar = findFirst(sorted, item -> item.startsWith("bar"));
int lastBar = findLast(sorted, item -> item.startsWith("bar"));
if (firstBar < 0 && lastBar < 0) return;
List<String> allFoos = sorted.subList(
Math.max(firstBar, 0),
lastBar >= 0 ? lastBar + 1 : sorted.size()
);
Assertions.assertThat(allFoos).allMatch(item -> item.startsWith("bar"));
}
@Provide
Arbitrary<String> withFooBars() {
Arbitrary<String> postFix = Arbitraries.strings().alpha().ofMaxLength(10);
return Arbitraries.oneOf(
postFix, postFix.map(post -> "foo" + post), postFix.map(post -> "bar" + post)
);
}
int findFirst(List<String> list, Predicate<String> condition) {
for (int i = 0; i < list.size(); i++) {
String item = list.get(i);
if (condition.test(item)) {
return i;
}
}
return -1;
}
int findLast(List<String> list, Predicate<String> condition) {
for (int i = list.size() - 1; i >= 0; i--) {
String item = list.get(i);
if (condition.test(item)) {
return i;
}
}
return -1;
}
}
这是一个符合规范的简单实现:
class MySorter {
static List<String> sortFooBar(List<String> in) {
ArrayList<String> result = new ArrayList<>();
int countFoos = 0;
for (String item : in) {
if (item.startsWith("foo")) {
result.add(0, item);
countFoos++;
} else if (item.startsWith("bar")) {
result.add(result.size(), item);
} else {
result.add(countFoos, item);
}
}
return result;
}
}
在此示例中,属性的代码超过了实现的代码量。这可能是好是坏,具体取决于所需行为的棘手程度。
鉴于以下订购要求:
所有以“foo”开头的字符串都应该排在第一位。
所有以“bar”开头的字符串都应该放在最后。
不以“foo”或“bar”开头的字符串也可以出现在列表中。
如何使用基于 属性 的测试来测试上述要求的实现而不头疼?
有没有比下面更优雅的东西:
List<String> strings = Arrays.asList("foo", "bar", "bar1", "jar");
Collections.shuffle(strings);
assertListStartWith(strings, "foo");
assertListEndsWith(strings, "bar", "bar1");
assertThat(strings, hasItem( "jar"));
我假设你有一些带签名的排序器函数
List<String> sortFooBar(List<String> list)
我看到至少 sortFooBar(list)
应该满足的五个属性:
- 保留列表中的所有项目,并且只保留那些项目
- 第一个“foo”之前没有项目
- 第一个和最后一个“foo”之间没有其他项目
- 最后一个“栏”之后没有项目
- 第一个和最后一个“栏”之间没有其他项目
在真正的函数式语言中,这些属性都很容易在 Java 中表达,它需要一些代码。所以这是我对使用 jqwik 作为 PBT 框架和 AssertJ 进行断言的问题的看法:
import java.util.*;
import java.util.function.*;
import org.assertj.core.api.*;
import net.jqwik.api.*;
class MySorterProperties {
@Property
void allItemsAreKept(@ForAll List<@From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
Assertions.assertThat(sorted).containsExactlyInAnyOrderElementsOf(list);
}
@Property
void noItemBeforeFoo(@ForAll List<@From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
int firstFoo = findFirst(sorted, item -> item.startsWith("foo"));
if (firstFoo < 0) return;
Assertions.assertThat(sorted.stream().limit(firstFoo)).isEmpty();
}
@Property
void noItemBetweenFoos(@ForAll List<@From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
int firstFoo = findFirst(sorted, item -> item.startsWith("foo"));
int lastFoo = findLast(sorted, item -> item.startsWith("foo"));
if (firstFoo < 0 && lastFoo < 0) return;
List<String> allFoos = sorted.subList(
Math.max(firstFoo, 0),
lastFoo >= 0 ? lastFoo + 1 : sorted.size()
);
Assertions.assertThat(allFoos).allMatch(item -> item.startsWith("foo"));
}
@Property
void noItemAfterBar(@ForAll List<@From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
int lastBar = findLast(sorted, item -> item.startsWith("bar"));
if (lastBar < 0) return;
Assertions.assertThat(sorted.stream().skip(lastBar + 1)).isEmpty();
}
@Property
void noItemBetweenBars(@ForAll List<@From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
int firstBar = findFirst(sorted, item -> item.startsWith("bar"));
int lastBar = findLast(sorted, item -> item.startsWith("bar"));
if (firstBar < 0 && lastBar < 0) return;
List<String> allFoos = sorted.subList(
Math.max(firstBar, 0),
lastBar >= 0 ? lastBar + 1 : sorted.size()
);
Assertions.assertThat(allFoos).allMatch(item -> item.startsWith("bar"));
}
@Provide
Arbitrary<String> withFooBars() {
Arbitrary<String> postFix = Arbitraries.strings().alpha().ofMaxLength(10);
return Arbitraries.oneOf(
postFix, postFix.map(post -> "foo" + post), postFix.map(post -> "bar" + post)
);
}
int findFirst(List<String> list, Predicate<String> condition) {
for (int i = 0; i < list.size(); i++) {
String item = list.get(i);
if (condition.test(item)) {
return i;
}
}
return -1;
}
int findLast(List<String> list, Predicate<String> condition) {
for (int i = list.size() - 1; i >= 0; i--) {
String item = list.get(i);
if (condition.test(item)) {
return i;
}
}
return -1;
}
}
这是一个符合规范的简单实现:
class MySorter {
static List<String> sortFooBar(List<String> in) {
ArrayList<String> result = new ArrayList<>();
int countFoos = 0;
for (String item : in) {
if (item.startsWith("foo")) {
result.add(0, item);
countFoos++;
} else if (item.startsWith("bar")) {
result.add(result.size(), item);
} else {
result.add(countFoos, item);
}
}
return result;
}
}
在此示例中,属性的代码超过了实现的代码量。这可能是好是坏,具体取决于所需行为的棘手程度。