Property Testing Example With Vavr – 使用Vavr的财产测试实例

最后修改: 2017年 3月 22日

中文/混合/英文(键盘快捷键:t)

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、CheckedFunctionProperty类来定义基于属性的测试,使用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项目,所以应该很容易导入并按原样运行。