This is the second part of a series about testing with ScalaCheck tool. The first part describes Generators, which generate data for your parameters in the code. While this part does a deep dive into Properties.
这是关于使用ScalaCheck工具进行测试的系列的第二部分。 第一部分描述生成器,生成器为代码中的参数生成数据。 虽然这部分深入探讨了Properties 。
Anecdote
轶事
A tester comes into a bar. He orders a beer. Then he orders 0 beer. He orders -1 beer. After some time, he orders 999999999 beers.
测试人员进入酒吧。 他点啤酒。 然后他点了0啤酒。 他点-1啤酒。 一段时间后,他点了999999999啤酒。
A genuine customer comes into a bar and asks, “Where is the toilet?”.
一位真正的顾客走进酒吧,问:“洗手间在哪里?”。
As a result of the last action, a bar lights on fire and burns to the ground.
作为最后一个动作的结果,一根酒吧着火并燃烧到地面。
The moral of this anecdote is “test your code properly”, as my mentor always emphasises. As a matter of fact, most times a programmer will attempt to test his code precisely as described above.
正如我的导师一直强调的那样,这种轶事的寓意是“正确测试您的代码”。 事实上,大多数情况下,程序员会尝试如上所述精确地测试其代码。
Introduction to property-based testing
基于属性的测试简介
“ScalaCheck teaches you to look into the properties of your functions.” — said my Scala mentor (Wenderson Ferreira). How is it different from the usual tests that you write in JUnit for example? And what exactly is a Property?
“ ScalaCheck可以教您研究函数的属性。” —我的Scala导师( Wenderson Ferreira )说。 它与您在JUnit中编写的常规测试有何不同? 物业到底是什么?
In JUnit, you will probably check a summation function in this way: 2 + 3 = 5. It is a distinct example. And you would probably write a set of such distinct examples, going over the “usual suspects” (so-called common edge cases): zero, very large number and -1. In tests written as concrete examples, your test code shows how your application works in an individual situation. Your tests will always produce a very specified, yet incomplete assessment of your code.
在JUnit中,您可能会以这种方式检查求和函数:2 + 3 =5。这是一个不同的示例。 您可能会写出一组这样的不同示例,遍历“通常的可疑对象”(所谓的常见边缘案例):零,非常大的数字和-1。 在作为具体示例编写的测试中,您的测试代码显示了您的应用程序在特定情况下的工作方式。 您的测试将始终对您的代码产生非常明确但尚未完成的评估。
In ScalaCheck, you check if a + b == b + a! In this example, you check the behaviour of a mathematical summation function. And it is a more elaborate way of testing. ScalaCheck will automatically generate arguments to test all a and b possibilities, not limited to a number of “usual suspects”, as you will typically see in concrete testing code. This specific behaviour of a mathematical function is translated in ScalaCheck in abstract behaviour of your code, or a Property.
在ScalaCheck中,您检查a + b == b + a! 在此示例中,您将检查数学求和函数的行为。 这是一种更复杂的测试方法。 ScalaCheck将自动生成参数以测试所有a和b可能性,而不仅限于一些“通常的可疑对象”,正如您通常会在具体的测试代码中看到的那样。 数学函数的这种特定行为在ScalaCheck中以代码或Property的抽象行为进行转换。
Hands-on examples
动手实例
I was interested to test a Calculator class from part 1 of this ScalaCheck series. What is the behaviour of an ADDITION Option, or in ScalaCheck language, what is the Property of the summation of two numbers?
我有兴趣测试计算器类 从此ScalaCheck系列的第1部分中获得。 什么是附加的选件的行为,或在ScalaCheck语言,什么是两个数字的总和的房产吗?
For simplicity, let’s start with the addition of two integers. It is easier to start with an example of Int data type, because BigDecimal has its quirks. Nonetheless, I will present a solution for both.
为简单起见,让我们从两个整数的加法开始。 从Int数据类型的示例开始比较容易,因为BigDecimal具有其怪癖。 尽管如此,我将为两者提出一个解决方案。
A simple example: Property of the summation with Int
一个简单的例子:Int的求和属性
To begin with, I created an object called SimpleCalculator with four methods: summate, subtract, multiply and divide. These are regular mathematical operations between two numbers, in this case between two integers.
首先,我用四个方法创建了一个名为SimpleCalculator的对象:求和,减,乘和除。 这些是两个数字之间的常规数学运算,本例中为两个整数之间。
#code for SimpleCalculator object SimpleCalculator {
def summate(a: Int, b: Int): Int = {
a + b
}
def subtract(a: Int, b: Int): Int = {
a - b
}
def multiply(a: Int, b: Int): Int = {
a * b
}
def divide(a: Int, b: Int): Int = {
a / b
}
}
The most common way to create properties in ScalaCheck is using Prop.forAll method from org.scalacheck.Prop class. Moreover, if you want to group several properties together, you can do that by using org.scalacheck.Properties class. The code below shows how to implement that using inheritance (“extends” option).
在ScalaCheck中创建属性的最常见方法是使用org.scalacheck.Prop类中的Prop.forAll方法。 此外,如果要将多个属性分组在一起,则可以使用org.scalacheck.Properties类来实现。 下面的代码显示了如何使用继承(“ extends”选项)来实现这一点。
I want to focus on more details on the content of the forAll call. In general, forAll method returns true if the logical expression that the method contains is true for all elements, false otherwise. In the example below, generic a and b — two integer parameters — are fed into the forAll call which checks the behaviour of the summation method through a Boolean logic. And the tricky part of ScalaCheck testing lies precisely in defining this Boolean expression, because it has to imitate a function’s behaviour to be tested.
我想专注于有关forAll呼叫内容的更多细节。 通常,如果该方法包含的逻辑表达式对所有元素都为true,则forAll方法返回true,否则为false。 在下面的示例中,将泛型a和b (两个整数参数)输入到forAll调用中,该调用通过布尔逻辑检查求和方法的行为。 ScalaCheck测试的棘手部分恰好在于定义此布尔表达式,因为它必须模仿要测试的函数的行为。
#code for Property “summation”import org.scalacheck.{Prop, Properties}
import org.scalacheck.Prop.forAllobject SimpleCalculatorProps extends Properties ("SimpleCalculator")
{
property("summate") = forAll { (a: Int, b: Int) =>
val result = SimpleCalculator.summate(a, b)
result == a + b && result == b + a
}...}
As you can see, we don’t concentrate on the concrete examples for a and b. It is taken care for us by ScalaCheck Generators, which are automatically produced. We define a Boolean expression which reflects a specific property of the summation method, namely a functional symmetry. If all generated numbers will comply with the Boolean expression, ScalaCheck will approve your code.
如您所见,我们不集中于a和b的具体示例。 自动生成的ScalaCheck Generators会照顾我们。 我们定义一个布尔表达式,它反映求和方法的特定属性,即功能对称。 如果所有生成的数字都符合布尔表达式,ScalaCheck将批准您的代码。
#ScalaCheck testing output for Int arguments+ SimpleCalculator.summate: OK, passed 100 tests.
This is the typical output of a successful ScalaCheck test. Or in this case, a successful output of 100 tests. Please note that all tests had random input arguments and were performed in parallel. If your code can’t handle huge number of things happening simultaneously (concurrent access), then ScalaCheck will instantly fail your code. The default number of tests is 100, but you can adjust it according to your requirements.
这是成功的ScalaCheck测试的典型输出。 或在这种情况下,成功输出100个测试。 请注意,所有测试均具有随机输入参数,并且是并行执行的。 如果您的代码不能处理大量同时发生的事情(并发访问),那么ScalaCheck将立即使您的代码失败。 默认的测试数量为100,但是您可以根据需要进行调整。
A simple example: failing Property of summation with BigDecimal
一个简单的例子:BigDecimal求和的失败属性
If you change a data type from Int to BigDecimal in the code from the previous example, then ScalaCheck will fail your program.
如果您在上一个示例的代码中将数据类型从Int更改为BigDecimal ,则ScalaCheck将使您的程序失败。
#ScalaCheck testing output for BigDecimal arguments! SimpleCalculator.summate: Falsified after 1 passed tests.
> ARG_0: -0.00001131560419259331
> ARG_0_ORIGINAL: -0.3707897181828976
> ARG_1: 0.00001108784593808157497528613518274946
> ARG_1_ORIGINAL: 6702191619228002457
This ScalaCheck output shows that a code using BigDecimal as arguments was falsified after 1 test that actually has passed. In the output, you can see the initial or original (ARG_0_ORIGINAL) arguments that were passed to the forAll call, together with the simplified arguments for a (ARG_0) and b (ARG_1) that do not pass the Boolean expression.
此ScalaCheck输出显示,在实际通过1次测试之后,伪造了使用BigDecimal作为参数的代码。 在输出中,您可以看到传递给forAll调用的初始或原始(ARG_0_ORIGINAL)参数,以及未传递布尔表达式的 (ARG_0)和b (ARG_1)的简化参数。
Test case simplification is a feature of ScalaCheck which can find bugs in your code. As it happens, ScalaCheck narrows down failed behaviour of your code and prints the simplest set of arguments that make your code fail. Typically, when you examine the original input and the simplified arguments, the weakness in your code becomes obvious. In the case of BigDecimal the first thought that comes to mind is “why the number of digits after the dot varies?” Well, maybe the rounding of BigDecimal is not consistent? *
简化测试用例是ScalaCheck的一项功能,可以在代码中发现错误。 碰巧的是,ScalaCheck缩小了代码的失败行为,并输出了使代码失败的最简单的参数集。 通常,当您检查原始输入和简化的参数时,代码中的弱点就变得很明显。 对于BigDecimal ,首先想到的是“为什么点后的位数不同?” 好吧,也许BigDecimal的舍入不一致? *
Advanced example: Property of ADDITION with BigDecimal
高级示例:具有BigDecimal的ADDITION属性
Coming back to the example of Calculator class, which was described in part 1. It gets two arguments, namely a: Int and b: BigDecimal and has a calculator method with four Options (ADDITION, SUBTRACTION, DIVISION, MULTIPLICATION). ADDITION Option is a basic summation of two numbers. To test this option, we can use the same Boolean logic a + b == b + a as demonstrated above. However, we need to apply a round method that will deal with the precision of BigDecimal argument.
回到第1部分中描述的Calculator类的示例。 它有两个参数,即a:Int和b:BigDecimal,并具有带有四个选项 (ADDITION,SUBTRACTION,DIVISION,MULTIPLICATION)的计算器方法。 ADDITION Option是两个数字的基本和。 要测试此选项,我们可以使用与上述相同的布尔逻辑a + b == b + a。 但是,我们需要应用舍入方法来处理BigDecimal参数的精度。
# code for property “ADDITION”package caskApiimport caskApi.Operation.ADDITIONimport org.scalacheck.Properties
import org.scalacheck.Prop.forAll
import java.math.MathContextobject CalculatorProps extends Properties ("Calculator") {
property("ADDITION") = forAll { (a: Int, b: BigDecimal) =>
val result = Calculator(a, b).calculator(ADDITION).round(new MathContext(4))
result == (a + b).round(new MathContext(4)) && result == (b + a).round(new MathContext(4))
}...}
Promptly running the test for ADDITION property with the precision of BigDecimal specified will pass all the tests automatically generated by ScalaCheck.
立即以指定的BigDecimal精度运行ADDITION属性的测试将通过ScalaCheck自动生成的所有测试。
#ScalaCheck test output+ Calculator.ADDITION: OK, passed 100 tests.
Finally, try to guess the ScalaCheck solutions for other mathematical operations, i.e. subtraction, multiplication and division and check your guess in my repo for this project. Warning: division is a real doozy!
最后,尝试猜测用于其他数学运算(例如,减法,乘法和除法)的ScalaCheck解决方案,并在此项目的我的回购中检查您的猜测。 警告:分裂是真正的傻瓜!
Summary
摘要
ScalaCheck is a property-based testing tool. Simply put, ScalaCheck checks hundreds or even thousands of concrete tests by generating random data to test an abstract behaviour of your block of code. It will do its best to falsify this behaviour, aka Property. Nonetheless, if all of these concrete tests will pass, then ScalaCheck will regard this property as valid and you can consider your application very well tested.
ScalaCheck是基于属性的测试工具。 简而言之,ScalaCheck通过生成随机数据来测试您的代码块的抽象行为来检查数百甚至数千个具体测试。 它将尽最大努力来伪造这种行为,也称为Property 。 但是,如果所有这些具体测试都将通过,那么ScalaCheck会将该属性视为有效,并且您可以认为您的应用程序已经过了很好的测试。
* If you run into problems with BigDecimal, then first check Scala version that you use for your project. You might have run into version regression (lchoran summarises it very clearly).
*如果您遇到BigDecimal的问题,请首先检查用于项目的Scala版本。 您可能已经遇到了版本回归( lchoran对其进行了非常清晰的总结)。
翻译自: https://itnext.io/a-newbie-way-to-play-with-scalacheck-ac409a8205a2