1. Overview
1.概述
In this article, we’ll be looking at the concept of Property Testing and its implementation in the vavr-test library.
在这篇文章中,我们将探讨属性测试的概念及其在vavr-test library中的实现。
The Property based testing (PBT) allows us to specify the high-level behavior of a program regarding invariants it should adhere to.
基于属性的测试(PBT)允许我们指定一个程序的高层行为,关于它应该遵守的不变性。
2. What Is Property Testing?
2.什么是财产测试?
A property is the combination of an invariant with an input values generator. For each generated value, the invariant is treated as a predicate and checked whether it yields true or false for that value.
属性是不变量与输入值生成器的组合。对于每个生成的值,不变量被视为一个谓词,并检查它对该值产生的结果是真还是假。
As soon as there is one value which yields false, the property is said to be falsified, and checking is aborted. If a property cannot be invalidated after a specific amount of sample data, the property is assumed to be satisfied.
只要有一个值产生了错误,就说该属性被伪造了,并且检查被中止。如果一个属性在经过特定数量的样本数据后不能被证伪,那么这个属性就被认为是被满足的。
Thanks to that behavior, our test fail-fast if a condition is not satisfied without doing unnecessary work.
由于这种行为,我们的测试在条件不满足的情况下可以快速失败,而不做不必要的工作。
3. Maven Dependency
3.Maven的依赖性
First, we need to add a Maven dependency to the vavr-test library:
首先,我们需要为vavr-test库添加一个Maven依赖项。
<dependency>
<groupId>io.vavr</groupId>
<artifactId>jvavr-test</artifactId>
<version>${vavr.test.version}</version>
</dependency>
<properties>
<vavr.test.version>2.0.5</vavr.test.version>
</properties>
4. Writing Property Based Tests
4.编写基于属性的测试
Let’s consider a function that returns a stream of strings. It is an infinite stream of 0 upwards that maps numbers to the strings based on the simple rule. We are using here an interesting Vavr feature called the Pattern Matching:
让我们考虑一个返回字符串流的函数。它是一个无限的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($(), "")));
}
Writing the unit test for such method will be error prone because there is a high probability that we’ll forget about some edge case and basically not cover all possible scenarios.
为这样的方法编写单元测试会很容易出错,因为我们很有可能会忘记一些边缘情况,基本上没有涵盖所有可能的情况。
Fortunately, we can write a property-based test that will cover all edge cases for us. First, we need to define which kind of numbers should be an input for our test:
幸运的是,我们可以写一个基于属性的测试,它将为我们涵盖所有的边缘情况。首先,我们需要定义哪类数字应该是我们测试的输入。
Arbitrary<Integer> multiplesOf2 = Arbitrary.integer()
.filter(i -> i > 0)
.filter(i -> i % 2 == 0 && i % 5 != 0);
We specified that input number needs to fulfill two conditions – it needs to be greater that zero, and needs to be dividable by two without remainder but not by five.
我们规定,输入的数字需要满足两个条件–它需要大于零,并且需要可以无余数地除以2,但不能除以5。
Next, we need to define a condition that checks if a function that is tested returns proper value for given argument:
接下来,我们需要定义一个条件,检查被测试的函数是否为给定参数返回适当的值。
CheckedFunction1<Integer, Boolean> mustEquals
= i -> stringsSupplier().get(i).equals("DividedByTwoWithoutRemainder");
To start a property-based test, we need to use the Property class:
要开始一个基于属性的测试,我们需要使用属性类。
CheckResult result = Property
.def("Every second element must equal to DividedByTwoWithoutRemainder")
.forAll(multiplesOf2)
.suchThat(mustEquals)
.check(10_000, 100);
result.assertIsSatisfied();
We’re specifying that, for all arbitrary integers that are multiples of 2, the mustEquals predicate must be satisfied. The check() method takes a size of a generated input and number of times that this test will be run.
我们指定,对于所有2的倍数的任意整数,必须满足mustEquals谓词。check() 方法需要一个生成的输入的大小和这个测试将被运行的次数。
We can quickly write another test that will verify if the stringsSupplier() function returns a DividedByTwoAndFiveWithoutRemainder string for every input number that is divisible by two and five without the remainder.
我们可以快速编写另一个测试,验证stringsSupplier()函数是否为每个输入的数字返回一个DividedByTwoAndFiveWithoutRemainder字符串,而这个字符串是可以被2和5整除的,没有余数。
The Arbitrary supplier and CheckedFunction need to be changed:
Arbitrary供应商和CheckedFunction需要被改变。
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");
Then we can run the property-based test for one thousand iterations:
然后我们可以运行基于属性的测试,进行一千次迭代。
Property.def("Every fifth element must equal to DividedByTwoAndFiveWithoutRemainder")
.forAll(multiplesOf5)
.suchThat(mustEquals)
.check(10_000, 1_000)
.assertIsSatisfied();
5. Conclusion
5.结论
In this quick article, we had a look at the concept of property based testing.
在这篇快速文章中,我们看了一下基于属性的测试的概念。
We created tests using the vavr-test library; we used the Arbitrary, CheckedFunction, and Property class to define property-based test using vavr-test.
我们使用vavr-test库创建了测试;我们使用Arbitrary、CheckedFunction和Property类来定义基于属性的测试,使用vavr-test。
The implementation of all these examples and code snippets can be found over on GitHub – this is a Maven project, so it should be easy to import and run as it is.
所有这些例子和代码片段的实现都可以在GitHub上找到over–这是一个Maven项目,所以应该很容易导入并按原样运行。