提交 7fd6b488 编写于 作者: W wizardforcel

2021-10-05 22:34:28

上级 c8867c80
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
# 它是如何工作的。。。 # 它是如何工作的。。。
`jshell`提示符处输入的代码片段被包装成刚好足够执行它们的代码。因此,变量、方法和类声明被包装在类中,表达式被包装在方法中,而方法又被包装在类中。其他东西,例如导入和类定义,保持原样是因为它们是顶级实体,也就是说,不需要在另一个类中包装类定义,因为类定义是可以自身存在的顶级实体。类似地,在 Java 中,import 语句可以自己出现,它们出现在类声明之外,因此不需要包装在类中。 `jshell`提示符处输入的代码片段被包装成刚好足够执行它们的代码。因此,变量、方法和类声明被包装在类中,表达式被包装在方法中,而方法又被包装在类中。其他东西,例如导入和类定义,保持原样是因为它们是顶级实体,也就是说,不需要在另一个类中包装类定义,因为类定义是可以自身存在的顶级实体。类似地,在 Java 中,`import`语句可以自己出现,它们出现在类声明之外,因此不需要包装在类中。
在接下来的菜谱中,我们将看到如何定义方法、导入其他包和定义类。 在接下来的菜谱中,我们将看到如何定义方法、导入其他包和定义类。
......
...@@ -271,7 +271,7 @@ $54 ==> 2018-05-04T22:10:37+05:30[Asia/Kolkata] ...@@ -271,7 +271,7 @@ $54 ==> 2018-05-04T22:10:37+05:30[Asia/Kolkata]
第一次使用`now()`使用系统时钟以及系统时区打印当前日期和时间。第二次使用`now()`使用系统时钟,但时区由`java.time.ZoneId`提供,在本例中为亚洲/加尔各答。`now()`的第三种用法使用`java.time.ZoneId`提供的固定时钟和时区。 第一次使用`now()`使用系统时钟以及系统时区打印当前日期和时间。第二次使用`now()`使用系统时钟,但时区由`java.time.ZoneId`提供,在本例中为亚洲/加尔各答。`now()`的第三种用法使用`java.time.ZoneId`提供的固定时钟和时区。
固定时钟是使用`java.time.Clock`类及其静态方法`fixed()`创建的,该方法以`java.time.Instant``java.time.ZoneId`为例。`java.time.Instant`的实例是在 epoch 之后使用一些静态秒数构建的。`java.time.Clock`用于表示一个时钟,新的日期/时间 API 可以使用该时钟来确定当前时间。如前所述,时钟可以固定,然后我们可以在亚洲/加尔各答时区创建一个比当前系统时间提前一小时的时钟,如下所示: 固定时钟是使用`java.time.Clock`类及其静态方法`fixed()`创建的,该方法以`java.time.Instant``java.time.ZoneId`为例。`java.time.Instant`的实例是在纪元之后使用一些静态秒数构建的。`java.time.Clock`用于表示一个时钟,新的日期/时间 API 可以使用该时钟来确定当前时间。如前所述,时钟可以固定,然后我们可以在亚洲/加尔各答时区创建一个比当前系统时间提前一小时的时钟,如下所示:
```java ```java
var hourAheadClock = Clock.offset(Clock.system(ZoneId.of("Asia/Kolkata")), Duration.ofHours(1)); var hourAheadClock = Clock.offset(Clock.system(ZoneId.of("Asia/Kolkata")), Duration.ofHours(1));
...@@ -674,7 +674,7 @@ jshell> d.addTo(LocalDate.now()) ...@@ -674,7 +674,7 @@ jshell> d.addTo(LocalDate.now())
# 如何表示大纪元时间 # 如何表示大纪元时间
在本配方中,我们将使用`java.time.Instant`表示一个时间点,并将该时间点转换为历元秒/毫秒。Java 历元用于表示从 1970-01-01 到 0:00:00Z 的时间瞬间,`java.time.Instant`存储 Java 历元的秒数。正值表示时间在历元之前,负值表示时间在历元之后。它使用 UTC 的系统时钟来计算当前时间的瞬时值。 在本配方中,我们将使用`java.time.Instant`表示一个时间点,并将该时间点转换为历元秒/毫秒。Java 历元用于表示从`1970-01-01 00:00:00Z`开始的时间瞬间,`java.time.Instant`存储 Java 历元的秒数。正值表示时间在历元之前,负值表示时间在历元之后。它使用 UTC 的系统时钟来计算当前时间的瞬时值。
# 准备 # 准备
...@@ -703,8 +703,8 @@ $42 ==> 1530863845158 ...@@ -703,8 +703,8 @@ $42 ==> 1530863845158
`java.time.Instant`类在其两个字段中存储时间信息: `java.time.Instant`类在其两个字段中存储时间信息:
* 秒,属于`long`类型:存储从 1970-01-01T00:00:00Z 开始的秒数。 * 秒,属于`long`类型:存储从`1970-01-01T00:00:00Z`开始的秒数。
* Nano,属于`int` 类型:它存储纳秒数 * 纳秒,属于`int` 类型:它存储纳秒数
当您调用`now()`方法时,`java.time.Instant`使用 UTC 的系统时钟来表示该时刻。然后我们可以使用`atZone()``atOffset()`将其转换为所需的时区,我们将在下一节中看到。 当您调用`now()`方法时,`java.time.Instant`使用 UTC 的系统时钟来表示该时刻。然后我们可以使用`atZone()``atOffset()`将其转换为所需的时区,我们将在下一节中看到。
...@@ -956,7 +956,7 @@ $41 ==> ThaiBuddhist BE 2561-07-30 ...@@ -956,7 +956,7 @@ $41 ==> ThaiBuddhist BE 2561-07-30
您可以使用 JDK 支持的其他日历系统,即泰国、佛教和民国(中文)日历系统。通过编写`java.time.chrono.Chronology``java.time.chrono.ChronoLocalDate``java.time.chrono.Era`的实现来创建我们的定制日历系统也是值得探索的。 您可以使用 JDK 支持的其他日历系统,即泰国、佛教和民国(中文)日历系统。通过编写`java.time.chrono.Chronology``java.time.chrono.ChronoLocalDate``java.time.chrono.Era`的实现来创建我们的定制日历系统也是值得探索的。
# 如何使用 DateTimeFormatter 格式化日期 # 如何使用`DateTimeFormatter`格式化日期
在使用`java.util.Date`时,我们使用`java.text.SimpleDateFormat`将日期格式化为不同的文本表示形式,反之亦然。格式化日期是指给定一个日期或一个时间对象,以不同的格式表示该日期,例如: 在使用`java.util.Date`时,我们使用`java.text.SimpleDateFormat`将日期格式化为不同的文本表示形式,反之亦然。格式化日期是指给定一个日期或一个时间对象,以不同的格式表示该日期,例如:
...@@ -1054,7 +1054,7 @@ $72 ==> "01 August 18 6:26:27 PM GMT+5:30" ...@@ -1054,7 +1054,7 @@ $72 ==> "01 August 18 6:26:27 PM GMT+5:30"
但是,为了向最终用户显示日期和时间信息,我们需要将其格式化为可读的格式,为此,我们需要一个自定义的`DateTimeFormatter`。如果您需要自定义`java.time.format.DateTimeFormatter`,这里有两种创建方式: 但是,为了向最终用户显示日期和时间信息,我们需要将其格式化为可读的格式,为此,我们需要一个自定义的`DateTimeFormatter`。如果您需要自定义`java.time.format.DateTimeFormatter`,这里有两种创建方式:
* 使用模式,例如 dd-MMMM-yyyy `java.time.format.DateTimeFormatter`中的`ofPattern()`方法 * 使用模式,例如`dd-MMMM-yyyy``java.time.format.DateTimeFormatter`中的`ofPattern()`方法
* 使用`java.time.DateTimeFormatterBuilder` * 使用`java.time.DateTimeFormatterBuilder`
**使用模式** **使用模式**
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
本章介绍如何测试应用程序如何捕获和自动测试用例,如何在 API 与其他组件集成之前对其进行单元测试,以及如何集成所有单元。我们将向您介绍**行为驱动开发****BDD**),并展示它如何成为您应用程序开发的起点。我们还将演示 JUnit 框架如何用于单元测试。有时,在单元测试期间,我们必须使用一些虚拟数据来插桩依赖项,这可以通过模拟依赖项来完成。我们将向您展示如何使用模拟库来实现这一点。我们还将向您展示如何编写夹具来填充测试数据,以及如何通过集成不同的 API 并将它们测试在一起来测试应用程序的行为。我们将介绍以下配方: 本章介绍如何测试应用程序如何捕获和自动测试用例,如何在 API 与其他组件集成之前对其进行单元测试,以及如何集成所有单元。我们将向您介绍**行为驱动开发****BDD**),并展示它如何成为您应用程序开发的起点。我们还将演示 JUnit 框架如何用于单元测试。有时,在单元测试期间,我们必须使用一些虚拟数据来插桩依赖项,这可以通过模拟依赖项来完成。我们将向您展示如何使用模拟库来实现这一点。我们还将向您展示如何编写夹具来填充测试数据,以及如何通过集成不同的 API 并将它们测试在一起来测试应用程序的行为。我们将介绍以下配方:
* 黄瓜行为测试 * Cucumber行为测试
* 使用 JUnit 对 API 进行单元测试 * 使用 JUnit 对 API 进行单元测试
* 通过模拟依赖项进行单元测试 * 通过模拟依赖项进行单元测试
* 使用夹具填充测试数据 * 使用夹具填充测试数据
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
从方法论上讲,本章的内容也适用于其他语言和专业,但这些示例主要是为 Java 开发人员编写的。 从方法论上讲,本章的内容也适用于其他语言和专业,但这些示例主要是为 Java 开发人员编写的。
# 黄瓜行为测试 # Cucumber行为测试
以下是程序员经常抱怨的三个问题: 以下是程序员经常抱怨的三个问题:
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
有相当多的建议和程序有助于缓解这些问题,但没有一个能够完全消除这些问题。在我们看来,最成功的是与 BDD 结合使用的敏捷过程方法,使用 Cucumber 或其他类似的框架。短迭代允许在业务(客户)和程序员之间进行快速调整和协调,而带有 Cucumber 的 BDD 用一种称为 Gherkin 的正式语言捕获需求,但不需要维护大量文档。 有相当多的建议和程序有助于缓解这些问题,但没有一个能够完全消除这些问题。在我们看来,最成功的是与 BDD 结合使用的敏捷过程方法,使用 Cucumber 或其他类似的框架。短迭代允许在业务(客户)和程序员之间进行快速调整和协调,而带有 Cucumber 的 BDD 用一种称为 Gherkin 的正式语言捕获需求,但不需要维护大量文档。
小黄瓜编写的需求必须分解为**特征**。每个功能都存储在一个扩展名为`.feature`的文件中,由一个或多个**场景**组成,这些场景描述了功能的不同方面。每个场景由描述用户操作或输入数据以及应用程序如何响应的步骤组成。 Cucumber编写的需求必须分解为**特征**。每个功能都存储在一个扩展名为`.feature`的文件中,由一个或多个**场景**组成,这些场景描述了功能的不同方面。每个场景由描述用户操作或输入数据以及应用程序如何响应的步骤组成。
程序员实现必要的应用程序功能,然后使用它在一个或多个`.java`文件中实现场景。每个步骤都在一个方法中实现。 程序员实现必要的应用程序功能,然后使用它在一个或多个`.java`文件中实现场景。每个步骤都在一个方法中实现。
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
# 怎么做。。。 # 怎么做。。。
1. 安装黄瓜。Cucumber 安装只不过是将框架作为 Maven 依赖项添加到项目中。由于我们要添加几个 Cucumber JAR 文件,并且所有文件都必须具有相同的版本,因此首先在`pom.xml`中添加`cucumber.version`属性是有意义的: 1. 安装Cucumber。Cucumber 安装只不过是将框架作为 Maven 依赖项添加到项目中。由于我们要添加几个 Cucumber JAR 文件,并且所有文件都必须具有相同的版本,因此首先在`pom.xml`中添加`cucumber.version`属性是有意义的:
```java ```java
<properties> <properties>
...@@ -110,7 +110,7 @@ ...@@ -110,7 +110,7 @@
如您所见,在本例中,您需要添加`cucumber-testng`JAR 文件,而不是`cucumber-junit`JAR 文件。TestNG 提供了丰富多样的断言方法,包括深度集合和其他对象比较。 如您所见,在本例中,您需要添加`cucumber-testng`JAR 文件,而不是`cucumber-junit`JAR 文件。TestNG 提供了丰富多样的断言方法,包括深度集合和其他对象比较。
2. 吃黄瓜`cucumber-junit`JAR 文件还提供了一个`@RunWith`注释,将一个类指定为测试运行程序: 2. 运行Cucumber`cucumber-junit`JAR 文件还提供了一个`@RunWith`注释,将一个类指定为测试运行程序:
```java ```java
package com.packt.cookbook.ch16_testing; package com.packt.cookbook.ch16_testing;
...@@ -543,7 +543,7 @@ import static org.junit.Assert.fail; ...@@ -543,7 +543,7 @@ import static org.junit.Assert.fail;
``` ```
然后,我们可以编写代码来测试在`Vehicle`类的构造函数中传递的值为 null 时的情况(因此应该抛出异常): 然后,我们可以编写代码来测试在`Vehicle`类的构造函数中传递的值为`null`时的情况(因此应该抛出异常):
```java ```java
@Test @Test
...@@ -1357,7 +1357,7 @@ void subscribe(SubmissionPublisher<Integer> publisher, ...@@ -1357,7 +1357,7 @@ void subscribe(SubmissionPublisher<Integer> publisher,
![](img/e2e9ca71-45f0-42b1-955e-071d82ab264d.png) ![](img/e2e9ca71-45f0-42b1-955e-071d82ab264d.png)
`Process`enum 类的名称指向数据库`result`表中相应的记录。同样,在这个阶段,我们主要是寻找任何结果,而不是在如何正确的价值观。 `Process`枚举类的名称指向数据库`result`表中相应的记录。同样,在这个阶段,我们主要是寻找任何结果,而不是在如何正确的价值观。
在基于`FactoryTraffic`生成的数据成功集成我们应用程序的子系统后,我们可以尝试连接到提供真实交通数据的外部系统。在`FactoryTraffic`中,我们现在将从生成`TrafficUnit`对象切换到从真实系统获取数据: 在基于`FactoryTraffic`生成的数据成功集成我们应用程序的子系统后,我们可以尝试连接到提供真实交通数据的外部系统。在`FactoryTraffic`中,我们现在将从生成`TrafficUnit`对象切换到从真实系统获取数据:
......
...@@ -334,7 +334,7 @@ JUnit 被设计成一个单元测试框架。然而,它不仅可以用于实 ...@@ -334,7 +334,7 @@ JUnit 被设计成一个单元测试框架。然而,它不仅可以用于实
| `assertTrue` | 断言某个条件为真。如果不是,则该方法抛出一个带有给定消息(如果有)的`AssertionFailedError`。 | | `assertTrue` | 断言某个条件为真。如果不是,则该方法抛出一个带有给定消息(如果有)的`AssertionFailedError`。 |
| `assertFalse` | 断言某个条件为假。如果不是,则该方法抛出一个带有给定消息(如果有)的`AssertionFailedError`。 | | `assertFalse` | 断言某个条件为假。如果不是,则该方法抛出一个带有给定消息(如果有)的`AssertionFailedError`。 |
| `assertEquals` | 断言两个对象相等。如果没有,该方法将抛出一个带有给定消息(如果有)的`AssertionFailedError`。 | | `assertEquals` | 断言两个对象相等。如果没有,该方法将抛出一个带有给定消息(如果有)的`AssertionFailedError`。 |
| `assertNotNull` | 断言对象不为 null。如果是,该方法抛出一个带有消息的`AssertionFailedError`(如果有)。 | | `assertNotNull` | 断言对象不为`null`。如果是,该方法抛出一个带有消息的`AssertionFailedError`(如果有)。 |
| `assertNull` | 断言对象为空。如果不是,则该方法抛出一个带有给定消息(如果有)的`AssertionFailedError`。 | | `assertNull` | 断言对象为空。如果不是,则该方法抛出一个带有给定消息(如果有)的`AssertionFailedError`。 |
| `assertSame` | 断言两个对象引用同一个对象。如果没有,该方法将抛出一个带有给定消息(如果有)的`AssertionFailedError`。 | | `assertSame` | 断言两个对象引用同一个对象。如果没有,该方法将抛出一个带有给定消息(如果有)的`AssertionFailedError`。 |
| `assertNotSame` | 断言两个对象不引用同一个对象。如果有,该方法将抛出一个带有给定消息(如果有)的`AssertionFailedError`。 | | `assertNotSame` | 断言两个对象不引用同一个对象。如果有,该方法将抛出一个带有给定消息(如果有)的`AssertionFailedError`。 |
...@@ -470,8 +470,8 @@ JUnit4 测试生命周期 ...@@ -470,8 +470,8 @@ JUnit4 测试生命周期
`org.junit.Assert`类提供了执行断言(谓词)的静态方法。以下是最有用的断言方法: `org.junit.Assert`类提供了执行断言(谓词)的静态方法。以下是最有用的断言方法:
* `assertTrue`:如果条件变为 false,则断言失败并抛出`AssertionError` * `assertTrue`:如果条件变为`false`,则断言失败并抛出`AssertionError`
* `assertFalse`:如果条件变为 true,则断言失败并抛出`AssertionError` * `assertFalse`:如果条件变为`true`,则断言失败并抛出`AssertionError`
* `assertNull`:检查参数是否为空,如果参数不是`null`则抛出`AssertionError` * `assertNull`:检查参数是否为空,如果参数不是`null`则抛出`AssertionError`
* `assertNotNull`:检查参数是否为空;否则抛出`AssertionError` * `assertNotNull`:检查参数是否为空;否则抛出`AssertionError`
* `assertEquals`:比较两个对象或原语类型。此外,如果实际值与预期值不匹配,则抛出`AssertionError` * `assertEquals`:比较两个对象或原语类型。此外,如果实际值与预期值不匹配,则抛出`AssertionError`
......
...@@ -276,7 +276,7 @@ Jupiter 断言的完整列表(类`org.junit.Jupiter.assertions`) ...@@ -276,7 +276,7 @@ Jupiter 断言的完整列表(类`org.junit.Jupiter.assertions`)
对于表中包含的每个断言,可以提供可选的失败消息(字符串)。此消息始终是断言方法中的最后一个参数。这与 JUnit4 有一点不同,JUnit4 中此消息是方法调用的第一个参数。 对于表中包含的每个断言,可以提供可选的失败消息(字符串)。此消息始终是断言方法中的最后一个参数。这与 JUnit4 有一点不同,JUnit4 中此消息是方法调用的第一个参数。
下面的示例显示了使用`assertEquals``assertTrue``assertFalse` 断言的测试。注意,我们在类的开头导入静态断言方法,以提高测试逻辑的可读性。在本例中,我们找到了`assertEquals` 方法,在本例中比较了两种基本类型(也可以用于对象)。第二,`assertTrue`方法评估`boolean`表达式是否为真。第三,方法`assertFalse`计算布尔表达式是否为 false。在本例中,请注意消息是作为 Lamdba 表达式创建的。这样,断言消息将被延迟评估,以避免不必要地构造复杂消息: 下面的示例显示了使用`assertEquals``assertTrue``assertFalse` 断言的测试。注意,我们在类的开头导入静态断言方法,以提高测试逻辑的可读性。在本例中,我们找到了`assertEquals` 方法,在本例中比较了两种基本类型(也可以用于对象)。第二,`assertTrue`方法评估`boolean`表达式是否为真。第三,方法`assertFalse`计算布尔表达式是否为`false`。在本例中,请注意消息是作为 Lamdba 表达式创建的。这样,断言消息将被延迟评估,以避免不必要地构造复杂消息:
```java ```java
package io.github.bonigarcia; package io.github.bonigarcia;
...@@ -566,7 +566,7 @@ class SimpleTaggingTest { ...@@ -566,7 +566,7 @@ class SimpleTaggingTest {
从 JUnit 5 M6 开始,标记测试的标签应满足以下语法规则: 从 JUnit 5 M6 开始,标记测试的标签应满足以下语法规则:
* 标记不能为 null 或空白。 * 标记不能为`null`或空白。
* 修剪过的标记(即删除了前导和尾随空格的标记)不得包含空格。 * 修剪过的标记(即删除了前导和尾随空格的标记)不得包含空格。
* 修剪后的标签不得包含 ISO 控制字符或以下保留字符:`,``(``)``&``|``!` * 修剪后的标签不得包含 ISO 控制字符或以下保留字符:`,``(``)``&``|``!`
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
简单地说,Mockito 是一个测试框架,允许创建、插桩和验证模拟对象。为此,Mockito 提供了一个 API 来隔离 SUT 及其文档。一般来说,使用 Mockito 包括三个不同的步骤: 简单地说,Mockito 是一个测试框架,允许创建、插桩和验证模拟对象。为此,Mockito 提供了一个 API 来隔离 SUT 及其文档。一般来说,使用 Mockito 包括三个不同的步骤:
1. **模拟对象**:为了隔离我们的 SUT,我们使用 Mockito API 创建其关联文档的 mock。这样,我们保证 SUT 不依赖于它的实际文档,而我们的单元测试实际上关注于 SUT。 1. **模拟对象**:为了隔离我们的 SUT,我们使用 Mockito API 创建其关联文档的 mock。这样,我们保证 SUT 不依赖于它的实际文档,而我们的单元测试实际上关注于 SUT。
2. **设置期望值**:mock 对象与其他测试替身对象(如桩)的区别在于,可以根据单元测试的需要,使用自定义期望值对 mock 对象进行编程。Mockito 术语中的这个过程称为 stubing 方法,其中这些方法属于 mock。默认情况下,模拟对象模仿真实对象的行为。实际上,这意味着模拟对象返回适当的伪值,例如布尔类型为 false,对象为 null,整数或长返回类型为 0,等等。Mockito 允许我们使用一个丰富的 API 来改变这种行为,它允许桩在调用方法时返回一个特定的值。 2. **设置期望值**:mock 对象与其他测试替身对象(如桩)的区别在于,可以根据单元测试的需要,使用自定义期望值对 mock 对象进行编程。Mockito 术语中的这个过程称为 stubing 方法,其中这些方法属于 mock。默认情况下,模拟对象模仿真实对象的行为。实际上,这意味着模拟对象返回适当的伪值,例如布尔类型为`false`,对象为`null`,整数或长返回类型为 0,等等。Mockito 允许我们使用一个丰富的 API 来改变这种行为,它允许桩在调用方法时返回一个特定的值。
当一个模拟对象没有任何预期(即没有*桩方法*时,从技术上讲,它不是*模拟*对象,而是*虚拟*对象(请看第一章、“软件质量和 Java 测试回顾”对于定义)。 当一个模拟对象没有任何预期(即没有*桩方法*时,从技术上讲,它不是*模拟*对象,而是*虚拟*对象(请看第一章、“软件质量和 Java 测试回顾”对于定义)。
...@@ -345,7 +345,7 @@ public class LoginRepository { ...@@ -345,7 +345,7 @@ public class LoginRepository {
现在,我们将使用 JUnit5 和 Mockito 测试我们的系统。首先,我们测试控制器组件。因为我们正在进行单元测试,所以需要将`LoginController`登录与系统的其余部分隔离开来。要做到这一点,我们需要模拟它的依赖关系,在本例中是`LoginService`组件。使用前面解释的 SUT/DOC 术语,在这个测试中,我们的 SUT 是类`LoginController`,它的 DOC 是类`LoginService` 现在,我们将使用 JUnit5 和 Mockito 测试我们的系统。首先,我们测试控制器组件。因为我们正在进行单元测试,所以需要将`LoginController`登录与系统的其余部分隔离开来。要做到这一点,我们需要模拟它的依赖关系,在本例中是`LoginService`组件。使用前面解释的 SUT/DOC 术语,在这个测试中,我们的 SUT 是类`LoginController`,它的 DOC 是类`LoginService`
为了用 JUnit5 实现我们的测试,首先我们需要用`@ExtendWith`注册`MockitoExtension`。然后,我们用`@InjectMocks`(类别`LoginController`)声明 SUT,用`@Mock`(类别`LoginService`声明其 DOC。我们实施了两个测试(`@Test`。第一个(`testLoginOk`指定调用 mock`loginService`的方法 login 时,该方法应返回 true。之后,实际执行 SUT,并验证其响应(在这种情况下,返回的字符串必须是`OK`。此外,Mockito API 再次用于评估是否不再与 mock`LoginService`进行交互。第二个测试(`testLoginKo`)是等效的,但是将方法 login 插桩为返回 false,因此在这种情况下 SUT`(LoginController)`的响应必须是`KO` 为了用 JUnit5 实现我们的测试,首先我们需要用`@ExtendWith`注册`MockitoExtension`。然后,我们用`@InjectMocks`(类别`LoginController`)声明 SUT,用`@Mock`(类别`LoginService`声明其 DOC。我们实施了两个测试(`@Test`。第一个(`testLoginOk`指定调用 mock`loginService`的方法 login 时,该方法应返回`true`。之后,实际执行 SUT,并验证其响应(在这种情况下,返回的字符串必须是`OK`。此外,Mockito API 再次用于评估是否不再与 mock`LoginService`进行交互。第二个测试(`testLoginKo`)是等效的,但是将方法 login 插桩为返回`false`,因此在这种情况下 SUT`(LoginController)`的响应必须是`KO`
```java ```java
package io.github.bonigarcia; package io.github.bonigarcia;
...@@ -1596,17 +1596,17 @@ public class AppiumTest { ...@@ -1596,17 +1596,17 @@ public class AppiumTest {
有关`selenium-jupiter`的更多示例,请访问[这里](https://bonigarcia.github.io/selenium-jupiter/) 有关`selenium-jupiter`的更多示例,请访问[这里](https://bonigarcia.github.io/selenium-jupiter/)
# 黄瓜 # Cucumber
[Cucumber](https://cucumber.io/) 是一个测试框架,旨在自动化按照**行为驱动开发****BDD**)风格编写的验收测试。Cucumber 是用 Ruby 编写的,不过也有其他语言(包括 Java、JavaScript 和 Python)的实现。 [Cucumber](https://cucumber.io/) 是一个测试框架,旨在自动化按照**行为驱动开发****BDD**)风格编写的验收测试。Cucumber 是用 Ruby 编写的,不过也有其他语言(包括 Java、JavaScript 和 Python)的实现。
# 黄瓜 # Cucumber
Cucumber 执行用名为 Gherkin 的语言编写的指定测试。它是一种具有给定结构的纯文本自然语言(例如,英语或 Cucumber 支持的其他 60 多种语言之一)。小黄瓜被设计成供非程序员使用,通常是客户、业务分析、经理等等。 Cucumber 执行用名为 Gherkin 的语言编写的指定测试。它是一种具有给定结构的纯文本自然语言(例如,英语或 Cucumber 支持的其他 60 多种语言之一)。Cucumber被设计成供非程序员使用,通常是客户、业务分析、经理等等。
小黄瓜文件的扩展名为`*.feature*` Cucumber文件的扩展名为`*.feature*`
小黄瓜文件中,非空行可以以关键字开头,然后是自然语言中的文本。主要关键词如下: Cucumber文件中,非空行可以以关键字开头,然后是自然语言中的文本。主要关键词如下:
* **特性**:待测试软件特性的高层描述。它可以看作是一个用例描述。 * **特性**:待测试软件特性的高层描述。它可以看作是一个用例描述。
* **场景**:说明业务规则的具体示例。场景遵循相同的模式: * **场景**:说明业务规则的具体示例。场景遵循相同的模式:
...@@ -1614,7 +1614,7 @@ Cucumber 执行用名为 Gherkin 的语言编写的指定测试。它是一种 ...@@ -1614,7 +1614,7 @@ Cucumber 执行用名为 Gherkin 的语言编写的指定测试。它是一种
* 描述一个事件。 * 描述一个事件。
* 描述预期结果。 * 描述预期结果。
这些动作在小黄瓜术语中被称为步骤,主要是**给出****当**,或**然后** 这些动作在Cucumber术语中被称为步骤,主要是**给出****当**,或**然后**
有两个额外的步骤:**和**(用于逻辑和不同的步骤)和**但**(用于**和**的否定形式)。 有两个额外的步骤:**和**(用于逻辑和不同的步骤)和**但**(用于**和**的否定形式)。
...@@ -1627,9 +1627,9 @@ Cucumber 执行用名为 Gherkin 的语言编写的指定测试。它是一种 ...@@ -1627,9 +1627,9 @@ Cucumber 执行用名为 Gherkin 的语言编写的指定测试。它是一种
当一行不以关键字开头时,Cucumber 不会解释该行。它用于自定义描述。 当一行不以关键字开头时,Cucumber 不会解释该行。它用于自定义描述。
一旦我们定义了要测试的功能,我们就需要所谓的*步骤定义*,它允许将纯文本小黄瓜翻译成实际执行 SUT 的动作。在 Java 中,可以很容易地通过注释对步骤实现的方法进行注释:`@Given``@Then``@When``@And``@But`。每个步骤的字符串值可以包含正则表达式,这些正则表达式在方法中映射为字段。请参见下一节中的示例。 一旦我们定义了要测试的功能,我们就需要所谓的*步骤定义*,它允许将纯文本Cucumber翻译成实际执行 SUT 的动作。在 Java 中,可以很容易地通过注释对步骤实现的方法进行注释:`@Given``@Then``@When``@And``@But`。每个步骤的字符串值可以包含正则表达式,这些正则表达式在方法中映射为字段。请参见下一节中的示例。
# 黄瓜 JUnit 5 的扩展 # Cucumber JUnit 5 的扩展
Cucumber artifacts for Java 的最新版本包含一个 JUnit 5 Cucumber 扩展。本节包含在 Gherkin 和 JUnit 5 中定义的一个功能的完整示例,用于使用 Cucumber 执行该功能。与往常一样,[本例的源代码托管在 GitHub 上](https://github.com/bonigarcia/mastering-junit5) Cucumber artifacts for Java 的最新版本包含一个 JUnit 5 Cucumber 扩展。本节包含在 Gherkin 和 JUnit 5 中定义的一个功能的完整示例,用于使用 Cucumber 执行该功能。与往常一样,[本例的源代码托管在 GitHub 上](https://github.com/bonigarcia/mastering-junit5)
...@@ -1639,7 +1639,7 @@ Cucumber artifacts for Java 的最新版本包含一个 JUnit 5 Cucumber 扩展 ...@@ -1639,7 +1639,7 @@ Cucumber artifacts for Java 的最新版本包含一个 JUnit 5 Cucumber 扩展
JUnit5 与 Cucumber 项目结构和内容 JUnit5 与 Cucumber 项目结构和内容
首先,我们需要创建小黄瓜文件,该文件旨在测试一个简单的计算器系统。这个计算器将是 SUT 或我们的测试。我们的专题文件内容如下: 首先,我们需要创建Cucumber文件,该文件旨在测试一个简单的计算器系统。这个计算器将是 SUT 或我们的测试。我们的专题文件内容如下:
```java ```java
Feature: Basic Arithmetic Feature: Basic Arithmetic
...@@ -1660,7 +1660,7 @@ Feature: Basic Arithmetic ...@@ -1660,7 +1660,7 @@ Feature: Basic Arithmetic
| 3 | 7 | 10 | | 3 | 7 | 10 |
``` ```
然后,我们需要实现步骤定义。如前所述,我们使用注释和正则表达式将小黄瓜文件中包含的文本映射到 SUT 的实际练习,具体取决于以下步骤: 然后,我们需要实现步骤定义。如前所述,我们使用注释和正则表达式将Cucumber文件中包含的文本映射到 SUT 的实际练习,具体取决于以下步骤:
```java ```java
package io.github.bonigarcia; package io.github.bonigarcia;
...@@ -1701,7 +1701,7 @@ import cucumber.api.java.en.When; ...@@ -1701,7 +1701,7 @@ import cucumber.api.java.en.When;
} }
``` ```
当然,我们仍然需要实现 JUnit5 测试。为了实现 Cucumber 与 JUnit 5 的集成,Cucumber 扩展需要通过`@ExtendWith(CucumberExtension.class)`在我们班注册。在内部,`CucumberExtension`实现了 Jupiter 扩展模型的`ParameterResolver`回调。目标是将黄瓜特征的相应测试作为 Jupiter`DynamicTest`对象注入测试中。注意,在示例中,`@TestFactory`是如何使用的。 当然,我们仍然需要实现 JUnit5 测试。为了实现 Cucumber 与 JUnit 5 的集成,Cucumber 扩展需要通过`@ExtendWith(CucumberExtension.class)`在我们班注册。在内部,`CucumberExtension`实现了 Jupiter 扩展模型的`ParameterResolver`回调。目标是将Cucumber特征的相应测试作为 Jupiter`DynamicTest`对象注入测试中。注意,在示例中,`@TestFactory`是如何使用的。
或者,我们可以用`@CucumberOptions`注释我们的测试类。此注释允许为我们的测试配置 Cumber 设置。此批注允许的元素包括: 或者,我们可以用`@CucumberOptions`注释我们的测试类。此注释允许为我们的测试配置 Cumber 设置。此批注允许的元素包括:
...@@ -1743,7 +1743,7 @@ public class CucumberTest { ...@@ -1743,7 +1743,7 @@ public class CucumberTest {
![](img/00124.gif) ![](img/00124.gif)
黄瓜和青瓜生产 JUnit 5 Cucumber和青瓜生产 JUnit 5
# Docker # Docker
...@@ -1800,7 +1800,7 @@ JUnit5 Docker 的使用非常简单。我们只需要用`@Docker`注释我们的 ...@@ -1800,7 +1800,7 @@ JUnit5 Docker 的使用非常简单。我们只需要用`@Docker`注释我们的
* `ports`:Docker 容器的端口映射。这是必需的,因为容器必须至少有一个端口可见才能使用。 * `ports`:Docker 容器的端口映射。这是必需的,因为容器必须至少有一个端口可见才能使用。
* `environments`:传递给 docker 容器的可选环境变量。默认值:`{}` * `environments`:传递给 docker 容器的可选环境变量。默认值:`{}`
* `waitFor`:运行测试前等待的可选日志。默认值:`@WaitFor(NOTHING)` * `waitFor`:运行测试前等待的可选日志。默认值:`@WaitFor(NOTHING)`
* `newForEachCase`:布尔标志,用于确定是否应为每个测试用例重新创建容器。如果只为测试类创建一次,则该值将为 false。默认值:`true` * `newForEachCase`:布尔标志,用于确定是否应为每个测试用例重新创建容器。如果只为测试类创建一次,则该值将为`false`。默认值:`true`
考虑下面的例子。这个测试类使用`@Docker`注释来启动 MySql 容器(容器图像 MySql)和每个测试的开始。内部集装箱端口为`3306`,将映射到主机端口`8801`。然后,定义了几个环境属性(MySql 根密码、默认数据库以及用户名和密码)。直到容器日志中出现跟踪`mysqld:ready for connections`(表示 MySql 实例已启动并正在运行)后,测试才会开始执行。在测试主体中,我们针对容器中运行的 MySQL 实例启动 JDBC 连接。 考虑下面的例子。这个测试类使用`@Docker`注释来启动 MySql 容器(容器图像 MySql)和每个测试的开始。内部集装箱端口为`3306`,将映射到主机端口`8801`。然后,定义了几个环境属性(MySql 根密码、默认数据库以及用户名和密码)。直到容器日志中出现跟踪`mysqld:ready for connections`(表示 MySql 实例已启动并正在运行)后,测试才会开始执行。在测试主体中,我们针对容器中运行的 MySQL 实例启动 JDBC 连接。
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
在整个软件开发过程中设计和实施测试的时间会导致不同的测试方法,即(见列表后的图表): 在整个软件开发过程中设计和实施测试的时间会导致不同的测试方法,即(见列表后的图表):
* **行为驱动开发****BDD**):在分析阶段开始时,软件消费者(最终用户或客户)和一些开发团队(通常是项目负责人、经理或分析师)之间进行了对话。这些对话用于具体化场景(即,建立对系统功能的共同理解的具体示例)。这些示例构成了使用 Cucumber 等工具开发验收测试的基础(有关更多详细信息,请参阅第 5 章、“JUnit 5 与外部框架的集成”)。BDD 中验收测试的描述(例如,在 Cucumber 中使用小黄瓜)生成准确描述应用程序功能的自动化测试和文档。BDD 方法自然地与迭代或敏捷方法相一致,因为预先定义需求非常困难,并且随着团队对项目了解的深入,需求也会不断变化。 * **行为驱动开发****BDD**):在分析阶段开始时,软件消费者(最终用户或客户)和一些开发团队(通常是项目负责人、经理或分析师)之间进行了对话。这些对话用于具体化场景(即,建立对系统功能的共同理解的具体示例)。这些示例构成了使用 Cucumber 等工具开发验收测试的基础(有关更多详细信息,请参阅第 5 章、“JUnit 5 与外部框架的集成”)。BDD 中验收测试的描述(例如,在 Cucumber 中使用Cucumber)生成准确描述应用程序功能的自动化测试和文档。BDD 方法自然地与迭代或敏捷方法相一致,因为预先定义需求非常困难,并且随着团队对项目了解的深入,需求也会不断变化。
随着 2001 年[敏捷宣言](http://agilemanifesto.org/)的诞生,*敏捷*一词开始普及。它由 17 位软件从业者(肯特·贝克、詹姆斯·格朗宁、罗伯特·C·马丁、迈克·比德尔、吉姆·海史密斯、史蒂夫·梅勒、阿里·范本内库姆、安德鲁·亨特、肯·施瓦伯、阿利斯泰尔·科伯恩、罗恩·杰弗里斯、杰夫·萨瑟兰、沃德·坎宁安、乔恩·克恩、戴夫·托马斯、马丁·福勒和布赖恩·马里克)编写,并包括 12 条原则的列表,用于指导迭代和以人为中心的软件开发过程。基于这些原则,出现了一些软件开发框架,如 SCRUM、看板或极限编程(XP)。 随着 2001 年[敏捷宣言](http://agilemanifesto.org/)的诞生,*敏捷*一词开始普及。它由 17 位软件从业者(肯特·贝克、詹姆斯·格朗宁、罗伯特·C·马丁、迈克·比德尔、吉姆·海史密斯、史蒂夫·梅勒、阿里·范本内库姆、安德鲁·亨特、肯·施瓦伯、阿利斯泰尔·科伯恩、罗恩·杰弗里斯、杰夫·萨瑟兰、沃德·坎宁安、乔恩·克恩、戴夫·托马斯、马丁·福勒和布赖恩·马里克)编写,并包括 12 条原则的列表,用于指导迭代和以人为中心的软件开发过程。基于这些原则,出现了一些软件开发框架,如 SCRUM、看板或极限编程(XP)。
......
...@@ -1012,7 +1012,7 @@ JBehave 故事执行报告 ...@@ -1012,7 +1012,7 @@ JBehave 故事执行报告
有关 JBehave 及其优势的更多信息,请访问[这里](http://jbehave.org/) 有关 JBehave 及其优势的更多信息,请访问[这里](http://jbehave.org/)
# 黄瓜 # Cucumber
Cucumber 最初是一个 Ruby BDD 框架。如今,它支持多种语言,包括 Java。它提供的功能与 JBehave 非常相似。 Cucumber 最初是一个 Ruby BDD 框架。如今,它支持多种语言,包括 Java。它提供的功能与 JBehave 非常相似。
...@@ -1080,7 +1080,7 @@ $> gradle testCucumber ...@@ -1080,7 +1080,7 @@ $> gradle testCucumber
![](img/00ef22c3-dab2-47a2-a9b5-9677e2487c12.png) ![](img/00ef22c3-dab2-47a2-a9b5-9677e2487c12.png)
黄瓜故事执行报告 Cucumber故事执行报告
完整的代码示例可以在[存储库中找到](https://bitbucket.org/vfarcic/tdd-java-ch02-example-web) 完整的代码示例可以在[存储库中找到](https://bitbucket.org/vfarcic/tdd-java-ch02-example-web)
......
...@@ -355,7 +355,7 @@ Argument(s) are different! Wanted: ...@@ -355,7 +355,7 @@ Argument(s) are different! Wanted:
mongoCollection.save(Turn: 3; X: 2; Y: 1; Player: Y); mongoCollection.save(Turn: 3; X: 2; Y: 1; Player: Y);
``` ```
这次我们调用的是预期的方法,但传递给它的参数并不是我们所希望的。在规范中,我们将期望值设置为 Bean(new`TicTacToeBean(3, 2, 1, 'Y')`),在实现中,我们传递 null。不仅如此,Mockito 验证还可以告诉我们是否调用了正确的方法,以及传递给该方法的参数是否正确。 这次我们调用的是预期的方法,但传递给它的参数并不是我们所希望的。在规范中,我们将期望值设置为 Bean(new`TicTacToeBean(3, 2, 1, 'Y')`),在实现中,我们传递`null`。不仅如此,Mockito 验证还可以告诉我们是否调用了正确的方法,以及传递给该方法的参数是否正确。
规范的正确实施如下所示: 规范的正确实施如下所示:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册