1. 概述
本教程介紹 Cucumber,這是一種常用的用於用户驗收測試的工具,以及如何在 REST API 測試中使用它。
此外,為了使文章具有自包含性和獨立性,不受任何外部 REST 服務的影響,我們將使用 WireMock,一個樁化和模擬 Web 服務庫。如果您想了解更多關於該庫的信息,請參閲 WireMock 的介紹。
2. Gherkin – the Language of Cucumber
Cucumber is a testing framework that supports Behavior Driven Development (BDD), allowing users to define application operations in plain text. It works based on the Gherkin Domain Specific Language (DSL). This simple but powerful syntax of Gherkin lets developers and testers write complex tests while keeping it comprehensible to even non-technical users.
2.1. Introduction to Gherkin
Gherkin is a line-oriented language using line endings, indentations and keywords to define documents. Each non-blank line usually starts with a Gherkin keyword, followed by an arbitrary text, which is usually a description of the keyword.
The whole structure must be written into a file with the
Here is a simple Gherkin document example:
Feature: A short description of the desired functionality
Scenario: A business situation
Given a precondition
And another precondition
When an event happens
And another event happens too
Then a testable outcome is achieved
And something else is also completed
In the following sections, we’ll describe a couple of the most important elements in a Gherkin structure.
2.2. Feature
We use a Gherkin file to describe an application feature that needs to be tested. The file contains the
Cucumber parser skips all the text, except for the
2.3. Scenarios and Steps
A Gherkin structure may consist of one or more scenarios, recognized by the
These things are done using steps, identified by one of the five keywords:
Cucumber does not actually distinguish these keywords, however they are still there to make the feature more readable and consistent with the BDD structure.
3. Cucumber-JVM Implementation
Cucumber was originally written in Ruby and has been ported into Java with Cucumber-JVM implementation, which is the subject of this section.
3.1. Maven Dependencies
In order to make use of Cucumber-JVM in a Maven project, the following dependency needs to be included in the POM:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>6.8.0</version>
<scope>test</scope>
</dependency>
To facilitate JUnit testing with Cucumber, we need to have one more dependency:
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<version>6.8.0</version>
</dependency>
Alternatively, we can use another artifact to take advantage of lambda expressions in Java 8, which won’t be covered in this tutorial.
3.2. Step Definitions
Gherkin scenarios would be useless if they were not translated into actions and this is where step definitions come into play. Basically, a step definition is an annotated Java method with an attached pattern whose job is to convert Gherkin steps in plain text to executable code. After parsing a feature document, Cucumber will search for step definitions that match predefined Gherkin steps to execute.
In order to make it clearer, let’s take a look at the following step:
Given I have registered a course in Baeldung
And a step definition:
@Given("I have registered a course in Baeldung")
public void verifyAccount() {
// method implementation
}
When Cucumber reads the given step, it will be looking for step definitions whose annotating patterns match the Gherkin text.
4. 創建和運行測試
4.1. 編寫功能文件
讓我們從在以
Feature: 測試 REST API
用户應該能夠向 Web 服務發送 GET 和 POST 請求,
由 WireMock 表示
Scenario: 向 Web 服務上傳數據
當用户在項目上上傳數據
則服務器應處理它並返回成功狀態
Scenario: 從 Web 服務檢索數據
當用户想要獲取 'Cucumber' 項目的信息
則請求的數據應返回
我們現在將此文件保存到名為
4.2. 配置 JUnit 與 Cucumber 協作
為了使 JUnit 意識到 Cucumber 並讀取功能文件以進行運行時執行,
@RunWith(Cucumber.class)
@CucumberOptions(features = "classpath:Feature")
public class CucumberIntegrationTest {
}
如你所見,
4.3. 編寫步驟定義
當 Cucumber 解析步驟時,它將搜索帶有 Gherkin 關鍵字的註解的方法以查找匹配的步驟定義。
步驟定義的表達式可以是正則表達式或 Cucumber 表達式。在本教程中,我們將使用 Cucumber 表達式。
以下方法完全匹配 Gherkin 步驟。該方法將用於向 REST Web 服務發送數據:
@When("users upload data on a project")
public void usersUploadDataOnAProject() throws IOException {
}
這裏有一個匹配 Gherkin 步驟的方法,並且從文本中獲取一個參數,該參數將用於從 REST Web 服務檢索信息:
@When("users want to get information on the {string} project")
public void usersGetInformationOnAProject(String projectName) throws IOException {
}
如你所見,
或者,我們可以使用正則表達式:
@When("^users want to get information on the '(.+)' project$")
public void usersGetInformationOnAProject(String projectName) throws IOException {
}
請注意,
我們將提供上述兩種方法的運行代碼在下一部分。
4.4. 創建和運行測試
首先,我們將使用 JSON 結構來説明由 POST 請求上傳到服務器的數據,並使用 GET 請求下載到客户端。此結構已保存到
{
"testing-framework": "cucumber",
"supported-language":
[
"Ruby",
"Java",
"Javascript",
"PHP",
"Python",
"C++"
],
"website": "cucumber.io"
}
為了演示 REST API,我們使用 WireMock 服務器:
WireMockServer wireMockServer = new WireMockServer(options().dynamicPort());
此外,我們將使用 Apache HttpClient API 來表示用於連接到服務器的客户端:
CloseableHttpClient httpClient = HttpClients.createDefault();
現在,讓我們在步驟定義中編寫測試代碼。我們將首先為
服務器在客户端連接之前必須運行:
wireMockServer.start();
使用 WireMock API 間接 REST 服務:
configureFor("localhost", wireMockServer.port());
stubFor(post(urlEqualTo("/create"))
.withHeader("content-type", equalTo("application/json"))
.withRequestBody(containing("testing-framework"))
.willReturn(aResponse().withStatus(200)));
現在,向服務器發送帶有
HttpPost request = new HttpPost("http://localhost:" + wireMockServer.port() + "/create");
StringEntity entity = new StringEntity(jsonString);
request.addHeader("content-type", "application/json");
request.setEntity(entity);
HttpResponse response = httpClient.execute(request);
以下代碼斷言 POST 請求已成功接收並處理:
assertEquals(200, response.getStatusLine().getStatusCode());
verify(postRequestedFor(urlEqualTo("/create"))
.withHeader("content-type", equalTo("application/json")));
服務器在被使用後應停止:
wireMockServer.stop();
我們將實現的第二個方法是
wireMockServer.start();
configureFor("localhost", wireMockServer.port());
stubFor(get(urlEqualTo("/projects/cucumber"))
.withHeader("accept", equalTo("application/json"))
.willReturn(aResponse().withBody(jsonString)));
提交 GET 請求並接收響應:
HttpGet request = new HttpGet("http://localhost:" + wireMockServer.port() + "/projects/" + projectName.toLowerCase());
request.addHeader("accept", "application/json");
HttpResponse httpResponse = httpClient.execute(request);
我們將使用一個幫助方法將
String responseString = convertResponseToString(httpResponse);
這裏是該轉換幫助方法的實現:
private String convertResponseToString(HttpResponse response) throws IOException {
InputStream responseStream = response.getEntity().getContent();
Scanner scanner = new Scanner(responseStream, "UTF-8");
String responseString = scanner.useDelimiter("\\Z").next();
scanner.close();
return responseString;
}
以下驗證整個過程:
assertThat(responseString, containsString("\"testing-framework\": \"cucumber\""));
assertThat(responseString, containsString("\"website\": \"cucumber.io\""));
verify(getRequestedFor(urlEqualTo("/projects/cucumber"))
.withHeader("accept", equalTo("application/json")));
最後,如之前所述停止服務器。
5. 並行運行功能
Cucumber-JVM 本身支持跨多個線程的並行測試執行。我們將使用 JUnit 結合 Maven Failsafe 插件來執行運行器。 另一種方法是使用 Maven Surefire。
JUnit 以功能文件為單位並行運行,而不是場景,這意味着 同一線程將執行功能文件中所有場景。
現在,讓我們添加插件配置:
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven-failsafe-plugin.version}</version>
<configuration>
<includes>
<include>CucumberIntegrationTest.java</include>
</includes>
<parallel>methods</parallel>
<threadCount>2</threadCount>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
注意:
- parallel:classes, methods或兩者都使用 – 在我們的情況下,classes將使每個測試類在單獨的線程中運行
- threadCount:指示為此執行應分配的線程數
這就是運行 Cucumber 功能的全部內容。
6. 結論
在本教程中,我們介紹了 Cucumber 的基本知識以及該框架如何使用 Gherkin 領域特定語言來測試 REST API。