文档结构  
翻译进度:98%     翻译赏金:0 元 (?)    ¥ 我要打赏

当今,每个人都听说过测试驱动开发(TDD),以及它将对整个产品和开发周期带来的好处。这些都是显而易见的。每次你为一段代码编写测试,你将知道代码是否正常运行。而且,更重要的是,以后你将第一时间知道代码是否发生中断。

行为驱动开发(BDD)是对此理念的扩展,但不同的是它并不是测试代码,而是测试产品,特别是产品是否按照你的期望行为去运行。

在本文中,我将向你展现如何搭建并运行Cucumber,该框架被用来运行以BDD风格编写的自动化验收测试。这种测试的优势在于,它们可以用简单的英文来编写,因此参与项目中的非技术人员也可以理解它。在阅读本文完毕后,你可以决定Cucumber是否适用与你以及你的团队,并开始编写你自己的验收测试。

第 1 段(可获 2.16 积分)

准备好了吗?让我们来探索吧。

BDD vs TDD — 有什么不同?

此作者的其他文章

区别主要是在测试的结构和写法上。

在TDD的设定里,测试由编写被测代码的开发人员编写、维护和理解。一般来说不需要其他人去理解测试,这很棒。

在BDD的设定里,测试需要被除了编写功能的开发人员之外的更多人理解。 有更多的项目参与者对产品的功能感兴趣。

这些人员可能包括质量保证人员、产品分析师、销售人员,甚至是高级管理人员。

第 2 段(可获 1.68 积分)

这意味着,在一个理想的情况下,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();
});

 

第 3 段(可获 0.5 积分)

和:

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?

Cucumber是一个为行为驱动开发设计的测试框架。它允许你以Gherkin的形式定义你的测试,然后将这些可执行的Gherkin脚本与代码绑定。

第 4 段(可获 1.2 积分)

Gherkin是一种用来编写Cucumber测试的特定领域语言。它允许测试脚本以人类可读的格式编写,并在参与产品开发的所有人员之间共享。

Gherkin文件包含用Gherkin语言编写的测试。这些文件通常具有.feature的文件扩展名。Gherkin文件的内容通常被简称为“gherkins”。

Gherkins

在一个Gherkin定义的测试中,存在特征和场景的概念。这类似于其他测试框架中的测试套件和测试用例,被用来以一种简洁的方式去构建测试。

第 5 段(可获 1.36 积分)

一个场景实际上只是一个单一的测试。它应该在你的应用程序中只测试一件事。

一个功能是一组相关的场景。因此,它会在你的应用程序中测试许多相关的东西。理想情况下,Gherkin文件中的功能将紧密映射到应用程序中的功能 — Gherkin由此得名。

每个Gherkin文件只包含一个功能,每个功能都包含一个或多个场景。

然后场景由步骤组成,这些步骤以特定的方式排序:

  • Given – 这些步骤用于在测试之前设置初始状态
  • When – 这些步骤是要实际执行的测试
  • Then –  这些步骤被用来断言测试的结果
第 6 段(可获 1.56 积分)

理想情况下,每个场景应该是单个测试用例,因此When步骤的数量应该保持很少。

步骤都是可选的。例如,如果你完全不需要在测试开始前设置任何内容,你可以不设置Given步骤。

Gherkin文件是为人类可读设计的,并为参与产品开发的任何人带来益处。由于参与人员包括非技术人员,因此Gherkin文件应始终使用商业语言而不是技术语言编写。这意味着,例如,编写测试时,你不需要去引用某个UI组件,而是要去描述你想要测试的产品的功能。

第 7 段(可获 1.33 积分)

一个简单的Gherkin测试

以下是一个使用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脚本依然是正确的。

第 8 段(可获 1.09 积分)

你可以在Cucumber wiki上阅读更多关于Given When Then的内容。

Cucumber.js

当你使用Gherkin来编写测试用例后,你需要一些方法来执行它们。在JavaScript世界中,有一个叫做Cucumber.js的模块,可以让你做到这一点。它的工作原理是允许你定义可关联到你的Gherkin文件中定义的各个步骤的JavaScript代码。然后通过加载Gherkin文件,以正确的顺序执行与每个步骤相关的JavaScript代码来运行测试。

例如,在上面的示例中,将执行以下步骤:

第 9 段(可获 1.33 积分)
Given('I have loaded Google', function() {});
When('I search for {stringInDoubleQuotes}', function() {});
Then('the first result is {stringInDoubleQuotes}', function() {});

不用担心这些代码的含义 – 稍后会详细解释。本质上,它定义了一些Cucumber.js将代码绑定到Gherkin文件中的步骤的方法。

将Cucumber.js添加到你的版本中

