1. 简介

本文将快速介绍 JBehave 框架,并重点演示如何从 BDD(行为驱动开发)视角测试 REST API。

2. JBehave 与 BDD

JBehave 是一个行为驱动开发框架,旨在提供直观且易用的自动化验收测试方案。

如果你不熟悉 BDD,建议先阅读这篇介绍 Cucumber(另一个 BDD 框架)的文章,其中涵盖了 BDD 的通用结构和特性。

与其他 BDD 框架类似,JBehave 采用以下核心概念:

  • Story(故事):表示可自动执行的业务功能单元,包含一个或多个场景
  • Scenarios(场景):描述系统行为的具体示例
  • Steps(步骤):使用经典 BDD 关键词(GivenWhenThen)实现实际行为

典型场景示例:

Given 前置条件
When 触发事件
Then 验证结果

每个场景步骤对应 JBehave 中的注解:

  • @Given:初始化上下文
  • @When:执行操作
  • @Then:验证预期结果

3. Maven 依赖

在 Maven 项目中使用 JBehave,需在 pom.xml 中添加 jbehave-core 依赖:

<dependency>
    <groupId>org.jbehave</groupId>
    <artifactId>jbehave-core</artifactId>
    <version>4.1</version>
    <scope>test</scope>
</dependency>

4. 快速上手

使用 JBehave 需遵循以下步骤:

  1. 编写用户故事
  2. 将故事步骤映射到 Java 代码
  3. 配置用户故事
  4. 运行测试
  5. 查看结果

4.1. 编写故事

以简单故事为例:"作为用户,我希望增加计数器,使其值递增 1"。在 .story 文件中定义:

Scenario: 用户增加计数器时,其值应增加 1

Given 一个计数器
And 计数器初始值为任意整数
When 用户增加计数器
Then 计数器值应比之前大 1

4.2. 步骤映射

将上述步骤实现为 Java 代码:

public class IncreaseSteps {
    private int counter;
    private int previousValue;

    @Given("一个计数器")
    public void aCounter() {
    }

    @Given("计数器初始值为任意整数")
    public void counterHasAnyIntegralValue() {
        counter = new Random().nextInt();
        previousValue = counter;
    }

    @When("用户增加计数器")
    public void increasesTheCounter() {
        counter++;
    }

    @Then("计数器值应比之前大 1")
    public void theValueOfTheCounterMustBe1Greater() {
        assertTrue(1 == counter - previousValue);
    }
}

⚠️ 注意:注解中的值必须与故事描述完全匹配

4.3. 配置故事

配置故事运行环境:

public class IncreaseStoryLiveTest extends JUnitStories {

    @Override
    public Configuration configuration() {
        return new MostUsefulConfiguration()
          .useStoryLoader(new LoadFromClasspath(this.getClass()))
          .useStoryReporterBuilder(new StoryReporterBuilder()
            .withCodeLocation(codeLocationFromClass(this.getClass()))
            .withFormats(CONSOLE));
    }

    @Override
    public InjectableStepsFactory stepsFactory() {
        return new InstanceStepsFactory(configuration(), new IncreaseSteps());
    }

    @Override
    protected List<String> storyPaths() {
        return Arrays.asList("increase.story");
    }
}
  • storyPaths():指定 .story 文件路径
  • stepsFactory():提供步骤实现类
  • configuration():配置故事加载器和报告生成器

执行命令 mvn clean test 即可运行测试。

4.4. 查看测试结果

控制台输出测试结果。测试通过时,输出与故事描述一致:

Scenario: 用户增加计数器时,其值应增加 1
Given 一个计数器
And 计数器初始值为任意整数
When 用户增加计数器
Then 计数器值应比之前大 1

若未实现某个步骤(如 @When),报告会明确提示:

Scenario: 用户增加计数器时,其值应增加 1
Given 一个计数器
And 计数器初始值为任意整数
When 用户增加计数器 (PENDING)
Then 计数器值应比之前大 1 (NOT PERFORMED)

对应代码:

@When("用户增加计数器")
@Pending
public void whenTheUserIncreasesTheCounter() {
    // PENDING
}

@Then 断言失败,错误信息直接显示:

Scenario: 用户增加计数器时,其值应增加 1
Given 一个计数器
And 计数器初始值为任意整数
When 用户增加计数器
Then 计数器值应比之前大 1 (FAILED)
(java.lang.AssertionError)

