本教程介绍Cucumber——一个常用的用户验收测试工具,以及如何在REST API测试中使用它。为使文章自包含且不依赖外部REST服务,我们将使用WireMock(一个桩服务和模拟Web服务库)。想深入了解该库可参考WireMock入门指南

2. Gherkin – Cucumber的语言

Cucumber是支持行为驱动开发(BDD)的测试框架,允许用户用纯文本定义应用操作。其核心是Gherkin领域特定语言(DSL)。这种简洁而强大的语法让开发者和测试人员能编写复杂测试,同时保持非技术人员的可读性。

2.1. Gherkin简介

Gherkin是一种行导向语言,通过行结束符、缩进和关键词定义文档。每个非空行通常以Gherkin关键词开头,后接任意描述文本。整个结构需写入扩展名为.feature的文件才能被Cucumber识别。

简单示例:

Feature: 所需功能的简短描述

  Scenario: 业务场景
    Given 前置条件
    And 另一个前置条件
    When 事件发生
    And 另一个事件也发生
    Then 可验证的结果达成
    And 其他事项也完成

2.2. 功能(Feature)

Gherkin文件用于描述需要测试的应用功能。文件以Feature关键词开头,同行紧跟功能名称,下方可跨行添加可选描述。Cucumber解析器会跳过除Feature外的所有文本,仅作为文档保留。

2.3. 场景与步骤(Scenarios and Steps)

Gherkin结构包含一个或多个场景(Scenario关键词标识)。场景本质是测试,用于验证应用能力,需描述初始上下文、可能发生的事件及预期结果。通过步骤实现,步骤由五个关键词标识:

  • Given:将系统置于用户交互前的明确定义状态,可视为用例前置条件
  • When:描述应用事件(用户操作或其他系统触发的事件)
  • Then:指定测试预期结果,结果应与被测功能的业务价值相关
  • And/But:替换上述关键词,用于连接多个同类型步骤

Cucumber实际不区分这些关键词,但它们的存在提升了可读性和BDD结构一致性。

3. Cucumber-JVM实现

Cucumber最初用Ruby编写,后移植到Java形成Cucumber-JVM实现。

3.1. Maven依赖

在Maven项目中需添加以下依赖:

<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-java</artifactId>
    <version>6.8.0</version>
    <scope>test</scope>
</dependency>

为支持JUnit测试,还需添加:

<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-junit</artifactId>
    <version>6.8.0</version>
</dependency>

3.2. 步骤定义(Step Definitions)

Gherkin场景需转换为动作,这就是步骤定义的作用。步骤定义本质是带模式注解的Java方法,用于将纯文本Gherkin步骤转换为可执行代码。Cucumber解析功能文档后,会搜索匹配预定义Gherkin步骤的步骤定义执行。

示例: Gherkin步骤:

Given 我已在Baeldung注册课程

步骤定义:

@Given("我已在Baeldung注册课程")
public void verifyAccount() {
    // 方法实现
}

当Cucumber读取该步骤时,会寻找注解模式匹配Gherkin文本的步骤定义。

4. 创建和运行测试

4.1. 编写功能文件

创建扩展名为.feature的文件,声明场景和步骤:

Feature: 测试REST API
  用户应能向WireMock表示的Web服务提交GET和POST请求

  Scenario: 向Web服务上传数据
    When 用户上传项目数据
    Then 服务器应处理并返回成功状态

  Scenario: 从Web服务检索数据
    When 用户想获取'Cucumber'项目信息
    Then 返回请求的数据

将文件保存在Feature目录(需在运行时加载到类路径,如src/main/resources)。

4.2. 配置JUnit与Cucumber协同工作

声明Cucumber作为Runner,并指定功能文件和步骤定义的搜索路径:

@RunWith(Cucumber.class)
@CucumberOptions(features = "classpath:Feature")
public class CucumberIntegrationTest {
    
}
  • features元素定位功能文件
  • glue元素提供步骤定义路径(若测试用例和步骤定义在同一包中可省略)

4.3. 编写步骤定义

Cucumber解析步骤时,会搜索带Gherkin关键词注解的方法匹配步骤定义。步骤定义表达式可以是正则表达式或Cucumber表达式(本教程使用后者)。

匹配POST请求步骤的方法:

@When("用户上传项目数据")
public void usersUploadDataOnAProject() throws IOException {
    
}

带参数的GET请求步骤方法:

@When("用户想获取{string}项目信息")
public void usersGetInformationOnAProject(String projectName) throws IOException {
    
}

参数通过注解中的{string}声明(对应步骤文本中的Cucumber)。也可使用正则表达式:

@When("^用户想获取'(.+)'项目信息$")
public void usersGetInformationOnAProject(String projectName) throws IOException {
    
}

4.4. 创建和运行测试

数据结构示例(POST请求上传/GET请求下载):

{
    "testing-framework": "cucumber",
    "supported-language": 
    [
        "Ruby",
        "Java",
        "Javascript",
        "PHP",
        "Python",
        "C++"
    ],
    "website": "cucumber.io"
}

初始化服务与客户端:

WireMockServer wireMockServer = new WireMockServer(options().dynamicPort());
CloseableHttpClient httpClient = HttpClients.createDefault();

实现usersUploadDataOnAProject方法:

@When("用户上传项目数据")
public void usersUploadDataOnAProject() throws IOException {
    // 启动服务器
    wireMockServer.start();
    
    // 桩接REST服务
    configureFor("localhost", wireMockServer.port());
    stubFor(post(urlEqualTo("/create"))
      .withHeader("content-type", equalTo("application/json"))
      .withRequestBody(containing("testing-framework"))
      .willReturn(aResponse().withStatus(200)));
    
    // 发送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);
    
    // 验证响应
    assertEquals(200, response.getStatusLine().getStatusCode());
    verify(postRequestedFor(urlEqualTo("/create"))
      .withHeader("content-type", equalTo("application/json")));
    
    // 停止服务器
    wireMockServer.stop();
}

实现usersGetInformationOnAProject方法:

@When("用户想获取{string}项目信息")
public void usersGetInformationOnAProject(String projectName) throws IOException {
    // 启动服务器
    wireMockServer.start();
    
    // 桩接REST服务
    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);
    
    // 验证响应
    assertThat(responseString, containsString("\"testing-framework\": \"cucumber\""));
    assertThat(responseString, containsString("\"website\": \"cucumber.io\""));
    verify(getRequestedFor(urlEqualTo("/projects/cucumber"))
      .withHeader("accept", equalTo("application/json")));
    
    // 停止服务器
    wireMockServer.stop();
}

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

5. 并行运行功能

Cucumber-JVM原生支持多线程并行测试。使用JUnit配合Maven Failsafe插件执行运行器(也可用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/bothclasses使每个测试类在独立线程运行)
  • threadCount:指定分配线程数

6. 总结

本教程介绍了Cucumber基础,以及该框架如何使用Gherkin领域特定语言测试REST API。所有代码示例可在GitHub获取。


原始标题:REST API testing with Cucumber

« 上一篇: WireMock 教程