在版本中加入Cucumber.js,只需要添加cucumber模块,然后配置并运行。第一部分如下:

第 10 段(可获 0.96 积分)
$ npm install --save-dev cucumber

其中的第二个参数取决于你执行构建的方式。

手动执行

手动执行Cucumber是相对容易的,最好是先确保你能做到这一点,因为以下解决方案都是自动执行相同的方法。

安装后,可执行文件将为./node_modules/.bin/cucumber.js。当你运行它时,它需要知道文件系统上的哪个地方可以找到它所需的所有文件。这些文件都是Gherkin文件和要执行的JavaScript代码。

按照惯例,所有的Gherkin文件都将保存在features目录中,如果不指定,那么Cucumber将在同一个目录中查找要执行的JavaScript代码。指定Cucumber在哪里查找这些文件是一个明智的做法,因为这样可以更好地控制构建过程。

第 11 段(可获 1.88 积分)

例如,如果将所有的Gherkin文件保存在myFeatures目录中,并将所有JavaScript代码保存在mySteps中,则可以执行以下操作:

$ ./node_modules/.bin/cucumber.js ./myFeatures -r ./mySteps

-r标志是一个包含JavaScript文件的目录,以便为自动测试所需。还有其他一些标志可能会引起关注 — 请阅读帮助文本以了解它们的工作原理: $ ./node_modules/.bin/cucumber.js --help

这些目录以递归方式进行扫描,因此你可以根据特定情况对文件进行浅层或深层嵌套。

第 12 段(可获 1.04 积分)

npm 脚本

一旦手动运行Cucumber,将它作为npm脚本添加到构建中是一件小事。你只需要添加下面的命令 — 路径并不是限定的,因为npm会为你处理  — 在你的package.json中,如下所示:

"scripts": {
  "cucumber": "cucumber.js ./myFeatures -r ./mySteps"
}

这些做完了,你就可以执行:

$ npm run cucumber

它就会像之前一样执行你的Cucumber测试。

Grunt

的确存在用于执行Cucumber.js测试的Grunt插件。然而不幸的是,它已经过时了,并且不适用于最新版本的Cucumber.js,这意味着如果你使用它,你将错过许多新的改进。

第 13 段(可获 1.41 积分)

相反,我的首选方法是使用grunt-shell插件以与上述完全相同的方式执行命令。

安装完成后,配置,这只是将以下插件配置添加到您的Gruntfile.js中的一种情况:

shell: {
  cucumber: {
    command: 'cucumber.js ./myFeatures -r ./mySteps'
  }
}

现在,和之前一样,你可以通过运行grunt shell:cucumber来执行你的测试。

Gulp

Gulp与Grunt的情况完全相同,现有的插件已经过时并且会使用旧版本的Cucumber工具。同样的,在Gulp中你可以使用gulp-shell模块来执行Cucumber.js命令,就像其他场景一样。

第 14 段(可获 1.23 积分)

设置一下:

gulp.task('cucumber', shell.task([
  'cucumber.js ./myFeatures -r ./mySteps'
]));

现在,和之前一样,你可以通过运行gulp cucumber来执行你的测试。

第一个Cucumber测试

请注意,本文中的所有代码示例均可在GitHub上获得。

现在我们知道如何执行Cucumber了,那就让我们编写一个测试。在这个例子中,我们将使用一些精心设计的例子向你展现系统是如何运作的。上,你可以做到更多,比如直接调用你正在测试的代码,对正在运行的服务进行HTTP API调用,或者使用Selenium驱动浏览器来测试你的应用程序。

第 15 段(可获 1.33 积分)

我们将用一个简单的例子将证明算法有效。一共有两个功能 — 加法和乘法。

首先,先配置好。

$ 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:

第 16 段(可获 1.03 积分)
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
$
第 17 段(可获 0.33 积分)

哇。我们刚刚写下了我们的Gherkin,并且一切都在执行。不过它现在还不能起作用,因为我们不知道如何处理这些步骤,但Cucumber非常清楚地告诉了我们。

现在让我们写第一步的文件吧。这将简单地按照Cucumber输出告诉我们的方式来执行这些步骤,这些步骤没有起到实际的作用,但是整理了输出。

文件为 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');
  });
});
第 18 段(可获 0.91 积分)

defineSupportCode hook是Cucumber.js的一种允许你提供用于各种不同场景的代码的方法。这些步骤都会被覆盖到,每次你想编写Cucumber可以直接调用的代码时,代码必须在某一步骤的代码块中。

注意,示例代码定义了三个不同的步骤 - 分别为Given,When和Then。每一个步骤的代码块都会被赋予一个字符串 - 或者是正则表达式 - 与feature文件中的某个步骤相匹配,并且与该步骤匹配时会执行该函数。占位符可以放入步骤字符串中 - 或者,如果使用正则表达式,则可以使用捕获表达式 - 而这些占位符将被提取出来并作为参数提供给您的函数。

