使用 Cucumber 進行 REST API 測試

REST,Testing
Remote
1
03:27 PM · Dec 01 ,2025

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 feature extension to be recognized by Cucumber.

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 Feature keyword at the very beginning, followed up by the feature name on the same line and an optional description that may span multiple lines underneath.

Cucumber parser skips all the text, except for the Feature keyword, and includes it for the purpose of documentation only.

2.3. Scenarios and Steps

A Gherkin structure may consist of one or more scenarios, recognized by the Scenario keyword. A scenario is basically a test allowing users to validate a capability of the application. It should describe an initial context, events that may happen and expected outcomes created by those events.

These things are done using steps, identified by one of the five keywords: Given, When, Then, And, and But.

  • Given: This step is to put the system into a well-defined state before users start interacting with the application. A Given clause can by considered a precondition for the use case.
  • When: A When step is used to describe an event that happens to the application. This can be an action taken by users, or an event triggered by another system.
  • Then: This step is to specify an expected outcome of the test. The outcome should be related to business values of the feature under test.
  • And and But: These keywords can be used to replace the above step keywords when there are multiple steps of the same type.

    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 結尾的文件中聲明場景和步驟開始:

    Feature: 測試 REST API
      用户應該能夠向 Web 服務發送 GET 和 POST 請求,
      由 WireMock 表示
    
      Scenario: 向 Web 服務上傳數據
        當用户在項目上上傳數據
        則服務器應處理它並返回成功狀態
    
      Scenario: 從 Web 服務檢索數據
        當用户想要獲取 'Cucumber' 項目的信息
        則請求的數據應返回

    我們現在將此文件保存到名為 Feature 的目錄中,條件是目錄將被加載到運行時類路徑中,例如 src/main/resources

    4.2. 配置 JUnit 與 Cucumber 協作

    為了使 JUnit 意識到 Cucumber 並讀取功能文件以進行運行時執行,Cucumber 類必須被聲明為 Runner。我們還需要告訴 JUnit 搜索功能文件和步驟定義的位置。

    @RunWith(Cucumber.class)
    @CucumberOptions(features = "classpath:Feature")
    public class CucumberIntegrationTest {
        
    }

    如你所見,CucumberOption 中的 features 元素會定位在功能文件中創建的。另一個重要的元素,稱為 glue,提供指向步驟定義的路徑。但是,如果測試用例和步驟定義位於與本教程相同的包中,則該元素可以被省略。

    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 {
        
    }

    如你所見,usersGetInformationOnAProject 方法接受一個 String 參數,即項目名稱。此參數由 {string} 註解聲明,在這裏它與 Cucumber 中的文本相對應。

    或者,我們可以使用正則表達式:

    @When("^users want to get information on the '(.+)' project$")
    public void usersGetInformationOnAProject(String projectName) throws IOException {
        
    }

    請注意,‘^’‘$’ 表示正則表達式的起始和結束位置,而 ‘(.+)’ 對應於 String 參數。

    我們將提供上述兩種方法的運行代碼在下一部分。

    4.4. 創建和運行測試

    首先,我們將使用 JSON 結構來説明由 POST 請求上傳到服務器的數據,並使用 GET 請求下載到客户端。此結構已保存到 jsonString 字段中,如下所示:

    {
        "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();

    現在,讓我們在步驟定義中編寫測試代碼。我們將首先為 usersUploadDataOnAProject 方法執行此操作。

    服務器在客户端連接之前必須運行:

    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)));

    現在,向服務器發送帶有 jsonString 字段中提取的內容的 POST 請求:

    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();

    我們將實現的第二個方法是 usersGetInformationOnAProject(String projectName)。 類似於第一次測試,我們需要啓動服務器,然後間接 REST 服務:

    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);

    我們將使用一個幫助方法將 httpResponse 變量轉換為 String

    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。

  • user avatar
    0 位用戶收藏了這個故事!
    收藏

    發佈 評論

    Some HTML is okay.