Cucumber行为驱动开发BDD入门教程 JavaScript版
本博客从Cucumber官方教程翻译过来,因水平有限,翻译有误的地方请读者不吝赐教。
以下是翻译部分:
在这个快速教程中,你将学习如何:
-
安装Cucumber
-
-
使用JavaScript写第一个步骤定义(step definition)
-
运行Cucumber
-
学习BDD的基本工作流
我们将用Cucumber来开发一个可以辨别今天是否已经星期五的小型库(library)。
在我们开始前,你需要以下工具:
-
Node.js
-
一个文本编辑器
打开终端,验证Node.js已经恰当安装了:
node -v
npm -v
这两行命令各自都应该会打印出版本号。
创建一个空的Cucumber项目
我们通过创建一个新的文件夹和一个空的Node.js项目来开始。
mkdir hellocucumber cd hellocucumber npm init --yes
添加Cucumber作为开发的依赖
npm install cucumber --save -dev
在文本编辑器中打开package.json
,改变test
处,使它看起来像:
{ "name": "hellocucumber", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "cucumber-js" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "cucumber": "^5.0.3" } }
准备文件结构
mkdir features
mkdir features/step_definitions
在项目的根目录创建一个cucumber.js
文件,添加以下内容:
module.exports = { default: `--format-options '{"snippetInterface": "synchronous"}'` }
创建一个features/step_definitions/stepdefs.js
文件,添加以下内容:
const assert = require('assert');
const { Given, When, Then } = require('cucumber');
你现在就有了一个安装了Cucumber的简单项目。
验证Cucumber安装
为了确保每个组件在一起能够正确工作,我们现在运行Cucumber
# Run via NPM
npm test
#Run standalone
./node_modules/.bin/cucumber-js
你应该能看见类似下面内容:
0 Scenarios
0 steps
0m00.000s
Cucumber的输出告诉我们它没有发现可以运行的场景。
写一个Scenario
当我们用Cucumber来完成BDD时,我们使用具体的例子(example)来指定我们想要软件完成什么功能。场景(Scenario)要在产品代码之前编写。它们(指场景)以可执行规范(executable specification)的身份作为它们生命周期的开始。当产品代码做出来后,场景就承担起活文件(living documentation)和自动化测试(automated tests)的角色。
在Cucumber中,一个实例(example)被称为一个场景(Scenario)。场景在.feature
文件中定义, 该文件放在features
文件夹或其子文件夹中。
一个具体的实例(concrete example)是星期天不是星期五。
创建一个名为features/is_it_friday_yet.feature
空文件,添加以下内容:
Feature: Is it Friday yet? Everybody wants to know when it's Friday Scenario: Sunday isn't Friday Given today is Sunday When I ask whether it's Friday yet Then I should be told "Nope"
这个文件的第一行以关键字Feature:
开头,后跟着一个名字。使用和文件名一样的名字比较好。
第二行是对特征(feature)的简要介绍。Cucumber不会运行这行,它只起到文档记录的作用。
第四行中Scenario: Sunday is not Friday
是一个场景(Scenario
),就是一个描述了软件表现什么行为的具体实例(concrete example
)。
最后以关键字Given
,When
和Then
开头的三行是四个场景的步骤(step
)。这里是Cucumber将会执行的地方。
看到提示scenario是undefined
既然我们有了一个场景,我们可以让Cucumber执行它。
npm test
Cucumber将会告诉我们,有一个undefined的场景和三个undefined的步骤。同时Cucumber还会建议我们用一些代码片段来定义(define
)这三个步骤:
UUU
Warnings:
1) Scenario: Sunday is not Friday # features/is_it_friday_yet.feature:4
? Given today is Sunday
Undefined. Implement with the following snippet:
Given('today is Sunday', function () {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
? When I ask whether it's Friday yet
Undefined. Implement with the following snippet:
When('I ask whether it\'s Friday yet', function () {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
? Then I should be told "Nope"
Undefined. Implement with the following snippet:
Then('I should be told {string}', function (string) {
// Write code here that turns the phrase above into concrete actions
return 'pending';
});
1 Scenario (1 undefined)
3 steps (3 undefined)
0m00.000s
复制这三个undefined步骤的代码片段,把它们粘到features/step_definitions/stepdefs.js
。
看到提示scenario是pending
重新运行Cucumber。这次输出有一点不一样:
P--
Warnings:
1) Scenario: Sunday is not Friday # features/is_it_friday_yet.feature:4
? Given today is Sunday # features/step_definitions/stepdefs.js:3
Pending
- When I ask whether it's Friday yet # features/step_definitions/stepdefs.js:8
- Then I should be told "Nope" # features/step_definitions/stepdefs.js:13
1 Scenario (1 pending)
3 steps (1 pending, 2 skipped)
0m00.001s
Cucumber发现了我们的一个步骤定义并且执行了它。它们被标识为pending
,意味着我们需要让它们做一些有意义的事情。
看到提示scenario为failing
下一步就是完成在步骤定义的注释告诉我们要做的事情。
Write code here that turns the phrase above into concrete actions
尝试在步骤的代码中使用同样的单词。
把你的步骤定义代码变成这样:
const assert = require('assert'); const { Given, When, Then } = require('cucumber'); function isItFriday(today) { // We'll leave the implementation blank for now } Given('today is Sunday', function () { this.today = 'Sunday'; }); When('I ask whether it\'s Friday yet', function () { this.actualAnswer = isItFriday(this.today); }); Then('I should be told {string}', function (expectedAnswer) { assert.equal(this.actualAnswer, expectedAnswer); });
重新运行Cucumber:
..F
Failures:
1) Scenario: Sunday is not Friday # features/is_it_friday_yet.feature:4
✔ Given today is Sunday # features/step_definitions/stepdefs.js:8
✔ When I ask whether it's Friday yet # features/step_definitions/stepdefs.js:12
✖ Then I should be told "Nope" # features/step_definitions/stepdefs.js:16
AssertionError [ERR_ASSERTION]: undefined == 'Nope'
at World.<anonymous> (/private/tmp/tutorial/hellocucumber/features/step_definitions/stepdefs.js:17:10)
1 Scenario (1 failed)
3 steps (1 failed, 2 passed)
进步了!前两个步骤通过了,但是最后一个失败了。
看到提示scenario为passing
让我们完成可能的最简单的事情来让场景得以通过。在这个例子中,只需要让函数返回Nope
就可以了:
function isItFriday(today) { return 'Nope'; }
重新运行Cucumber:
...
1 Scenario (1 passed)
3 steps (3 passed)
0m00.003s
恭喜你!你已经得到你的第一个Cucumber全绿的场景。
添加另一个failing测试
下一个要测试的东西是,当今天是星期五的时候我们应该得到结果为正确。
更新is-it-friday-yet.feature
文件:
Feature: Is it Friday yet? Everybody wants to know when it's Friday Scenario: Sunday isn't Friday Given today is Sunday When I ask whether it's Friday yet Then I should be told "Nope" Scenario: Friday is Friday Given today is Friday When I ask whether it's Friday yet Then I should be told "TGIF"
我们需要增加一个步骤定义来设置today
为Friday:
Given('today is Friday', function () { this.today = 'Friday'; });
当我们运行这个测试时,将会失败:
.....F
Failures:
1) Scenario: Friday is Friday # features/is_it_friday_yet.feature:9
✔ Given today is Friday # features/step_definitions/stepdefs.js:8
✔ When I ask whether it's Friday yet # features/step_definitions/stepdefs.js:16
✖ Then I should be told "TGIF" # features/step_definitions/stepdefs.js:20
AssertionError [ERR_ASSERTION]: 'Nope' == 'TGIF'
+ expected - actual
-Nope
+TGIF
at World.<anonymous> (/private/tmp/tutorial/hellocucumber/features/step_definitions/stepdefs.js:21:10)
2 scenarios (1 failed, 1 passed)
6 steps (1 failed, 5 passed)
这是因为我们还没有完成逻辑部分!让我们接下来完成这个工作。
通过
我们应该更新我们的代码,让它真的去验证today
是否等于Friday
:
function isItFriday(today) { if (today === "Friday") { return "TGIF"; } else { return "Nope"; } }
重新运行Cucumber:
......
2 scenarios (2 passed)
6 steps (6 passed)
0m00.002s
使用变量和实例
众所周知,一个星期除了星期天和星期五外还有其它天。让我们使用变量来更新场景,然后验证更多的可能性。我们将会使用变量和实例来验证星期五,星期天和任意其它天。
更新is-it-friday-yet.feature
文件。注意当我们开始使用多个实例(Examples
)时,我们是如何从单个场景(Scenario
)到场景大纲(Scenario Outline
)的:
Feature: Is it Friday yet? Everybody wants to know when it's Friday Scenario Outline: Today is or is not Friday Given today is "<day>" When I ask whether it's Friday yet Then I should be told "<answer>" Examples: | day | answer | | Friday | TGIF | | Sunday | Nope | | anything else! | Nope |
我们需要用一个读取<day>
字符串的值的步骤定义来替换原来的步骤定义中的today is Sunday
和today is Friday
两处地方。更新stepdefs.js
文件,像下面这样:
const assert = require('assert'); const { Given, When, Then } = require('cucumber'); function isItFriday(today) { if (today === "Friday") { return "TGIF"; } else { return "Nope"; } } Given('today is {string}', function (givenDay) { this.today = givenDay; }); When('I ask whether it\'s Friday yet', function () { this.actualAnswer = isItFriday(this.today); }); Then('I should be told {string}', function (expectedAnswer) { assert.equal(this.actualAnswer, expectedAnswer); });
重新运行Cucumber:
.........
3 scenarios (3 passed)
9 steps (9 passed)
0m00.001s
重构
既然我们有了可以工作的代码,我们应该做一些重构工作:
-
我们应该把
isItFriday
函数从测试代码中移到生成代码中 -
我们可以从步骤定义中的某些地方提取一些
helper
方法,这样在分布在几个地方的函数都可以使用。
总结