揭秘行为驱动开发与 Cucumber-JVM
本文旨在满足希望快速入门 BDD 实践和 Cucumber-JVM 的用户的需求。
一点理论
撇开诸如可重用性、可维护性和可伸缩性等花哨的端到端测试编写术语,我总是喜欢为它们的编写提供一个简单的定义。那就是,测试用例应该以一种可以任意次数、任意顺序、使用各种不同数据集运行的方式编写和组织。然而,这并非听起来那么简单。这种测试编写方法要求不同的团队从第一天起就开始协作讨论产品行为。因此,行为驱动开发(Behavior Driven Development)在其整体上都基于三个朋友(业务分析师、开发人员和测试人员)之间的公平协作。
有趣的是,BDD 测试流行的主要原因是它使用了非技术性、清晰、简洁的纯英语(或您选择的任何其他国际语言 [1])语言。这样,业务所有者就可以通过以一种不仅被不同团队(开发人员和测试人员)而且也被测试框架理解的语言来指定需求,从而发挥重要且及时的作用。
在我们 Cucumber-JVM 的例子中,常用的可理解语言是 Gherkin,它塑造了整体概念。Gherkin 是一种没有技术障碍的语言;它迫使整个团队通过创造性的协作来编写明确的基于需求的测试规范,而不是技术细节。因此,BDD 拥抱了通过使需求从利益相关者到测试人员都更加清晰和直接来简化事情的思想。
$ cucumber --i18n help
先决条件
如最初所述,本文旨在通过 Cucumber 在 Java 环境中演示 BDD 测试方法的实现,并深入关注其 Gherkin 语言。因此,读者应该具备 Java 的中级知识,包括基本的 OOP 概念,以及对构建工具(Maven 或 Gradle)的理解。此外,还需要管理依赖项以及使用 FileInputStream 来读取属性中的值。
免责声明
如果您不满足这些先决条件,请鼓起勇气进行一些逆向工程来理解所写的代码,并在本文提供的 Git 仓库的基础上建立一些理解。
实践见解
在通过检查 Gherkin 语法和 Cucumber-JVM 结构中的各种示例来深入研究实践见解之前,请允许我简要展示一下我们将通过本文学习的项目的高层视图。
尽管图 1 中的项目结构本身就很有说明性,但我还是会花几分钟来描述一下。首先,必须有两个主要文件夹——main 和 test。main 包含所有与自动化框架相关的代码,这些代码应该是模块化和可伸缩的,取决于被测试的应用程序动态。相比之下,test 文件夹仅用于测试用例及其元数据。
语法概述 – 使用 Gherkin 编写测试
test/resources 文件夹中的 .feature 文件用于测试规范,建议每个功能使用一个文件以实现模块化和可读性。由于使用 Gherkin 编写测试规范的主要思想是使整个测试过程对所有成员(尤其是技术知识不多的成员)都易于理解;因此,它有三个主要语法元素,没有它们,测试将不会被标记为完成。这些是 Given
、When
、Then
。尽管如此,整个测试用例必须在测试脚本的顶部有一个 Scenario 或 Scenario Outline [2],定义整个脚本的主要目标或任务。此外,Gherkin 还支持测试脚本中的一个或多个 And
语句。这样,Gherkin 的主要关键字或多或少都列在这里了。
- 功能
- Scenario 和 Scenario Outline
- 背景
- Given、When、Then、And、But
- Example 和 Examples
我将把学习和理论理解留给您,因为 官方文档在这方面非常有帮助。
Scenario 与 Scenario Outline
Scenario 是可执行测试规范的高级、单行定义。它包含 Given、When、Then 等步骤。然而,如果我们必须为多个示例运行相同的测试用例,那么我们的 Scenario 将不得不使用硬编码值进行重复,这通常是不可取的。
或者,可以使用 Scenario Outline 来用多个数据示例重复同一个 Scenario。因此,Scenario Outline 可以更好地理解为“参数化方法”。
以其官方文档中的以下示例为例
示例 1
显然,Example1 不是一个好的例子,因为冗余永远是不可取的,尤其是在它仅仅涉及硬编码值时。
示例 2
在 example2 中,事情看起来是解释性和符合逻辑的。
Google 图书 Web 服务
在本节中,我们将研究取自我为本文创建的演示项目的代码片段。现在我们将讨论用于测试示例 Web 服务 – Google 图书功能的代码。
编写可执行规范
请注意,我只会解释一个端到端的测试用例示例。其余的将由您通过查看此项目的 git 仓库来理解。
// src/test/resources/GoogleBooks.feature
Feature: Google Book Searching from https://www.googleapis.com/books/v1/volumes?q={ID}
Scenario Outline: Verify that the response status code is 200 and content type is JSON.
Given webService endpoint is up
When user sends a get request to webService endpoint using following details
| ID | <ID> |
Then verify <statusCode> and <contentType> from webService endpoint response
Examples:
| ID | statusCode | contentType |
| 1 | 200 | "application/json; charset=UTF-8" |
| 9546 | 200 | "application/json; charset=UTF-8" |
| 9 | 200 | "application/json; charset=UTF-8" |
首先,您将为每个打算测试的功能创建 feature 文件(在 test/resources 文件夹中),并使用 Feature
关键字为其提供描述。请注意,我们可以在一个 Feature
关键字下拥有多个 Scenarios(或 Scenarios Outlines),它们当然在功能上是相互关联的。
将 Scenario(或 Scenarios Outline)视为一个父代码块——它包含其范围内的所有后续语句,因此我们称它们为步骤。
接下来,我们有 Given
、When
、Then
,以及多个数据示例。Given
关键字确保给定的 Web 服务端点已启动;这是我们必须测试某些功能的提供状态。
然后我们有一个 When
语句,用于测试特定的用户操作 – 即,向 Web 服务的基 URL 发送带有唯一请求 ID 的 get 请求。由于请求 ID 是唯一的,并且每个页面请求都有不同的 ID,我们将它作为参数,以便我们可以传递多个 ID 并为每个给定的 ID 运行底层的示例 Scenario 多次。
最后,我们有一个 Then
语句以及 Examples
表。这确保了在执行 When
块中指定的用户操作后预期的行为。当然,<statusCode>
和 <contentType>
是我们希望验证的预期行为,因此我们将其作为参数传递并在 Examples 部分定义。要了解更多关于 Example Mapping 的工作原理,请参阅此 官方阅读。
编写相应的步骤定义文件
一旦我们编写了可执行规范(简单来说,就是在 Gherkin 中编写的测试脚本),我们就必须编写其步骤定义文件(即具有 public
方法的 Java 代码,每个方法对应 feature 文件中 Scenario(或 Scenario Outline)下的每个步骤)。是的,您没听错。这意味着每个 feature 文件都会有一个单独的步骤定义文件。换句话说,步骤定义文件是一个 Java 类,为 feature 文件中编写的每个 Given
、When
、Then
语句提供专门的函数。
// src/test/java/com/googlebooks/stepDefinitions/BookStepDefinition.java
@Given("webService endpoint is up")
public void webserviceEndpointIsUp() {
requestSpecification = new RequestSpecBuilder().
setBaseUri(prop.getProperty(BASE_URL)).
build();
}
@When("user sends a get request to webService endpoint using following details")
public void userSendsAGetRequestToWebServiceEndpointUsingID(Map<String, String> data) {
String ID = data.get("ID");
System.out.println("The current ID is: " + ID);
String pathParameters = "?q" + "=" + ID;
response = given().
spec(requestSpecification).
when().
get(pathParameters);
booksVolumePojo = GoogleBooksBusinessLogic.getBooksVolumeResponseObject(response);
}
@Then("verify {int} and {string} from webService endpoint response")
public void verifyResponseStatusCodeAndContentTypeFromWebServiceEndpointResponse
(int statusCode, String contentType) {
Assert.assertEquals(statusCode, response.getStatusCode());
Assert.assertEquals(contentType, response.getContentType());
}
上面的代码片段是上述 Gherkin 测试规范示例的步骤定义。花点时间将 feature 文件中的 Given
的措辞与其示例中的 @Given
定义进行匹配。同样,也匹配 When
和 Then
。下面的图片可以帮助您做到这一点。
编写 Cucumber 测试运行器类 – 粘合代码
到目前为止,您应该肯定在想一个我还没有谈过的主要遗漏。那就是映射代码,或任何桥接类,或者用更面向 Cucumber-JVM 的术语来说,就是粘合代码。我们需要以下粘合代码来在 feature 文件和相应的步骤定义文件之间建立映射引用。
// src/test/java/com/googlebooks/Runner/TestRunner.java
package com.googlebooks.Runner;
import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(glue = {"com.googlebooks.stepDefinitions"}, features = "src/test/resources",
plugin = {"pretty", "html:target/site/cucumber-pretty",
"json:target/cucumber.json"})
public class TestRunner {
}
一些额外的东西
到目前为止,我已经解释了足够多的内容,可以帮助您开始使用 Cucumber-JVM 并获得编写简单 Gherkin 测试的初步经验。但是,我不想让您抓耳挠腮地想为什么在链接的 git 仓库(如果您确信至少看一下的话)中有这么多其他东西。因此,我想添加这一部分作为补充。请享用,并尝试理解最后的一些内容。
图 1 显示了项目结构。有几个文件夹可能会让您感到困惑;但是,它们为我们的测试项目(框架)带来了模块化和可伸缩性。下面是对这些组件的解释。
业务逻辑类
// src/main/java/com/googlebooks/businessLogic/GoogleBooksBusinessLogic.java
public class GoogleBooksBusinessLogic {
public static BooksVolume getBooksVolumeResponseObject(Response jsonResponse){
BooksVolume booksVolumePojo = jsonResponse.getBody().as(BooksVolume.class);
System.out.println("String representation of booksVolume response pojo " +
booksVolumePojo.toString());
return booksVolumePojo;
}
}
业务逻辑层旨在涵盖一种处理某些内部、应用程序/业务特定需求的、或处理方式。例如,将数据从一种形式转换为另一种形式,以便在某个地方进行集中访问。在上面的代码中,我们将从 Web 服务收到的 JSON 响应转换为 Java Pojo – `BooksVolume
`。当我需要解析 JSON 到 Java 对象的响应时,我将调用此函数。请注意,出于演示目的,我只使用了一个示例,因此您只看到了一种转换。在大型项目中,您可能有许多此类转换和逻辑实现。除了其他优点外,这种转换允许我在多个需要处理相同响应的测试用例中重用此转换后的 Json 响应到 Java 对象。此外,它将使代码更加整洁,因为一旦您获得了转换后的 Java 对象,其余的代码流程将遵循相同的 Pojo 结构,从而使您更容易遵循面向对象规则。
属性读取器类
// src/main/java/com/googlebooks/utils/PropertyReader.java
public class PropertyReader {
protected static Properties prop;
public PropertyReader() {
try{
prop = new Properties();
FileInputStream ipStream = new FileInputStream(System.getProperty("user.dir") +
"/src/main/resources"
+ "/config/config.properties");
prop.load(ipStream);
}
catch (IOException exc){
exc.printStackTrace();
}
}
}
从它的代码和名称来看,您认为它的功能是什么?当然,它是用于读取外部可配置的常见属性,如基础 URL、IP、端口以及其他不应包含在项目编译中的变量,因此将其保存在我们代码库的资源文件夹中。在这里,我们的配置文件以 .properties 扩展名保存。因此,任何需要从 PropertyReader
类读取属性的类,例如我们的 `BookStepDefinition
` 类,都将扩展此类。
运行您的项目
要执行您的测试用例,您需要在终端中运行 mvn test
。
以下是我通过滚动窗口到最底部时得到的构建输出。
家庭作业练习
此项目旨在设计和解释,以帮助您快速入门 BDD 和 Cucumber-JVM。因此,如果您真的想编写好的 Gherkin 测试,请在项目中进行以下更改。
- 使用您选择的日志库(例如
slf4j
)添加适当的日志记录。 - 在 feature 文件中添加更多场景 – gherkin 测试,并在 definition 文件中添加其步骤定义。
- 如有必要,在属性读取器中添加变量。
- 探索 BDD 提供的报告库和 serenity 文档功能。Cucumber 最初是为 Ruby 设计的,因此请仔细研究 Cucumber-JVM 如何提供与 Ruby 中的 BDD 不同的 BDD 实现。
历史
- 2020 年 9 月 4 日:初始版本