1. Overview
1.概述
In this article, we’ll discuss a new Java-based testing framework called Lambda Behave.
在这篇文章中,我们将讨论一个新的基于 Java 的测试框架,该框架名为Lambda Behave。
As the name suggests, this testing framework is designed to work with Java 8 Lambdas. Further, in this article, we’ll look into the specifications and see an example for each.
顾名思义,这个测试框架被设计为与Java 8 Lambdas一起工作。此外,在这篇文章中,我们将研究这些规范,并看到每个规范的一个例子。
The Maven dependency we need to include is:
我们需要加入的Maven依赖项是。
<dependency>
<groupId>com.insightfullogic</groupId>
<artifactId>lambda-behave</artifactId>
<version>0.4</version>
</dependency>
The latest version can be found here.
最新版本可以在这里找到。
2. Basics
2.基础知识
One of the goals of the framework is to achieve great readability. The syntax encourages us to describe test cases using full sentences rather just a few words.
该框架的目标之一是实现极大的可读性。该语法鼓励我们使用完整的句子来描述测试用例,而不是只用几个字。
We can leverage parameterized tests and when we don’t want to bound test cases to some predefined values, we can generate random parameters.
我们可以利用参数化的测试,当我们不想将测试案例与一些预定义的值绑定时,我们可以生成随机参数。
3. Lambda Behave Test Implementation
3.Lambda行为测试的实现
Every specification suite begins with Suite.describe. At this point, we have several built-in methods to declare our specification. So, a Suite is like a JUnit test class, and the specifications are like the methods annotated with @Test in JUnit.
每个规范套件都以Suite.describe.开始,在这一点上,我们有几个内置方法来声明我们的规范。因此,Suite就像一个JUnit测试类,而规范就像JUnit中用@Test注释的方法。
To describe a test, we use should(). Similarly, if we name the expectation lambda parameter as “expect”, we could say what result we expect from the test, by expect.that().
为了描述一个测试,我们使用should()。类似地,如果我们将期望的lambda参数命名为“期望”,我们可以通过expect.that()说我们期望从测试中得到什么结果。
If we want to setup or tear down any data before and after a specification, we can use it.isSetupWith() and it.isConcludedWith(). In the same way, for doing something before and after the Suite, we’ll use it.initiatizesWith() and it.completesWith().
如果我们想在规范前后设置或拆解任何数据,我们可以使用 it.isSetupWith() 和 it.isConcludedWith()。以同样的方式,对于在Suite之前和之后做一些事情,我们将使用it.initiatizesWith()和it.completesWith()。
Let’s see an example of a simple test specification for the Calculator class:
让我们看看Calculator类的一个简单测试规范的例子。
public class Calculator {
public int add() {
return this.x + this.y;
}
public int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException();
}
return a / b;
}
}
We’ll start with Suite.describe and then add the code to initialize the Calculator.
我们将从Suite.description开始,然后添加代码来初始化Calculator。
Next, we’ll test the add() method by writing a specification:
接下来,我们将通过编写一个规范来测试add() 方法。
{
Suite.describe("Lambda behave example tests", it -> {
it.isSetupWith(() -> {
calculator = new Calculator(1, 2);
});
it.should("Add the given numbers", expect -> {
expect.that(calculator.add()).is(3);
});
}
Here, we named variables “it” and “expect” for better readability. Since those are lambda parameter names, we can replace these with any names of our choice.
在这里,我们将变量命名为“it”和“expect”,以提高可读性。由于这些是lambda参数的名字,我们可以用我们选择的任何名字来替换它们。
The first argument of should() describes using plain English, what this test should check. The second argument is a lambda, that indicates our expectation that the add() method should return 3.
should()的第一个参数用简单的英语描述了这个测试应该检查什么。第二个参数是一个lambda,它表明我们期望add()方法应该返回3。
Let’s add another test case for division by 0, and verify if we get an exception:
让我们再增加一个除以0的测试用例,并验证我们是否得到一个异常。
it.should("Throw an exception if divide by 0", expect -> {
expect.exception(ArithmeticException.class, () -> {
calculator.divide(1, 0);
});
});
In this case, we are expecting an exception, so we state expect.exception() and inside that, we write the code that should throw an exception.
在这种情况下,我们期待着一个异常,所以我们说明expect.exception(),在这里面,我们写下应该抛出一个异常的代码。
Note, that the text description must be unique for every specification.
注意,每个规格的文本描述必须是唯一的。。
4. Data-Driven Specifications
4.数据驱动的规范
This framework allows test parameterization at the specification level.
这个框架允许在规范层面进行测试参数化。
To create an example, let’s add a method to our Calculator class:
为了创建一个例子,让我们给我们的Calculator类添加一个方法。
public int add(int a, int b) {
return a + b;
}
Let’s write a data-driven test for it:
让我们为它写一个数据驱动的测试。
it.uses(2, 3, 5)
.and(23, 10, 33)
.toShow("%d + %d = %d", (expect, a, b, c) -> {
expect.that(calculator.add(a, b)).is(c);
});
The uses() method is used to specify input data in different numbers of columns. The first two arguments are the add() function parameters and the third one is the expected result. These parameters can also be used in the description as shown in the test.
uses()方法是用来指定不同列数的输入数据的。前两个参数是add()函数参数,第三个参数是预期结果。这些参数也可以在描述中使用,如测试中所示。
toShow() is used to describe the test using the parameters – with the following output:
toShow() 用于描述使用参数的测试–输出如下。
0: 2 + 3 = 5 (seed: 42562700892554)(Lambda behave example tests)
1: 23 + 10 = 33 (seed: 42562700892554)(Lambda behave example tests)
5. Generated Specifications – Property-Based Testing
5.生成的规范 – 基于属性的测试
Usually, when we write a unit test, we want to focus on broader properties that hold true for our system.
通常,当我们写一个单元测试时,我们想把重点放在对我们的系统保持真实的更广泛的属性上。
For example, when we test a String reversing function, we might check that if we reverse a particular String twice, we’ll end up with the original String.
例如,当我们测试一个String反转函数时,我们可能会检查,如果我们将一个特定的String反转两次,我们最终会得到原始String。
Property-Based testing focuses on the generic property without hard-coding specific test parameters. We can achieve this by using randomly generated test cases.
基于属性的测试专注于通用属性,而不硬编码具体的测试参数。我们可以通过使用随机生成的测试案例来实现这一点。
This strategy is similar to using Data-Driven specifications, but instead of specifying the table of data, we specify the number of test cases to be generated.
这种策略类似于使用数据驱动规范,但我们不是指定数据表,而是指定要生成的测试案例的数量。
So, our String reversal property-based test would look like this:
因此,我们基于String反转属性的测试将看起来像这样。
it.requires(2)
.example(Generator.asciiStrings())
.toShow("Reversing a String twice returns the original String",
(expect, str) -> {
String same = new StringBuilder(str)
.reverse().reverse().toString();
expect.that(same).isEqualTo(str);
});
We have indicated the number of required test cases using the requires() method. We use the example() clause to state what type of objects we need and how.
我们已经使用requires()方法指出了所需测试案例的数量。我们使用example()子句来说明我们需要什么类型的对象以及如何使用。
The output for this specification is:
该规范的输出是。
0: Reversing a String twice returns the original String(ljL+qz2)
(seed: 42562700892554)(Lambda behave example tests)
1: Reversing a String twice returns the original String(g)
(seed: 42562700892554)(Lambda behave example tests)
5.1. Deterministic Test Case Generation
5.1.确定性的测试用例生成
When we use the auto-generated test cases, it becomes quite difficult to isolate test failures. For example, if our functionality fails once in 1000 times, a specification that auto-generates just 10 cases, will have to be run over and over to observe the error.
当我们使用自动生成的测试案例时,隔离测试失败就变得相当困难。例如,如果我们的功能在1000次中失败一次,那么自动生成10个案例的规范将不得不反复运行以观察错误。
So, we need the ability to deterministically re-run tests, including previously failed cases.
因此,我们需要确定地重新运行测试的能力,包括以前失败的案例。
Lambda Behave is able to deal with this problem. As shown in the output of previous test case, it prints out the seed that was used to generate the random set of test cases. So, if anything fails, we can use the seed to re-create previously generated test cases.
Lambda Behave能够处理这个问题。如前面测试案例的输出所示,它打印出了用于生成随机测试案例集的种子。因此,如果有任何失败,我们可以使用种子来重新创建之前生成的测试案例。
We can look at the output of the test case and identify the seed: (seed: 42562700892554). Now, to generate the same set of tests again, we can use the SourceGenerator.
我们可以看一下测试用例的输出,确定种子。(seed: 42562700892554).现在,为了再次生成同一组测试,我们可以使用SourceGenerator。
The SourceGenerator contains the deterministicNumbers() method that takes just the seed as an argument:
SourceGenerator包含deterministicNumbers()方法,只接受种子作为一个参数。
it.requires(2)
.withSource(SourceGenerator.deterministicNumbers(42562700892554L))
.example(Generator.asciiStrings())
.toShow("Reversing a String twice returns the original String",
(expect, str) -> {
String same = new StringBuilder(str).reverse()
.reverse()
.toString();
expect.that(same).isEqualTo(str);
});
On running this test, we’ll get the same output as we saw previously.
在运行这个测试时,我们会得到与之前看到的相同的输出。
6. Conclusion
6.结论
In this article, we saw how to write unit tests using the Java 8 lambda expressions, in a new fluent testing framework, called Lambda Behave.
在这篇文章中,我们看到了如何使用Java 8的lambda表达式编写单元测试,在一个新的流畅的测试框架中,称为Lambda Behave。
As always the code for these examples can be found over on GitHub.
像往常一样,这些例子的代码可以在GitHub上找到over。