愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

BDD工具Cucumber学习记录

一、前言

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指定下testscript:

{
    // ...
    "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>场景描述,即明确地对软件行为做说明
    • GivenGWT(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基本的使用方法,而具体应用到业务中时,我们还可以抽取公共逻辑、写更模块化的测试逻辑脚本


参考