5. 测试 REST API

掌握 JBehave 基础后,我们用它测试 REST API。测试案例基于之前的 Java REST API 测试文章,主要验证 GitHub API 的响应状态码、头信息和载荷。

5.1. 测试状态码

故事描述:

Scenario: 用户查询不存在的 GitHub 用户时,应返回 404

Given GitHub 用户资料 API
And 随机不存在的用户名
When 通过 API 查询该用户
Then GitHub 返回 404 状态码

When 通过 API 查询 eugenp1
Then GitHub 返回 404 状态码

When 通过 API 查询 eugenp2
Then GitHub 返回 404 状态码

步骤实现:

public class GithubUserNotFoundSteps {
    private String api;
    private String nonExistentUser;
    private int githubResponseCode;

    @Given("GitHub 用户资料 API")
    public void givenGithubUserProfileApi() {
        api = "https://api.github.com/users/%s";
    }

    @Given("随机不存在的用户名")
    public void givenANonexistentUsername() {
        nonExistentUser = randomAlphabetic(8);
    }

    @When("通过 API 查询该用户")
    public void whenILookForTheUserViaTheApi() throws IOException {
        githubResponseCode = getGithubUserProfile(api, nonExistentUser)
          .getStatusLine()
          .getStatusCode();
    }

    @When("通过 API 查询 $user")
    public void whenILookForSomeNonExistentUserViaTheApi(String user) throws IOException {
        githubResponseCode = getGithubUserProfile(api, user)
          .getStatusLine()
          .getStatusCode();
    }

    @Then("GitHub 返回 404 状态码")
    public void thenGithubRespond404NotFound() {
        assertTrue(SC_NOT_FOUND == githubResponseCode);
    }
    //...
}

参数注入:步骤中的参数(如 $user)会按顺序映射到 Java 方法参数。也支持命名参数:

@When("通过 API 查询 $username")
public void whenILookForUser(@Named("username") String user) throws IOException

5.2. 测试媒体类型

验证 MIME 类型的故事:

Scenario: 用户查询有效 GitHub 用户时,应返回 JSON 数据

Given GitHub 用户资料 API
And 有效用户名
When 通过 API 查询该用户
Then GitHub 返回 JSON 格式数据

步骤实现:

public class GithubUserResponseMediaTypeSteps {
    private String api;
    private String validUser;
    private String mediaType;

    @Given("GitHub 用户资料 API")
    public void givenGithubUserProfileApi() {
        api = "https://api.github.com/users/%s";
    }

    @Given("有效用户名")
    public void givenAValidUsername() {
        validUser = "eugenp";
    }

    @When("通过 API 查询该用户")
    public void whenILookForTheUserViaTheApi() throws IOException {
        mediaType = ContentType
          .getOrDefault(getGithubUserProfile(api, validUser).getEntity())
          .getMimeType();
    }

    @Then("GitHub 返回 JSON 格式数据")
    public void thenGithubRespondDataOfTypeJson() {
        assertEquals("application/json", mediaType);
    }
}

5.3. 测试 JSON 载荷

验证响应载荷的故事:

Scenario: 用户查询有效 GitHub 用户时,返回的 JSON 应包含匹配的用户名

Given GitHub 用户资料 API
When 通过 API 查询 eugenp
Then 响应中 'login' 字段值应为 eugenp

步骤实现:

public class GithubUserResponsePayloadSteps {
    private String api;
    private GitHubUser resource;

    @Given("GitHub 用户资料 API")
    public void givenGithubUserProfileApi() {
        api = "https://api.github.com/users/%s";
    }

    @When("通过 API 查询 $user")
    public void whenILookForUserViaTheApi(String user) throws IOException {
        HttpResponse httpResponse = getGithubUserProfile(api, user);
        resource = RetrieveUtil.retrieveResourceFromResponse(httpResponse, GitHubUser.class);
    }

    @Then("响应中 'login' 字段值应为 $username")
    public void thenResponseContainsLoginPayload(String username) {
        assertThat(username, Matchers.is(resource.getLogin()));
    }
}

6. 总结

本文简要介绍了 JBehave 框架,并实现了 BDD 风格的 REST API 测试。

与原生 Java 测试代码相比,JBehave 实现的代码更清晰直观,测试报告也更优雅。完整示例代码可在 GitHub 项目 中获取。