每个人都听说过测试驱动开发(TDD),以及它将对整个产品和开发周期带来的好处。这些都是显而易见的。每次你为一段代码编写测试,你将知道代码是否正常运行。而且,更重要的是,以后你将第一时间知道代码是否发生中断。
行为驱动开发(BDD)是对此理念的扩展,但不同的是它并不是测试代码,而是测试产品,特别是产品是否按照你的期望行为去运行。(场景实例化)
在本文中,我将向你介绍如何搭建并运行Cucumber,该框架被用来运行以BDD风格编写的自动化验收测试。这种测试的优势在于,它们可以用简单的英文/中文(支持多国语言)来编写,因此参与项目中的非技术人员也可以理解它。在阅读本文完毕后,你可以决定Cucumber是否适用与你以及你的团队,并开始编写你自己的验收测试。
【区别主要是在测试的结构和写法上】
在TDD的设定里,测试由编写被测代码的开发人员编写、维护和理解。一般来说不需要其他人去理解测试,这很棒。
在BDD的设定里,测试需要被除了编写功能的开发人员之外的更多人理解。 有更多的项目参与者对产品的功能感兴趣。
这些人员可能包括质量保证人员、产品分析师、销售人员,甚至是高级管理人员。
这是两者之间的区别:
const assert = require('assert');
const webdriver = require('selenium-webdriver');
const browser = new webdriver.Builder()
.usingServer()
.withCapabilities({'browserName': 'chrome' })
.build();
browser.get('http://en.wikipedia.org/wiki/Wiki');
browser.findElements(webdriver.By.css('[href^="/wiki/"]'))
.then(function(links){
assert.equal(19, links.length); // Made up number
browser.quit();
});
Given I have opened a Web Browser
When I load the Wikipedia article on "Wiki"
Then I have "19" Wiki Links
这两种测试做了同样的事,但是一种是通俗易懂的,另一种只有懂得JavaScript和Selenium的人才能阅读。
这篇文章将向你展现如何使用Cucumber.js框架,在你的JavaScript项目中实现BDD测试,从而使你的产品从这种级别的测试中受益。
Cucumber是一个为行为驱动开发设计的测试框架。它允许你以Gherkin的形式定义你的测试,然后将这些可执行的Gherkin脚本与代码绑定。
Gherkin是一种用来编写Cucumber测试的特定领域语言。它允许测试脚本以人类可读的格式编写,并在参与产品开发的所有人员之间共享。
Gherkin文件包含用Gherkin语言编写的测试。这些文件通常具有.feature的文件扩展名。Gherkin文件的内容通常被简称为“gherkins”。
Gherkins
在一个Gherkin定义的测试中,存在特征和场景的概念。这类似于其他测试框架中的测试套件和测试用例,被用来以一种简洁的方式去构建测试。
一个场景实际上只是一个单一的测试。它应该在你的应用程序中只测试一件事。
一个功能是一组相关的场景。因此,它会在你的应用程序中测试许多相关的东西。理想情况下,Gherkin文件中的功能将紧密映射到应用程序中的功能 — Gherkin由此得名。
每个Gherkin文件只包含一个功能,每个功能都包含一个或多个场景。
然后场景由步骤组成,这些步骤以特定的方式排序:
Given – 这些步骤用于在测试之前设置初始状态
When – 这些步骤是要实际执行的测试
Then – 这些步骤被用来断言测试的结果
上面其实就叫Feature(剧本)文件,其遵循的Gherkin(剧本语法)标准。
这种剧本文件,客户、项目经理、业务分析师,QA都能看懂,因为其就是一个故事点或者需求点。而且通过特定的工具,比如Cucumber,
或CukeTest等工具,能够把其自动转换成为代码。开发人员根据自动生成的代码,断言一些预期的行为,并根据这些行为,完成相应的代码实现。
这样的自动化脚本,为一个项目中的各个人员了解项目的需求,实现提供了一个很好的交互桥梁。
下面是其一个交互的过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ouYRgxRn-1618134780207)(http://thyrsi.com/t6/645/1546154825x2728278644.jpg)]
如果是传统的方式,其交互方式,应该是:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YImO3k1M-1618134780210)(http://thyrsi.com/t6/645/1546154898x2728278644.jpg)]
理想情况下,每个场景应该是单个测试用例,因此When步骤的数量应该保持很少。
步骤都是可选的。例如,如果你完全不需要在测试开始前设置任何内容,你可以不设置Given步骤。
Gherkin文件是为人类可读设计的,并为参与产品开发的任何人带来益处。由于参与人员包括非技术人员,因此Gherkin文件应始终使用商业语言而不是技术语言编写。这意味着,例如,编写测试时,你不需要去引用某个UI组件,而是要去描述你想要测试的产品的功能。
行为驱动开发是一个软件工程的系列实践,能够帮助团队快速构建和交付更多价值和质量的软件产品。其和敏捷已经精益的开发实践,特别是测试驱动开发(TDD)和领域驱动开发(DDD),是一脉相承的。但是最重要的是BDD提供了一种通用的,简单的,结构化的描述语言,这种语言既可以是英语也可以是其他本地的语言,通过他能够很方便让项目成员和业务干系人非常顺畅的沟通需求,即使这些成员不懂的任何编程语言。
通过对比,可以发现BDD的这种方式,把用户或者客户真正的通过Feature文件联系在一起了,其沟通是顺畅的,各个角色,包括QA,BA,开发,测试,客户,用户可以通过这一媒介,进行高效无障碍的沟通,而不是像传统的方式,通过BA进行二次转达,从而丢失了很多重要的需求。
由此可见,其BDD的好处如下:
减少浪费!
节省成本!
容易并且安全的适应变化!
因为少了中间的转达环节,从而能够快速交付产品!
我们来看下面这个流程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1aXYMafK-1618134780212)(http://thyrsi.com/t6/645/1546155203x2728278644.jpg)]
从上图可以看出,当一个需求过来的时候,先通过项目干系人都能理解的Feature文件,描述项目的User Story,有的里面还有详细生动的数据示例(example),从而能够让所有的人更加容易理解其需求,比如:
Scenario Outline: Earning interest
Given I have an account of type with a balance of
When the monthly interest is calculated
Then I should have earned at an annual interest rate of
And I should have a new balance of
Example:
| initial-balance | account-type | interest-rate | new-balance |
| 10000 | current | 1 | 10008.33 |
| 10000 | savings | 3 | 10025 |
| 10000 | supersaver | 5 | 10041.67 |
通过上面的示例表(example)的表格可以更加容易的理解当前用例的意图了。
当Feature和Example文件都完成后,借助于第三方的开源框架实现,比如Cucumber,CukeTest、jBehave,SpecFlow等把Feature和Example转换成代码,
然后通过底层次的单元测试框架,比如JUnit,mocha,NUnit,Spock,RSpec,结合测试驱动开发,从而把业务代码的逻辑实现一举多得。
以下是一个使用Google搜索Cucumber.js的简单的Gherkin脚本的示例:
Given I have loaded Google
When I search for "cucumber.js"
Then the first result is "GitHub - cucumber/cucumber-js: Cucumber for JavaScript"
我们可以直截了当的看到,这个测试告诉我们该做什么,而不是如何去做。它是用面对所有读者的语言编写的,而且 — 重要的是 — 无论产品最终 如何调整,这些测试很可能依然是正确的。谷歌可能会彻底改变他们的页面UI,但只要功能不变,那么Gherkin脚本依然是正确的。
你可以在Cucumber wiki上阅读更多关于Given When Then的内容。
在上面的示例中,对应将执行js文件中以下步骤:
Given('I have loaded Google', function() {});
When('I search for {stringInDoubleQuotes}', function() {});
Then('the first result is {stringInDoubleQuotes}', function() {});
不用担心这些代码的含义 – 稍后会详细解释。
本质上,它定义了一些Cucumber.js将代码绑定到Gherkin文件中的步骤的方法。
当你使用Gherkin来编写测试用例后,你需要一些方法来执行它们。在JavaScript世界中,有一个叫做Cucumber.js的模块,可以让你做到这一点。它的工作原理是允许你定义可关联到你的Gherkin文件中定义的各个步骤的JavaScript代码。然后通过加载Gherkin文件,以正确的顺序执行与每个步骤相关的JavaScript代码来运行测试。
首先,你想要使用cucumber.js模块,就需要安装nodejs并配置相关环境变量(因为这些模块都是基于nodejs
进行封装打包的模块。)
nodejs下载
nodejs环境变量配置
其次,将Cucumber.js添加到你的版本中,在版本中加入Cucumber.js,只需要添加cucumber模块,然后配置并运行。
npm install cucumber //用于项目中的本地安装。
npm install -g cucumber //用于全局安装(服务器部署强烈建议使用全局安装cucumber模块)。
npm install --save-dev cucumber 用于//安装Cucumber.js作为开发依赖项
Cucumber.js包含一个可执行文件来运行这些功能。在项目中安装Cucumber之后,您可以使用以下命令运行它:
$ ./node_modules/.bin/cucumber-js
关于全局安装的注意事项:全局安装时 Cucumber不起作用,因为支持文件中需要黄瓜,并且不需要全局安装的模块。
手动执行Cucumber是相对容易的,最好是先确保你能做到这一点,因为以下解决方案都是自动执行相同的方法。
安装后,可执行文件将为./node_modules/.bin/cucumber-js。当你运行它时,它需要知道文件系统上的哪个地方可以找到它所需的所有文件。这些文件都是Gherkin文件和要执行的JavaScript代码。
按照惯例,所有的Gherkin文件都将保存在features目录中,如果不指定,那么Cucumber将在同一个目录中查找要执行的JavaScript代码。指定Cucumber在哪里查找这些文件是一个明智的做法,因为这样可以更好地控制构建过程,比如你指向运行某个文件夹下的测试用例。
例如,如果将所有的Gherkin文件保存在myFeatures目录中,并将所有JavaScript代码保存在mySteps中,则可以执行以下操作:
$ ./node_modules/.bin/cucumber-js ./myFeatures -r ./mySteps
-r标志是一个包含JavaScript文件的目录,以便为自动测试所需。还有其他一些标志可能会引起关注 — 请阅读帮助文本以了解它们的工作原理:
或者你也可以进入项目中,直接执行
$ ./node_modules/.bin/cucumber-js
当然,不指定目录路径,会在当前项目中全局扫描所有feature文件并进行执行。(这些目录以递归方式进行扫描,因此你可以根据特定情况对文件进行浅层或深层嵌套)
1、需要支持文件
--require 在执行功能之前需要支持文件。
使用glob模式。如果未使用,则需要以下文件:
如果功能存在于features目录中(在任何级别)
features/**/*.js
除此以外
/**/*.js 对于包含所选功能的每个目录
指定此选项时将禁用自动加载,并且所有加载都将变为显式。
2、格式
...
其他参数,请参考这里
一旦手动运行Cucumber,你也可以将它作为npm脚本添加到构建中,你只需要添加下面的命令 — 路径并不是限定的,因为npm会为你处理 — 在你的package.json中,如下所示:
【执行所有feature】
"scripts": {
"start": "./node_modules/.bin/cucumber-js"
}
这些做完了,你就可以执行:
$ npm run start
或
$ npm start
它就会像之前一样执行你的Cucumber测试。
【执行指定文件夹feature】
$ cucumber-js features/**/*.feature //指定glob模式
$ cucumber-js features/dir //指定功能目录
$ cucumber-js features/my_feature.feature //指定要素文件
$ cucumber-js features/my_feature.feature:3 //按行号指定方案
$ cucumber-js --name "topic 1" //通过名称与正则表达式匹配来指定方案,如果多次使用,则方案名称只需匹配提供的名称之一
同样可以放到package.json中
"scripts": {
"cucumber": "./node_modules/.bin/cucumber-js ./myFeatures -r ./mySteps"
}
这些做完了,你就可以执行:
$ npm run cucumber
或
$ npm start
它就会像之前一样执行你的Cucumber测试。
特别说明:cucumber 5.0.3之后运行方式cucumber.js变成了cucumber-js!
如果你使用的版本比较低,那么你的执行命令应该是"./node_modules/.bin/cucumber.js"
在IDEA编辑器中搜索 cucumber.js插件并安装,你也可以从这边下载cucumber.js插件
添加一个cucumber.js运行配置,并设置相关参数,IDEA官方参数配置介绍
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R6QYSLEa-1618134780214)(http://thyrsi.com/t6/647/1546353493x2890211744.jpg)]
你可能会遇到一个版本问题:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nPFThpNh-1618134780215)(http://thyrsi.com/t6/647/1546353701x2890211744.jpg)]
的确存在用于执行Cucumber.js测试的Grunt插件。然而不幸的是,【它已经过时了】,并且不适用于最新版本的Cucumber.js,这意味着如果你使用它,你将错过许多新的改进。
相反,我的首选方法是使用grunt-shell插件以与上述完全相同的方式执行命令。
安装完成后,配置,这只是将以下插件配置添加到您的Gruntfile.js中的一种情况:
shell: {
cucumber: {
command: 'cucumber-js ./myFeatures -r ./mySteps'
}
}
现在,和之前一样,你可以通过运行grunt shell:cucumber来执行你的测试。
Gulp与Grunt的情况完全相同,现有的插件已经过时并且会使用旧版本的Cucumber工具。同样的,在Gulp中你可以使用gulp-shell模块来执行Cucumber.js命令,就像其他场景一样。
设置一下:
gulp.task(‘cucumber’, shell.task([
‘cucumber-js ./myFeatures -r ./mySteps’
]));
现在,和之前一样,你可以通过运行gulp cucumber来执行你的测试。
现在我们知道如何执行Cucumber了,那就让我们编写一个测试。在这个例子中,我们将使用一些精心设计的例子向你展现系统是如何运作的。上,你可以做到更多,比如直接调用你正在测试的代码,对正在运行的服务进行HTTP API调用,或者使用Selenium驱动浏览器来测试你的应用程序。
我们将用一个简单的例子将证明算法有效。一共有两个功能 — 加法和乘法。
假设你已安装配置好nodejs.
首先,先配置好。
$ npm init
$ npm install --save-dev cucumber
$ mkdir features steps
如何执行测试完全取决于你自己。在这个例子中,我只是为了简单起见而手动完成它。在实际的项目中,你可以使用上述选项之一将其集成到版本中。
$ ./node_modules/.bin/cucumber-js features/ -r steps/
0 scenarios
0 steps
0m00.000s
$
现在,让我们来编写我们的第一个feature。首先进入features / addition.feature:
Feature: Addition
Scenario: 1 + 0
Given I start with 1
When I add 0
Then I end up with 1
Scenario: 1 + 1
Given I start with 1
When I add 1
Then I end up with 2
很简单,也很容易阅读。 告诉我们我们正在做什么,而不是如何做。 我们来试试吧:
$ ./node_modules/.bin/cucumber-js features/ -r steps/
Feature: Addition
Scenario: 1 + 0
? Given I start with 1
? When I add 0
? Then I end up with 1
Scenario: 1 + 1
? Given I start with 1
? When I add 1
? Then I end up with 2
Warnings:
1) Scenario: 1 + 0 - features/addition.feature:3
Step: Given I start with 1 - features/addition.feature:4
Message:
Undefined. Implement with the following snippet:
Given('I start with {int}', function (int, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
2) Scenario: 1 + 0 - features/addition.feature:3
Step: When I add 0 - features/addition.feature:5
Message:
Undefined. Implement with the following snippet:
When('I add {int}', function (int, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
3) Scenario: 1 + 0 - features/addition.feature:3
Step: Then I end up with 1 - features/addition.feature:6
Message:
Undefined. Implement with the following snippet:
Then('I end up with {int}', function (int, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
4) Scenario: 1 + 1 - features/addition.feature:8
Step: Given I start with 1 - features/addition.feature:9
Message:
Undefined. Implement with the following snippet:
Given('I start with {int}', function (int, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
5) Scenario: 1 + 1 - features/addition.feature:8
Step: When I add 1 - features/addition.feature:10
Message:
Undefined. Implement with the following snippet:
When('I add {int}', function (int, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
6) Scenario: 1 + 1 - features/addition.feature:8
Step: Then I end up with 2 - features/addition.feature:11
Message:
Undefined. Implement with the following snippet:
Then('I end up with {int}', function (int, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
2 scenarios (2 undefined)
6 steps (6 undefined)
0m00.000s
$
厉害了!我们刚刚写下了我们的Gherkin,并且一切都在执行。不过它现在还不能起作用,因为我们不知道如何处理这些步骤,但Cucumber非常清楚地告诉了我们。
现在让我们写第一步的文件吧。这将简单地按照Cucumber输出告诉我们的方式来执行这些步骤,这些步骤没有起到实际的作用,但是整理了输出。
此处说明:cucumber.js版本5.0.3 官方不再推荐像下面这么定义Given、When、Then、And在defineSupportCode回调函数中中,控制台会提示"使用defineSupportCode是不赞成,请要求/导入单独的方法"。
liyinchi$ ./node_modules/.bin/cucumber-js
(node:66659) DeprecationWarning: cucumber: defineSupportCode is deprecated. Please require/import the individual methods instead.
..................................
8 scenarios (8 passed)
18 steps (18 passed)
0m00.016s
文件为 steps/maths.js:
const defineSupportCode = require(‘cucumber’).defineSupportCode;
defineSupportCode(function({ Given, Then, When }) {
Given('I start with {int}', function (int, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
When('I add {int}', function (int, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
Then('I end up with {int}', function (int, callback) {
// Write code here that turns the phrase above into concrete actions
callback(null, 'pending');
});
});
defineSupportCode hook是Cucumber.js的一种允许你提供用于各种不同场景的代码的方法。这些步骤都会被覆盖到,每次你想编写Cucumber可以直接调用的代码时,代码必须在某一步骤的代码块中。
注意,示例代码定义了三个不同的步骤 - 分别为Given,When和Then。每一个步骤的代码块都会被赋予一个字符串 - 或者是正则表达式 - 与feature文件中的某个步骤相匹配,并且与该步骤匹配时会执行该函数。占位符可以放入步骤字符串中 - 或者,如果使用正则表达式,则可以使用捕获表达式 - 而这些占位符将被提取出来并作为参数提供给您的函数。
执行此操作提供了更简洁的输出,而实际上并没有做任何事情:
$ ./node_modules/.bin/cucumber-js features/ -r steps/
Feature: Addition
Scenario: 1 + 0
? Given I start with 1
- When I add 0
- Then I end up with 1
Scenario: 1 + 1
? Given I start with 1
- When I add 1
- Then I end up with 2
Warnings:
1) Scenario: 1 + 0 - features/addition.feature:3
Step: Given I start with 1 - features/addition.feature:4
Step Definition: steps/maths.js:4
Message:
Pending
2) Scenario: 1 + 1 - features/addition.feature:8
Step: Given I start with 1 - features/addition.feature:9
Step Definition: steps/maths.js:4
Message:
Pending
2 scenarios (2 pending)
6 steps (2 pending, 4 skipped)
0m00.002s
现在需要让脚本运行起来。我们所需要做的就是在我们的定义的步骤中实现代码。同时也需要整理一下,以便阅读。这本质上是消除了回调参数,因为我们没有做任何异步的操作。
在以上操作完成之后,“steps/maths.js”将如下所示:
const defineSupportCode = require('cucumber').defineSupportCode;
const assert = require('assert');
defineSupportCode(function({ Given, Then, When }) {
let answer = 0;
Given('I start with {int}', function (input) {
answer = input;
});
When('I add {int}', function (input) {
answer = answer + input;
});
Then('I end up with {int}', function (input) {
assert.equal(answer, input);
});
});
执行如下:
$ ./node_modules/.bin/cucumber-js features/ -r steps/
Feature: Addition
Scenario: 1 + 0
✔ Given I start with 1
✔ When I add 0
✔ Then I end up with 1
Scenario: 1 + 1
✔ Given I start with 1
✔ When I add 1
✔ Then I end up with 2
2 scenarios (2 passed)
6 steps (6 passed)
0m00.001s
全部通过。我们现在知道加法正确运行了。
请注意,我们只需编写一小段代码,而Cucumber将它们粘合在一起。
我们可以通过指定步骤代码如何从Gherkin文件执行来得到自动参数化测试。这意味可以非常简单的添加更多的场景。
接下来,让我们证明乘法正确运作。为此,我们需要在features/multiplication.feature中写下如下的Gherkin:
Feature: Multiplication
Scenario: 1 * 0
Given I start with 1
When I multiply by 0
Then I end up with 0
Scenario: 1 * 1
Given I start with 1
When I multiply by 1
Then I end up with 1
Scenario: 2 + 2
Given I start with 2
When I multiply by 2
Then I end up with 4
然后让我们在steps/maths.js中实现新的步骤。为此,我们只需在defineSupportCode方法内添加以下代码块:
When('I multiply by {int}', function (input) {
answer = answer * input;
});
就是这样。运行此操作将给出以下结果:
$ ./node_modules/.bin/cucumber-js features/ -r steps/
Feature: Addition
Scenario: 1 + 0
✔ Given I start with 1
✔ When I add 0
✔ Then I end up with 1
Scenario: 1 + 1
✔ Given I start with 1
✔ When I add 1
✔ Then I end up with 2
Feature: Multiplication
Scenario: 1 * 0
✔ Given I start with 1
✔ When I multiply by 0
✔ Then I end up with 0
Scenario: 1 * 1
✔ Given I start with 1
✔ When I multiply by 1
✔ Then I end up with 1
Scenario: 2 + 2
✔ Given I start with 2
✔ When I multiply by 2
✔ Then I end up with 4
5 scenarios (5 passed)
15 steps (15 passed)
0m00.003s
$
就这么简单,现在我们有了一个用来证明运算的易于扩展的测试套件。作为练习,请尝试扩展它以支持减法。如果遇到困难,你可以在评论中寻求帮助。
官方示例
这些功能都很不错,但是Cucumber的一些进阶功能能让我们的工作更加轻松。
【异步步骤的定义】
到目前为止,我们只写了同步步骤的定义,在JavaScript世界中,这不够!JavaScript中有很多情况下需要是异步的,所以我们需要一些方法来处理它。
值得庆幸的是,Cucumber.js有一些内置的处理方法,具体选择哪一种取决于你的喜好。
上述的方式是JavaScript中处理异步步骤的传统方法,即使用回调函数。如果定义步骤时将回调函数作为其最后一个参数,那么在触发此回调之前该步骤不会被视为完成。在这种情况下,如果回调被任何参数触发,那么这被认为是错误的,并且该步骤将失败。如果它没有任何参数触发,那么该步骤被认为是成功的。但是,如果回调没有被触发,那么框架最终会超时并且失败。本段文字的中心就是,如果你接收了一个回调参数,那么确保你调用它。
例如,使用回调进行HTTP API调用的步骤的定义如下所示。
这是使用Request编写的,因为它使用回调函数来响应。
When('I make an API call using callbacks', function(callbacks) {
request('http://localhost:3000/api/endpoint', (err, response, body) => {
if (err) {
callback(err);
} else {
doSomethingWithResponse(body);
callback();
}
});
});
首选方法和替代方法是按返回类型。如果你从你的步骤中返回一个Promise对象,那么只有当Promise状态为settled才认为该步骤已经完成。
如果Promise状态为rejected,那么步骤将失败,如果Promise状态为fulfilled,则步骤将会成功。
或者,如果您返回的不是Promise,那么该步骤将立即被视为已成功。
其中包括返回undefined或null。这意味着您可以在执行该步骤时选择是否需要返回Promise,框架将根据需要进行调整。
例如,使用Promise进行HTTP API调用的步骤定义如下所示。这是使用Fetch API编写的,因为它返回Promise响应。
When('I make an API call using promises', function() {
return fetch('http://localhost:3000/api/endpoint')
.then(res => res.json())
.then(body => doSomethingWithResponse(body));
});
一个Feature Background是一个Gherkin片段,它被预先添加到文件中每个Scenario的开头。这使得我们在每个Scenario之间可以共享常用的步骤,而不需要重复实现它们。
Background是使用Background关键字而不是Scenario关键字编写的。理想情况下,这其中应该只包含Given步骤,因为包含每个测试之间共享的When步骤或Then步骤是没有意义的。但是框架不会在这上面限制你,所以要悉心构造你的测试结构。
使用它,我们可以重新编写我们的添加的feature,如下所示:
Feature: Addition
Background:
Given I start with 1
Scenario: 1 + 0
When I add 0
Then I end up with 1
Scenario: 1 + 1
When I add 1
Then I end up with 2
这实际上与之前的脚本完全一样,但是由于我们已经将通用的步骤提取出来了,所以简短一些。
Scenario Outlines是从测试数据表中生成scenario的一种方式。这使得我们能以比以前更高效的方式进行参数化测试,可以使用插入不同值的方式重复同一测试脚本。
Scenario outlines通过使用Scenario Outline关键字而不是Scenario关键字编写,之后提供一个或多个Examples表。然后将来自Examples表格的参数替换到Scenario Outline中以生成运行的scenarios。
使用它,我们可以重构我们的乘法feature,如下所示:
Feature: Multiplication
Scenario Outline: *
Given I start with
When I multiply by
Then I end up with
Examples:
| a | b | answer |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
| 2 | 2 | 4 |
再一次,这和以前完全一样,但重复次数要少得多。你会看到你是否运行它,它会在输出中生成与之前完全相同的场景:
Feature: Multiplication
Scenario: 1 * 0
✔ Given I start with 1
✔ When I multiply by 0
✔ Then I end up with 0
Scenario: 1 * 1
✔ Given I start with 1
✔ When I multiply by 1
✔ Then I end up with 1
Scenario: 2 * 2
✔ Given I start with 2
✔ When I multiply by 2
✔ Then I end up with 4
我们刚刚在scenario outline中看到了一张表格,用于生成我们可以从中生成scenario的数据。
但是,我们也可以在scenario中使用数据表。这些可以用作提供数据表或结构化输入或许多其他事物的方式。
例如,可以将添加方案的方式重写为添加任意自变量的值,如下所示:
Scenario: Add numbers
Given I start with 0
When I add the following numbers:
| 1 |
| 2 |
| 3 |
| 4 |
Then I end up with 10
针对这个简单的例子,步骤将如下:
When('I add the following numbers:', function (table) {
answer = table.raw()
.map(row => row[0])
.map(v => parseInt(v))
.reduce((current, next) => current + next, answer);
});
我们提供的table参数是一个DataTable对象,它有一个可以调用的原始方法。此方法返回数据表中所有值的二维数组,因此外部数组中的每个表项都是表中的一行,而内部数组中的每个表项都是该行的单元格 - 一个字符串。
一个更复杂的例子是使用数据表来填充表单。然后使用该表来提供所有的输入,而不是使用难以阅读的步骤定义。这可以读取如下内容:
Scenario: Create a new user
When I create a new user with details:
| Username | graham |
| Email | [email protected] |
| Password | mySecretPassword |
Then the user is created successfully
在这种情况下,通过使用rowsHash方法,数据表类可以使我们更容易访问表。
步骤可能为如下所示:
When('I create a new user with details:', function (table) {
const data = table.rowsHash();
createUser(data);
});
在这种情况下,data对象将从数据表中解析出来,如下所示:
{
"Username": "graham",
"Email": "[email protected]",
"Password": "mySecretPassword"
}
通过第一列中的键可以轻松访问这些字段。
像大多数测试框架一样,Cucumber.js支持在场景运行之前和之后执行hook。
它们的设置方式与步骤定义相同,只需要简单按名字的调用hook - 在场景运行之前或之后,无论运行成功或失败与否。
一个简单的例子,为了能使我们的feature们更加可靠,编写代码如下:
var { Before,After,Given, When, Then,And} = require('cucumber');
Before(function() {
let answer = 0;
});
After(function() {
});
如上所述,优化后的数学步骤文件可以确保在每个场景运行之前将answer变量重置为0,这意味着如果我们从0开始则不需要Given步骤。
我们来看下浏览器自动化测试脚本的简单例子:
通过参数化url,
【feature文件】
Feature: Multiplication
Scenario Outline: open web
Given 打开浏览器访问 ""
Examples:
| url |
| http://www.baidu.com |
| http://www.qq.com |
| http://www.360.com |
【step_definitions文件夹下xxx.js】
var { setDefaultTimeout,Before,After,Given, When, Then,And} = require('cucumber');//引入cucumber
var webdriverio = require('webdriverio'); //引入webdriver库
var expect = require('chai'); //引入断言库
var assert = require('assert'); //引入断言库
var should = require('should');//引入断言库
var client;
var options = {
desiredCapabilities: {
//chrome、safari、firefox
browserName: 'safari' ,
chromeOptions: {
args: [
'headless',
// Use --disable-gpu to avoid an error from a missing Mesa
// library, as per
// https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md
'disable-gpu',
],
},
}
};
setDefaultTimeout(5000);//设置异步步骤的默认超时。默认为5000毫秒。
Before(function() {
client=webdriverio
.remote(options)
.init();//初始化浏览器对象
});
After(function() {
client.end();//退出浏览器
});
Given('打开浏览器访问 {string}', function(url) {
client.url(url);//打开网页
});
});
运行
$ ./node_modules/.bin/cucumber-js
运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aROCPUaz-1618134780217)(http://thyrsi.com/t6/644/1546050013x2728329023.jpg “运行结果”)]
通过接受回调函数作为第二个参数或返回Promise,你也可以在步骤定义完全相同的方式实现异步钩子。
如果你觉得Before和After简单操作对您来说还不够,那么官方还为我们提供了这些方法:
BeforeFeatures – 在运行任何一个feature文件之前调用一次,并提供了特性列表。
BeforeFeature – 在运行每个feature文件之前调用,提供该feature。
BeforeScenario – 在运行每个场景之前调用,提供了该场景。这大致类似于“之前”钩子。
BeforeStep – 在运行每个步骤之前调用,随步骤一起提供。
StepResult – 在运行每个步骤之后调用,并提供该步骤的结果。
AfterStep – 在运行每个步骤之后调用,该步骤提供。
ScenarioResult – 在每个场景运行之后调用,并提供场景的结果。
AfterScenario – 在每个场景运行后调用,提供了该场景。这大致类似于“后”钩子。
AfterFeature – 在运行每个feature之后调用,并提供该特性。
FeaturesResult – 在运行所有feature内容之后调用一次,并提供运行所有内容的结果。
AfterFeatures – 在运行所有feature内容之后调用一次,并提供了feature列表。
这些hook将与测试框架的整个生命周期进行交互,并将按上面列出的顺序进行调用。
//全局超时设置
var {setDefaultTimeout} = require('cucumber');
setDefaultTimeout(60 * 1000);
//局部超时设置
var {Before, Given} = require('cucumber');
Before({timeout: 60 * 1000}, function() {
// Does some slow browser/filesystem/network actions
});
Given(/^a slow step$/, {timeout: 60 * 1000}, function() {
// Does some slow browser/filesystem/network actions
});
除非绝对必要,否则不要使用!
通过将超时设置为-1禁用超时。如果使用这个,则需要实现自己的超时保护。否则,测试套件可能会过早结束或无限期挂起!
var {Before, Given} = require('cucumber');
var Promise = require('bluebird');
Given('the operation completes within {n} minutes', {timeout: -1}, function(minutes) {
const milliseconds = (minutes + 1) * 60 * 1000
const message = `operation did not complete within ${minutes} minutes`
return Promise(this.verifyOperationComplete()).timeout(milliseconds, message);
});
以下是旧文档信息请参考:
CLI
Custom Formatters
Custom snippet syntaxes
NodeJs Example
World
Hook
Timeouts
Data table
Attrachments
Gherkin API参考
FAQ
重要提醒!!defineSupportCode已被弃用详见
使用defineSupportCode方法中的registerHandler方法处理这些hook。代码如下:
defineSupportCode(function({ registerHandler }) {
registerHandler('BeforeStep', function(step) {
console.log('About to execute step:' + util.inspect(step));
});
registerHandler('ScenarioResult', function(scenario) {
console.log('Result of Scenario:' + util.inspect(scenario));
});
});
通过接受回调函数作为第二个参数,或者通过返回一个Promise对象,事件处理程序可以与定义步骤完全相同的方式进行异步处理。
(推荐)直接引入相关Hook,然后函数直接写到js文件中。代码如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jvbHGbMo-1618134780218)(http://thyrsi.com/t6/644/1546066757x2890149637.jpg “”)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JWvY14ZX-1618134780218)(http://thyrsi.com/t6/644/1546049359x2890174076.jpg “正确姿势”)]
到目前为止,我们都无法在步骤之间共享代码。我们可以很容易地拥有很多包含步骤定义,hook,event等的JavaScript文件,但它们彼此独立。
碰巧,事实并非如此。Cucumber.js有一个“World”的概念,它是所有运行场景的域。所有的步骤定义、挂钩和事件处理程序都可以通过访问this参数来访问它,无论定义步骤定义的文件如何。这就是为什么所有示例都是使用传统的function关键字编写的,而不是箭头函数。JavaScript中的箭头函数为您重新绑定此变量,这意味着您将无法访问测试中可能需要的World域。
这种方式使得你不通过额外的处理,就可以直接使用它。这也意味着我们可以通过在多个文件之间逻辑分割Cucumber代码并使它们按预期工作,同时仍然可以访问共享域,从而使我们的代码更加整洁。
首先cucumber支持许多国家的语言,查看语言列表
$ ./node_modules/.bin/cucumber-js --i18n-languages
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cYpMyL3q-1618134780219)(http://thyrsi.com/t6/644/1546068162x2890174094.jpg “cucumber支持的语言”)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OsZgov6P-1618134780220)(http://thyrsi.com/t6/644/1546068340x2890174094.jpg “中文”)]
来看一个简单中文例子
gherkin
# language: zh-CN
功能: 失败的登录
场景大纲: 失败的登录
假设 当我在网站的首页
当 输入用户名 <用户名>
当 输入密码 <密码>
当 提交登录信息
那么 页面应该返回 "Error Page"
例子:
|用户名 |密码 |
|'Jan1' |'password'|
|'Jan2' |'password'|
JavaScript对应实现方法
var { Given, When, Then,And} = require('cucumber');
Given('当我在网站的首页', function() {
return this.driver.get('http://0.0.0.0:7272/');
});
When('输入用户名 {string}', function (text) {
return this.driver.findElement(By.id('username_field')).sendKeys(text)
});
When('输入密码 {string}', function (text) {
return this.driver.findElement(By.id('password_field')).sendKeys(text)
});
When('提交登录信息', function () {
return this.driver.findElement(By.id('login_button')).click()
});
Then('页面应该返回 {string}', function (string) {
this.driver.getTitle().then(function(title) {
expect(title).to.equal(string);
});
});
如果你有写过java版cucumber,你会发现Java与JavaScript的区别,在于JavaScript只要在Gherkin feature文件中写中文:假设-当-那么,而JavaScript文件中,实现方法的Gherkin关键字还是英文Given-When-Then。
如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uNfMEY91-1618134780221)(http://thyrsi.com/t6/647/1546356083x2890211744.jpg)]
行为驱动开发是确保产品功能运行正确的绝佳方式,而Cucumber正是一个实践这种测试理念的强大工具。使用Cucumber,可以使产品中的每个参与者都可以阅读,理解甚至编写测试。
这篇文章只是抓住了Cucumber功能的表面,所以我建议你自己尝试一下,去体验它的作用。Cucumber也有一个非常活跃的社区,他们的Google Group和Gitter频道是当你遇到困难时寻求帮助的好去处。
你已经开始使用Cucumber了吗?这篇文章是否鼓励了你进行尝试?如果你想支持我,可以点击底部Donate按钮,支持下!
cuketest
cucumber debug
cucumber 官方教程
cucumber for JavaScript 官方源码
cucumber for JavaScript 官方API
cucumber for ruby 官方源码
cucumber for Java 官方源码
cucumber for Java IMB教程
转载请注明出处:https://liyinchigithub.github.io/ 谢谢您的配合