第 19 段(可获 1.74 积分)

执行此操作提供了更简洁的输出,而实际上并没有做任何事情:

$ ./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
第 20 段(可获 0.18 积分)

现在需要让脚本运行起来。我们所需要做的就是在我们的定义的步骤中实现代码。同时也需要整理一下,以便阅读。这本质上是消除了回调参数,因为我们没有做任何异步的操作。

在以上操作完成之后,“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);
  });
});
第 21 段(可获 0.78 积分)

执行如下:

$ ./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文件执行来得到自动参数化测试。这意味可以非常简单的添加更多的场景。

第 22 段(可获 0.8 积分)

接下来,让我们证明乘法正确运作。为此,我们需要在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方法内添加以下代码块:

第 23 段(可获 0.54 积分)
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
$
第 24 段(可获 0.13 积分)

就这么简单,现在我们有了一个用来证明运算的易于扩展的测试套件。作为练习,请尝试扩展它以支持减法。如果遇到困难,你可以在评论中寻求帮助。

Cucumber.js的进阶技巧

这些功能都很不错,但是Cucumber的一些进阶功能能让我们的工作更加轻松。

异步步骤的定义

到目前为止,我们只写了同步步骤的定义。在JavaScript世界中,这不够。 JavaScript中有很多情况下需要是异步的,所以我们需要一些方法来处理它。

第 25 段(可获 1.4 积分)

值得庆幸的是,Cucumber.js有一些内置的处理方法,具体选择哪一种取决于你的喜好。

上述的方式是JavaScript中处理异步步骤的传统方法,即使用回调函数。如果定义步骤时将回调函数作为其最后一个参数,那么在触发此回调之前该步骤不会被视为完成。在这种情况下,如果回调被任何参数触发,那么这被认为是错误的,并且该步骤将失败。如果它没有任何参数触发,那么该步骤被认为是成功的。但是,如果回调没有被触发,那么框架最终会超时并且失败。本段文字的中心就是,如果你接收了一个回调参数,那么确保你调用它。

 

第 26 段(可获 1.85 积分)

例如,使用回调进行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,则步骤将会成功。

第 27 段(可获 1.05 积分)

或者,如果您返回的不是Promise,那么该步骤将立即被视为已成功。其中包括返回undefinednull。这意味着您可以在执行该步骤时选择是否需要返回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));
});
第 28 段(可获 1.06 积分)

Feature Background

一个Feature Background是一个Gherkin片段,它被预先添加到文件中每个Scenario的开头。这使得我们在每个Scenario之间可以共享常用的步骤,而不需要重复实现它们。

Background是使用Background关键字而不是Scenario关键字编写的。理想情况下,这其中应该只包含Given步骤,因为包含每个测试之间共享的When步骤或Then步骤是没有意义的。但是框架不会在这上面限制你,所以要悉心构造你的测试结构。

第 29 段(可获 1.16 积分)

使用它,我们可以重新编写我们的添加的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 Outlines是从测试数据表中生成scenario的一种方式。这使得我们能以比以前更高效的方式进行参数化测试,可以使用插入不同值的方式重复同一测试脚本。

第 30 段(可获 0.96 积分)

Scenario outlines通过使用Scenario Outline关键字而不是Scenario关键字编写,之后提供一个或多个Examples表。然后将来自Examples表格的参数替换到Scenario Outline中以生成运行的scenarios。

使用它,我们可以重构我们的乘法feature,如下所示:

Feature: Multiplication

  Scenario Outline: <a> * <b>
    Given I start with <a>
    When I multiply by <b>
    Then I end up with <answer>

  Examples:
    | a | b | answer |
    | 1 | 0 | 0      |
    | 1 | 1 | 1      |
    | 2 | 2 | 4      |
第 31 段(可获 0.56 积分)

再一次,这和以前完全一样,但重复次数要少得多。你会看到你是否运行它,它会在输出中生成与之前完全相同的场景:

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中使用数据表。这些可以用作提供数据表或结构化输入或许多其他事物的方式。

第 32 段(可获 1.09 积分)

例如,可以将添加方案的方式重写为添加任意自变量的值,如下所示:

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对象,它有一个可以调用的原始方法。此方法返回数据表中所有值的二维数组,因此外部数组中的每个表项都是表中的一行,而内部数组中的每个表项都是该行的单元格 - 一个字符串。

 

第 33 段(可获 1.13 积分)

一个更复杂的例子是使用数据表来填充表单。然后使用该表来提供所有的输入,而不是使用难以阅读的步骤定义。这可以读取如下内容:

