Cucumber 黄瓜测试 BDD 从入门到精通

1. Cucumber

Cucumber 是 BDD(Behavior-Driven Development,行为驱动开发)的一个自动化测试工具,使用自然语言来描述测试用例,使得 非研发(QA、PM)也可以理解甚至编写 测试用例。

官方表示:应该将 Cucumber 视为一个【文档编写工具】,而非一个单纯的自动化测试工具

撰写时,应该要以 PM 也能理解 测试用例 为目标去编写 Cucumber

2. Gherkin

Gherkin 是 Cucumber 用来描述 测试用例 的语言,以下为关键字的用意与关联关系。

以 分享概览 为例

Given:新增 帐号[email protected] 、新增 概览1

When:将 概览1 分享给 帐号[email protected]

Then:校验 帐号[email protected] 是否能查看到 概览1

2.1 Scenario 范例

Feature: 授权功能Scenario: 帐号 通过绑定 角色 进行授权        Given 新增角色'分析师',拥有权限'1001, 1002, 1003'And 新增帐号'[email protected]'When 帐号'[email protected]'绑定角色'分析师'Then 鉴权 帐号'[email protected]',有权限'1001'And  鉴权 帐号'[email protected]',有权限'1002'But  鉴权 帐号'[email protected]',没有权限'1005'复制代码

2.2 Background 范例

Feature: 授权功能Background:      Given 新增帐号'[email protected]'Scenario: 帐号 通过绑定 角色 进行授权        Given 新增角色'分析师',拥有权限'1001, 1002, 1003'When 帐号'[email protected]'绑定角色'分析师'Then 鉴权 帐号'[email protected]',有权限'1001'And  鉴权 帐号'[email protected]',有权限'1002'But  鉴权 帐号'[email protected]',没有权限'1005'Scenario: 帐号 通过绑定 机构 进行授权        Given 新增机构'北京部门',拥有权限'2001, 2002, 2003'When 帐号'[email protected]'绑定机构'北京部门'Then 鉴权 帐号'[email protected]',有权限'2001'And  鉴权 帐号'[email protected]',有权限'2002'But  鉴权 帐号'[email protected]',没有权限'2005'复制代码

2.3 Scenario Outline 范例

可以在多个 Step 上共用同一个 "简单" 参数,且每一个 Example 都视为一个 Scenario

Feature:授权功能Scenario Outline:帐号通过绑定角色进行授权Given新增角色,拥有权限When新增帐号Then帐号绑定角色And鉴权帐号,有权限Examples:|role|permissions|account|has_permission||分析师|1001,1002,1003|[email protected]|1001||开发者|2001,2002,2003|[email protected]|2001||管理员|3001,3002,3003|[email protected]|3001|复制代码

3. 基本概念

3.1 文件结构

Gherkin 写在 .feature 文件中

Step 对应的逻辑 写在 .java 文件中

3.2 Step 映射

通过 Gherkin 语法上的描述,找到与 注解 value 值匹配的 Java 方法,将 Gherkin 与 Java 代码关联起来。

3.3 Scenario 独立

当同时执行多个 Scenario 时,执行每个 Scenario 对应的 Java 文件都会被重新创建。

不同的 Scenario 之间,不应该存在数据依赖(MySQL),如果存在依赖,将会使 Scenario 变得脆弱可以在 Backgroud,进行数据清理,来保证测试结果的正确性

二、最佳实践

1. 撰写 Scenairo 原则 - BRIEF

school.cucumber.io/courses/tak…

B:Business Language。

Scenairo 中使用的词语应该使用【业务团队成员】能够理解的词语,否则将无法与业务团队成员互动。

R:Real Data。

Scenairo 中应该使用 具体、真实 的数据(不要用 1、2、3、A、B、C),有助于让场景变得生动,并及早揭示边界条件与基本假设。

I:Intention Revealing。

Scenairo 应该描述试图实现的意图,而不是描述程式将如何实现它的机制。

确保每一行 Step 描述的是 意图 而非 机制。 (比如:创建帐号,就不要写成 "将帐号数据写入 user 表,并在 account_project 表绑定帐号与项目的关联")

E:Essential。

Scenairo 应该只保留必要的 Step,不直接促成结果的场景都应该被删除。

任何不能增加读者对预期行为理解的场景,都不應該出现在文档中。

F:Focus。

多数的 Scenairo 应该只专注于单一职责。

BRIEF

建议将大多数的 Scenairo 限制在五行或更少,这将使它们更易于阅读与推理,并有助于避免 同时测试多个规则 或 增加额外细节。

2. 保证 Scenairo 可读性好处

school.cucumber.io/courses/tak…

随时获得 你做的事情是否正确 的反馈

