一、前言
BDD(Behavior Driven Development)
是一种有效的软件开发与测试方法,它能够清晰化地描述应用程序的行为,用于在业务方、开发者与测试人员之间明确业务需求,并且为更好地自动化测试提供指导。Cucumber
是广泛使用的一个BDD
工具,支持多种语言,本文则基于JS
版的Cucumber
学习进行总结和记录。
二、如何创建一个Cucumber项目?
首先,我们需要先创建一个新目录并且初始化一个Node工程,如下:
$ mkdir learn-cucumber
$ cd learn-cucumber
$ npm init --yes
然后,添加Cucumber
作为开发依赖,即:
$ npm i cucumber -D
然后修改package.json
指定下test
script:
{
// ...
"scripts": {
"test": "cucumber-js"
}
// ...
}
此后,我们还需要创建一些目录结构,一个cucumber
项目需要拥有如下的目录和文件:
features
目录features/step_definitions
目录,并在此目录下创建stepdefs.js
,内容如下:
const assert = require('assert')
const { Given, When, Then } = require('cucumber')
- 在项目根目录下创建
cucumber.js
,其内容为:
module.exports = {
default: `--format-options '{"snippetInterface": "synchronous"}'`
}
所以,现在我们的目录结构看起来是这样子的:
+-- learn-cucumber
+-- features
| +-- step_definitions
| |__ stepdefs.js
|__ cucumber.js
三、Cucumber
1、Cucumber描述文档
当采用BDD
时,我们需要编写描述文档示例来明确软件的功能,那么在描述文档中,会有多个示例来描述一个特性,而一个示例则称之为一个场景(Scenario)
。描述文档放置于feature
目录,并以.feature
作为文件扩展名
现在,我们以实现”判断周日不是周五“这个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_name>
:特性以及特性名称,通常建议是使用和文件名相似的名称作为特性名称Feature描述
:第二行起的这一段内容是对特性进行描述的,它是一个简短的说明,Cucumber
不会执行这段内容,所以这段内容只是作为文档之用的Scenario: <scenario_desc>
:场景
描述,即明确地对软件行为做说明Given
:GWT(Given/When/Then)
是典型的BDD描述格式。Given
描述场景的前提条件、初始状态,通常是现在完成时When
:表明采取某个动作或者发生了某个事件,是个动词,采用一般现在时Then
:用should be
来描述一种期望的结果
2、编写测试逻辑
现在,我们可以执行如下命令来跑一下Cucumber
$ npm test
我们会发现执行结果为:
UUU
Warnings:
1) Scenario: Sunday isn't 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
这是因为,我们只编写了描述文档,但是并没有指定自动化测试的逻辑,为了能够让Cucumber
进行自动化测试,我们则还需要根据描述文档编写测试逻辑。测试逻辑可编写在features/step_definitions/stepdefs.js
里,我们可以简单地复制以上提示的内容,然后stepdefs.js
看起来便如下所示:
const assert = require('assert')
const { Given, When, Then } = require('cucumber')
Given('today is Sunday', function() {
return 'pending'
})
When('I ask whether it\'s Friday yet', function() {
return 'pending'
})
Then('I should be told {string}', function(string) {
return 'pending'
})
这时候,我们再跑一下npm test
,输出:
P--
Warnings:
1) Scenario: Sunday isn't Friday # features/is_it_friday_yet.feature:4
? Given today is Sunday # features/step_definitions/stepdefs.js:4
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:12
1 scenario (1 pending)
3 steps (1 pending, 2 skipped)
0m00.001s
这时候,Cucumber
会结合描述文档和测试逻辑,找出步骤定义并且执行测试逻辑。但是当前执行的测试结果为pending
,这说明我们还需要修改下代码,以做一些有意义的事情
3、测试失败时会怎么样?
我们想知道如果Cucumber
执行测试不成功时会发生什么,那么我们来改一下stepdefs.js
,使得预期输出结果不是Nope
,即:
const assert = require('assert')
const { Given, When, Then } = require('cucumber')
function isItFriday(today) {
// nothing to return
}
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)
})
因为isItFriday
没有任何返回值,所以this.actualAnswer
的指便永远都是undefined
,那么在Then
里,expectedAnswer
会在运行时被赋予Nope
这个值,故下断言的时候,是不能通过断言的。
执行npm test
,可看到输出如下:
..F
Failures:
1) Scenario: Sunday isn't 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> (/Users/ruphi.liu/Desktop/Lab/learn-cucumber/features/step_definitions/stepdefs.js:17:12)
1 scenario (1 failed)
3 steps (1 failed, 2 passed)
0m00.002s
4、测试成功时会怎么样?
接下来,我们可以再修改stepdefs.js
里的逻辑,使得测试能够通过,我们只需要修改isItFriday()
函数便可,即:
function isItFriday(today) {
return today === 'Friday' ? 'TGIF' : 'Nope'
}
再次npm test
,输出:
...
1 scenario (1 passed)
3 steps (3 passed)
0m00.001s
很好,Nice~
5、变量和例子
有时候,我们需要测试多个场景,这些场景可能只是输入和预期输出不同,如果要为此编写多个场景,那么太麻烦了!这种情况下,我们就可以使用变量
和例子
功能。仍然拿之前的例子,假设我们现在要判断周五
、周日
或者周XX
,那么我们可以将我们的描述文档改为:
Feature: Is it Friday yet?
Everybody wants to know when it's Friday
Scenario: Sunday isn't 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>"
和"<answer>"
就是变量,而Examples:
就是例子,例子其实就是对变量的枚举,当执行时,Cucumber
会读入例子中的每一行值,然后传入到测试脚本里执行。因此,我们还需要修改stepdefs.js
,如下:
const assert = require('assert')
const { Given, When, Then } = require('cucumber')
function isItFriday(today) {
return today === 'Friday' ? 'TGIF' : '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)
})
最后执行npm test
,得到:
.........
3 scenarios (3 passed)
9 steps (9 passed)
0m00.002s
以上就是Cucumber
基本的使用方法,而具体应用到业务中时,我们还可以抽取公共逻辑、写更模块化的测试逻辑脚本