Scenario: Create a new user
  When I create a new user with details:
    | Username | graham               |
    | Email    | my.email@example.com |
    | Password | mySecretPassword     |
  Then the user is created successfully

在这种情况下,通过使用rowsHash方法,数据表类可以使我们更容易访问表。

步骤可能为如下所示:

第 34 段(可获 0.85 积分)
When('I create a new user with details:', function (table) {
  const data = table.rowsHash();
  createUser(data);
});

在这种情况下,data对象将从数据表中解析出来,如下所示:

{
  "Username": "graham",
  "Email": "my.email@example.com",
  "Password": "mySecretPassword"
}

通过第一列中的键可以轻松访问这些字段。

Hooks

像大多数测试框架一样,Cucumber.js支持在场景运行之前和之后执行hook

它们的设置方式与步骤定义相同,只需要简单按名字的调用hook - 在场景运行之前或之后,无论运行成功或失败与否。

第 35 段(可获 1.05 积分)

一个简单的例子,为了能使我们的feature们更加可靠,编写代码如下:

defineSupportCode(function({ Before, Given, Then, When }) {
  let answer;

  Before(function() {
    answer = 0;
  });
});

如上所述,优化后的数学步骤文件可以确保在每个场景运行之前将answer变量重置为0,这意味着如果我们从0开始则不需要Given步骤。

 

如果你需要它,这些hook的第一个参数始终是hook运行前后的场景的场景结果。这可以用来使功能与正在运行的场景进行相应的适配。

第 36 段(可获 1.16 积分)

Hooks can be made asynchronous in exactly the same way as step definitions, by accepting a callback function as a second parameter or by returning a Promise.

Events

If the simple before and after hooks aren’t enough for you, for whatever reason, then there are many more events to explore. These give us the ability to handle:

  • BeforeFeatures – called once before anything is run at all, provided with the list of features.
  • BeforeFeature – called before each Feature file is run, provided with the Feature.
  • BeforeScenario – called before each Scenario is run, provided with the Scenario. This is roughly analogous to the “Before” hook.
  • BeforeStep – called before each Step is run, provided with the Step.
  • StepResult – called after each Step is run, provided with the result of the step.
  • AfterStep – called after each Step is run, provided with the Step.
  • ScenarioResult – called after each Scenario is run, provided with the result of the scenario.
  • AfterScenario – called after each Scenario is run, provided with the Scenario. This is roughly analogous to the “After” hook.
  • AfterFeature – called after each Feature is run, provided with the Feature.
  • FeaturesResult – called once after everything is run, provided with the result of running everything.
  • AfterFeatures – called once after everything is run, provided with the list of features.
第 37 段(可获 2.64 积分)

这些hook将与测试框架的整个生命周期进行交互,并将按上面列出的顺序进行调用。

使用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));
  });
});
第 38 段(可获 0.48 积分)

通过接受回调函数作为第二个参数,或者通过返回一个Promise对象,事件处理程序可以与定义步骤完全相同的方式进行异步处理。

World – 共享代码和数据

到目前为止,我们都无法在步骤之间共享代码。我们可以很容易地拥有很多包含步骤定义,hook,event等的JavaScript文件,但它们彼此独立。

碰巧,事实并非如此。Cucumber.js有一个“World”的概念,它是所有运行场景的域。所有的步骤定义、挂钩和事件处理程序都可以通过访问this参数来访问它,无论定义步骤定义的文件如何。这就是为什么所有示例都是使用传统的function关键字编写的,而不是箭头函数。JavaScript中的箭头函数为您重新绑定此变量,这意味着您将无法访问测试中可能需要的World域。

第 39 段(可获 2.25 积分)

这种方式使得你不通过额外的处理,就可以直接使用它。这也意味着我们可以通过在多个文件之间逻辑分割Cucumber代码并使它们按预期工作,同时仍然可以访问共享域,从而使我们的代码更加整洁。

总结

行为驱动开发是确保产品功能运行正确的绝佳方式,而Cucumber正是一个实践这种测试理念的强大工具。使用Cucumber,可以使产品中的每个参与者都可以阅读,理解甚至编写测试。

第 40 段(可获 1.24 积分)

这篇文章只是抓住了Cucumber功能的表面,所以我建议你自己尝试一下,去体验它的作用。Cucumber也有一个非常活跃的社区,他们的Google GroupGitter频道是当你遇到困难时寻求帮助的好去处。

你已经开始使用Cucumber了吗?这篇文章是否鼓励了你进行尝试?无论如何,我都希望在下面的评论中听到您的意见。

本文由Jani Hartikainen进行评审。感谢SitePoint的所有审稿者对SitePoint做出的贡献!

第 41 段(可获 1.34 积分)

文章评论