你的 Feature 可以变成描述你 系统功能 的 线上文档

Scenairo 将会引导你的技术设计

3. 开发流程推荐

school.cucumber.io/courses/tak…

在 Cucumber 中描述你想要实现的 Scenairo,把所有的 Step 串连起来,并运行 Cucumber 使其出现 失败 结果

持续实现 Step 与 API 的具体逻辑,并观察 API 是如何 失败 的,最终使 Scenairo 的结果变为 成功。

当测试通过后,对 API 实现进行 清理 与 优化(重构),使其更具可读性,并再次运行 Cucumber 保证重构后的结果正确。

以上为 测试驱动开发(TDD) 的 生命周期: Red、Green、Clean

如果改坏了系统逻辑,你的测试用例会告诉你。

推荐:在研发进行技术设计前,再多加一个 测试用例评审 的环节,让 PM、QA、RD 一起参与,方便及早发现问题,也能增加技术设计时考量的全面性

三、Cucumber 常用功能

1. 参数化

参数化 可以与 表格化、列表化、对象化 混用

1.1 关键字

类型正则

biginteger"-?\d+" 或者 "\d+"

string"([^"\] (\.[^"\] )*)"

bigdecimal"-?\d*[.,]\d+"

byte"-?\d+" 或者 "\d+"

double"-?\d*[.,]\d+"

short"-?\d+" 或者 "\d+"

float"-?\d*[.,]\d+"

word"\w+"

int"-?\d+" 或者 "\d+"

long"-?\d+" 或者 "\d+"

1.2 说明

Cucumber 支持在 Java 注解 中使用 {关键字} 作为占位符。 在 Step 中直接写上参数,将在 Java 代码中,会把占位符对应的参数作为方法参数传递进去。

字串 类型的 关键字,需要加上 单引号 或 双引号 作为声明

注解中声明占位符的顺序 为 注入方法参数的顺序

1.3 范例

Feature: 授权功能Scenario: 鉴权场景          Given 创建帐号'[email protected]',角色'admin',项目id1复制代码

@Given("创建帐号 {string},角色 {string},项目id {int}")publicvoidtest(Stringusername,Stringrole, Integer projectId) {      log.info("start to execute test(), params:[ username = {}, role = {}, projectId = {} ]",              username, role, projectId);  }复制代码

2. 表格化(DataTable)

2.1 设置 Gherkin 数据

下方的 List - Map、List - List、Map - List 都是共用同一套 Gherkin 代码,也就是说,同一个 Gherkin 代码,Cucumber 可以根据不同的方法参数类型,自动进行转换

Feature: 授权功能Scenario: 鉴权场景        Given 创建帐号        | username          | password | role      | project_id |        | [email protected] | a123456  | admin    |1|        | [email protected] | b123456  | analyst  |2|        | [email protected] | c123456  | developer |3|复制代码

2.2 List - Map(常用)

@Given("创建帐号")publicvoid test(List> dataTable) {for(Mapdata: dataTable) {          String username =data.get("username");          String password =data.get("password");          String role =data.get("role");          String projectId =data.get("project_id");          log.info("execute test(), fields:[ username = {}, password = {}, role = {}, projectId = {} ]",                  username, password, role, projectId);      }    }复制代码

2.3 List - List

@Given("创建帐号")publicvoid test(List> dataTable) {for(Listdata: dataTable) {          log.info("execute test(), fields:[ data = {} ]",data);      }    }复制代码

2.4 Map - List

@Given("创建帐号")  publicvoidtest(Map> dataTable) {for(Map.Entry> data : dataTable.entrySet()) {Stringusername = data.getKey();          List infos = data.getValue();          log.info("execute test(), fields:[ username = {}, infos = {} ]", username, infos);      }    }复制代码

3. 列表化

3.1 直列表

Feature: 授权功能Scenario: 鉴权场景        Given 删除帐号        | [email protected] |        | [email protected] |        | [email protected] |复制代码

@Given("删除帐号")publicvoidtest(List usernames){log.info("execute test(), fields:[ usernames = {} ]", usernames);  }复制代码

3.2 横列表

Feature: 授权功能Scenario: 鉴权场景        Given 删除帐号        | [email protected] | [email protected] | [email protected] |复制代码

@Given("删除帐号")  public void test(@TransposeList usernames) {log.info("execute test(), fields:[ usernames = {} ]", usernames);  }复制代码

记得加上 @Transpose,告诉 Cucumber 需要进行数据转换

4. 对象化

4.1 撰写 Gherkin 语法

Feature: 授权功能Scenario: 鉴权场景        Given 新增 帐号        | username        | role      | project    |        | [email protected] | admin    | default    |        | [email protected] | analyst  | production |        | [email protected] | developer | default    |复制代码

4.2 定义 Java 对象

@DatastaticclassAccount {/** 

    * 帐号 

    */privateStringusername;/** 

    * 角色 

    */privateStringrole;/** 

    * 项目 

    */privateStringproject;    }复制代码

4.3 撰写 封装 Java 对象的 方法

/** 

* 帐号对象 的 封装方法 

*/@DataTableTypepublicAccount defineAccount(Map entry) {        Account account = new Account();// 如果有指定数据,则将数据写入  Optional.ofNullable(entry.get("username")).ifPresent(account::setUsername);      Optional.ofNullable(entry.get("role")).ifPresent(account::setRole);  Optional.ofNullable(entry.get("project")).ifPresent(account::setProject);returnaccount;  }复制代码

在封装方法上方,需要加上 @DataTableType 注解

4.4 关联具体 Step 方法

@Given("新增 帐号")publicvoidaddAccount(List accountList){for(Account account : accountList) {log.info("execute addAccount(), fields:[ account = {} ]", account);      }    }复制代码

方法参数中,直接指定 对象封装方法 返回的 对象类型,Cucumber 就能直接进行关联

5. 参数化、表格化、列表化 混合使用

DataTable 与 List 必须作为 Java 方法的最后一个参数

5.1 表格化&参数化

Feature: 授权功能Scenario: 鉴权场景        Given 创建帐号,项目'default'| username          | password | role      |        | [email protected] | a123456  | admin    |        | [email protected] | b123456  | analyst  |        | [email protected] | c123456  | developer |复制代码

@Given("创建帐号,项目 {string}")  publicvoidtest(Stringproject, List> dataTable) {        log.info("execute test(), fields:[ project = {} ]", project);for(Map data : dataTable) {Stringusername = data.get("username");Stringpassword = data.get("password");Stringrole = data.get("role");          log.info("execute test(), fields:[ username = {}, password = {}, role = {} ]",                  username, password, role);      }    }复制代码

List> 必须作为最后一个方法参数

5.2 直列表&参数化

Feature: 授权功能Scenario: 鉴权场景        Given 删除帐号,项目'default'| [email protected] |        | [email protected] |        | [email protected] |复制代码

@Given("删除帐号,项目 {string}")publicvoidtest(StringprojectName, List usernames) {      log.info("execute test(), fields:[ projectName = {}, usernames = {} ]", projectName, usernames);  }复制代码

List 必须作为最后一个方法参数

5.3 横列表&参数化

Feature: 授权功能Scenario: 鉴权场景        Given 删除帐号,项目'default'| [email protected] | [email protected] | [email protected] |复制代码

@Given("删除帐号,项目 {string}")  public void test(String project,@TransposeList usernames) {log.info("execute test(), fields:[ project = {}, usernames = {} ]", project, usernames);  }复制代码

5.4 对象化&参数化

Feature: 授权功能Scenario: 鉴权场景        Given 新增 帐号,机构'北京部门'| username        | role      | project    |        | [email protected] | admin    | default    |        | [email protected] | analyst  | production |        | [email protected] | developer | default    |复制代码

@Given("新增 帐号,机构 {string}")publicvoidaddAccount(String organization, List accountList){log.info("execute addAccount(), fields:[ organization = {} ]", organization);for(Account account : accountList) {log.info("execute addAccount(), fields:[ account = {} ]", account);      }    }复制代码

6. 钩子方法(Hook)

6.1 注解

注解执行时机

@BeforeAll在启动 Cucumber 时执行

@Before在所有 Scenario 执行之前执行

@BeforeStep在所有 Step 执行之前执行

@AfterAll在结束 Cucumber 时执行

@After在所有 Scenario 执行之后执行

@AfterStep在所有 Step 执行之后执行

指定注解的 value,可以指定 Hook 的运行范围,不指定则表示在全项目生效。 注解可以定义在项目中的任意位置。

如果同时定义了多个钩子方法,则会依照注解中 order 属性的顺序多次执行。

6.2 全局生效范例

Gherkin 代码

Feature:测试功能  Scenario:测试场景1  Given测试功能1  When测试功能2  Then测试功能3  Scenario:测试场景2  Given测试功能1  When测试功能2  Then测试功能3复制代码

Java 代码

@Slf4jpublicclassTestDefs{@Beforepublicvoidbefore(){          log.info("执行 @Before 方法");      }@BeforeSteppublicvoidbeforeStep(){          log.info("执行 @BeforeStep 方法");      }@Afterpublicvoidafter(){          log.info("执行 @After 方法");      }@AfterSteppublicvoidafterStep(){          log.info("执行 @AfterStep 方法");      }@Given("测试功能1")publicvoidtest1(){          log.info("执行 测试功能1");      }@Given("测试功能2")publicvoidtest2(){          log.info("执行 测试功能2");      }@Given("测试功能3")publicvoidtest3(){          log.info("执行 测试功能3");      }  }复制代码

最终结果

6.3 局部生效范例

Gherkin 代码在不同的 Scenario 上,可以增加 自定义注解 作为标记

Feature: 测试功能@MyHookScenario: 测试场景1Given 测试功能1When  测试功能2@YourHookScenario: 测试场景2Given 测试功能1When  测试功能2复制代码

Java 代码Hook 注解的 value 值,可以用来指定,只有当 Scenario 上有该自定义注解时,才会执行 Hook 方法。

如果想将 Hook 方法在多个自定义注解下进行复用,可以通过 , 隔开,例如:@Before("@MyHook,@YourHook")

@Slf4jpublicclassTestDefs{@Before("@MyHook")publicvoidmyHookBefore(){          log.info("执行 @MyHook 的 @Before 方法");      }@BeforeStep("@MyHook")publicvoidmyHookBeforeStep(){          log.info("执行 @MyHook 的 @BeforeStep 方法");      }@Before("@YourHook")publicvoidyourHookBefore(){          log.info("执行 @YourHook 的 @Before 方法");      }@BeforeStep("@YourHook")publicvoidyourHookBeforeStep(){          log.info("执行 @YourHook 的 @BeforeStep 方法");      }@Given("测试功能1")publicvoidtest1(){          log.info("执行 测试功能1");      }@When("测试功能2")publicvoidtest2(){          log.info("执行 测试功能2");      }    }复制代码

最终结果

四、Gherkin 模版范例

0. Gherkin 模版撰写规范

所有 模版功能 只运行在 测试项目(project_id = 1) (避免有时要指定 project_id 有时又不需要的麻烦)

使用 Gherkin 参数化 的 字串 功能时,使用 单引号 将字串包裹 (考虑到模版参数可能出现 Json 字串,使用双引号会需要额外进行转译)

模版参数中,不要出现 id(包含 帐号id、角色id、职务id ... 等),应该改用 名称(用户名、角色中文名称、职务名称)去反查 (使用 id 将无可避免的出现要使用 上下文 将多个 Step 进行关联的问题,这将使测试用例变得脆弱,所以宁可用 名称 去反查 id,也不要直接指定 id)

Gherkin 语法表格化的表头字段,使用 下滑线命名法 来命名

模版方法名称定义遵守以下规范 (当一个模版可能同时存在于 Given 与 When 时,以 When 为主)

模版关键字说明范例

Given以 [模块][新增or更新or删除] + 空格 开头[帐号][新增] 新增帐号、[角色][更新] 更新角色

When以 [模块][功能] + 空格 开头[权限][功能] 授权数据资源权限、[权限][功能] 删除数据资源权限数据

Then以 [模块][校验] + 空格 开头[权限][校验] 校验帐号权限

随时注意编写的模版是否尽量符合第 2 大项的最佳实践

1. 帐号

通常为 Given 的最后一步,比如 帐号 要绑定 角色,一定是先创建好 角色,再通过 新增帐号 进行绑定

1.1 新增 帐号

(创建前自动删除原来的帐号、创建时允许指定角色)

属性说明必填默认值

username用户名是无

password密码否AAAaaa111@

user_cname帐号中文名称否与 username 相同

email信箱否与 username 相同

role_cname角色中文名称,默认角色用 role_name 即可否admin

# 最简单的范例Given [帐号][新增] 新增帐号    | username          |    | [email protected] |    | [email protected] |    | [email protected] |# 最完整的范例Given [帐号][新增] 新增帐号    | username          | password  | user_cname        | email            | role_cname |    | [email protected] | AAAaaa111@ | [email protected] | [email protected] | admin      |    | [email protected] | AAAaaa111@ | [email protected] | [email protected] | analyst    |    | [email protected] | AAAaaa111@ | [email protected] | [email protected] | developer  |复制代码

1.2 更新 帐号 的 角色

Given [帐号][更新] 更新帐号 '[email protected]' 的角色    | 角色A | 角色B | 角色C |复制代码

1.3 更新 帐号 的 职务

Given [帐号][更新] 更新帐号 '[email protected]' 的职务    | 职务A | 职务B | 职务C |复制代码

1.4 删除 帐号

Given [帐号][删除] 删除帐号    | [email protected] |    | [email protected] |    | [email protected] |    | [email protected] |复制代码

你可能感兴趣的:(Cucumber 黄瓜测试 BDD 从入门到精通)