首页 web前端 js教程 BDD在JavaScript:开始使用Cucumber和Gherkin

BDD在JavaScript:开始使用Cucumber和Gherkin

Feb 16, 2025 pm 01:09 PM

BDD in JavaScript: Getting Started with Cucumber and Gherkin

BDD in JavaScript: Getting Started with Cucumber and Gherkin

测试驱动开发 (TDD) 的好处已广为人知,它能提升产品质量和开发效率。每次编写代码测试时,都能确保代码的正确性,并能及时发现未来可能出现的代码错误。

行为驱动开发 (BDD) 在此基础上更进一步,它测试的是产品的行为,而非仅仅是代码,确保产品行为符合预期。本文将介绍如何使用 Cucumber 框架编写 BDD 风格的自动化验收测试。Cucumber 的优势在于,测试用例可以用简洁的自然语言编写,方便项目中非技术人员理解。阅读本文后,您可以判断 Cucumber 是否适合您的团队,并开始编写自己的验收测试。准备好了吗?让我们开始吧!

关键要点

  • BDD 在 TDD 的基础上,测试的是产品的行为而非代码,使其更易于被包括非技术人员在内的更广泛的利益相关者理解。
  • Cucumber 是一个 BDD 框架,它使用 Gherkin(一种易于理解的语言)来定义测试用例,确保所有利益相关者都能理解并参与测试过程。
  • Gherkin 语法将测试结构化为场景和特性,使用简单的 Given、When、Then 步骤来描述行为,而无需规定技术实现。
  • Cucumber.js 与 JavaScript 项目集成,运行 Gherkin 定义的测试,并通过各种插件和配置支持异步操作和外部测试工具。
  • Cucumber.js 的设置包括通过 npm 安装模块,配置它来查找特性文件和步骤定义,以及将其可选地集成到构建脚本或任务运行器(如 Grunt 或 Gulp)中。
  • 本文提供了一个基本的 Cucumber 测试示例,演示了为加法和乘法设置 Gherkin 场景,使用简单的断言来验证这些操作的正确性。
  • 本文还概述了 Cucumber.js 的高级功能,例如对异步测试的支持、用于参数化测试的场景大纲以及用于设置前提条件和后置条件的钩子,以增强测试能力。

BDD 与 TDD 的区别

主要体现在测试的结构和编写方式上。在 TDD 中,测试由编写代码的开发人员编写、维护和理解。其他人可能根本不需要阅读测试,这完全没问题。但在 BDD 中,测试需要被比编写功能的开发人员多得多的人理解。许多利益相关者都关心产品行为是否正确,例如 QA 人员、产品分析师、销售人员,甚至高层管理人员。这意味着,理想情况下,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); // 假设的数字
  browser.quit();
});
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

以及:

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); // 假设的数字
  browser.quit();
});
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

这两个测试执行相同的操作,但一个是可读的自然语言,另一个只能被了解 JavaScript 和 Selenium 的人理解。本文将向您展示如何使用 Cucumber.js 框架在 JavaScript 项目中实现 BDD 测试,从而使您的产品受益于这种级别的测试。

什么是 Cucumber/Gherkin?

Cucumber 是一个用于行为驱动开发的测试框架。它允许您以 Gherkin 格式定义测试,并通过将其与代码绑定来使这些 Gherkin 可执行。Gherkin 是一种领域特定语言 (DSL),用于编写 Cucumber 测试。它允许以人类可读的格式编写测试脚本,然后可以在产品开发中的所有利益相关者之间共享。Gherkin 文件是包含用 Gherkin 语言编写的测试的文件。这些文件通常具有 .feature 文件扩展名。这些 Gherkin 文件的内容通常简称为“Gherkin”。

Gherkin

在 Gherkin 定义的测试中,您有特性场景的概念。它们类似于其他测试框架中的测试套件和测试用例,提供了一种清晰的测试结构方式。场景只是一个单独的测试。它应该只测试应用程序中的一个方面。特性是一组相关的场景。因此,它将测试应用程序中许多相关的方面。理想情况下,Gherkin 文件中的特性将与应用程序中的特性紧密映射——因此得名。每个 Gherkin 文件都包含一个特性,每个特性都包含一个或多个场景。场景然后由步骤组成,这些步骤按特定顺序排列:

  • Given – 这些步骤用于在执行测试之前设置初始状态
  • When – 这些步骤是实际要执行的测试
  • Then – 这些步骤用于断言测试结果

理想情况下,每个场景都应该是一个单独的测试用例,因此 When 步骤的数量应该保持非常少。步骤是完全可选的。例如,如果您根本不需要设置任何内容,则可能没有 Given 步骤。Gherkin 文件旨在易于阅读,并使参与产品开发的任何人都能受益。这包括非技术人员,因此 Gherkin 文件应始终使用业务语言而不是技术语言编写。这意味着,例如,您不引用单个 UI 组件,而是描述您想要测试的产品概念。

Gherkin 测试示例

以下是搜索 Google 的 Cucumber.js 的 Gherkin 示例:

Given I have opened a Web Browser
When I load the Wikipedia article on "Wiki"
Then I have "19" Wiki Links
登录后复制
登录后复制
登录后复制
登录后复制

我们可以立即看到,此测试告诉我们做什么,而不是如何做。它使用任何人都能理解的语言编写,并且——重要的是——无论最终产品如何调整,它都最有可能保持正确。Google 可能会决定完全更改其 UI,但只要功能等效,则 Gherkin 仍然准确。您可以在 Cucumber wiki 上阅读更多关于 Given When Then 的信息。

Cucumber.js

在以 Gherkin 格式编写测试用例后,您需要一种方法来执行它们。在 JavaScript 世界中,有一个名为 Cucumber.js 的模块允许您执行此操作。它允许您定义 JavaScript 代码,Cucumber.js 可以将其连接到 Gherkin 文件中定义的各种步骤。然后,它通过加载 Gherkin 文件并按正确的顺序执行与每个步骤关联的 JavaScript 代码来运行测试。例如,在上面的示例中,您将拥有以下步骤:

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); // 假设的数字
  browser.quit();
});
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

不必过于担心所有这些含义——稍后将详细解释。但本质上,它定义了一些方法,Cucumber.js 框架可以使用这些方法将您的代码绑定到 Gherkin 文件中的步骤。

(以下内容与原文基本一致,略作调整以保持流畅性和可读性,并对部分语句进行同义词替换)

将 Cucumber.js 包含在您的构建中

将 Cucumber.js 包含在您的构建中,只需将 cucumber 模块添加到您的构建中,然后配置它即可运行。第一步如下所示:

Given I have opened a Web Browser
When I load the Wikipedia article on "Wiki"
Then I have "19" Wiki Links
登录后复制
登录后复制
登录后复制
登录后复制

第二步取决于您如何执行构建。

手动运行

手动执行 Cucumber 相对容易,最好先确保您可以这样做,因为以下解决方案都是自动执行相同操作的方法。安装后,可执行文件将是 ./node_modules/.bin/cucumber.js。运行它时,它需要知道在文件系统上的哪个位置可以找到所有必需的文件。这些文件既包括 Gherkin 文件,也包括要执行的 JavaScript 代码。按照惯例,所有 Gherkin 文件都将保存在 features 目录中,如果您没有指示它执行其他操作,则 Cucumber 也将在同一目录中查找要执行的 JavaScript 代码。但是,指示它查找这些文件的位置是一种明智的做法,这样您可以更好地控制构建过程。例如,如果您将所有 Gherkin 文件保存在 myFeatures 目录中,并将所有 JavaScript 代码保存在 mySteps 中,则可以执行以下操作:

Given I have loaded Google
When I search for "cucumber.js"
Then the first result is "GitHub - cucumber/cucumber-js: Cucumber for JavaScript"
登录后复制
登录后复制

-r 标志是一个包含 JavaScript 文件的目录,Cucumber 会自动加载这些文件用于测试。还有一些其他标志可能也很有趣——只需阅读帮助文本即可了解它们的工作方式:$ ./node_modules/.bin/cucumber.js --help。这些目录会递归扫描,因此您可以根据具体情况将文件嵌套得浅或深。

npm 脚本

手动运行 Cucumber 后,将其添加到构建中作为 npm 脚本是一个简单的情况。您只需将以下命令(无需完全限定路径,因为 npm 会为您处理)添加到您的 package.json 中,如下所示:

Given('I have loaded Google', function() {});
When('I search for {stringInDoubleQuotes}', function() {});
Then('the first result is {stringInDoubleQuotes}', function() {});
登录后复制
登录后复制

完成后,您可以执行:

$ npm install --save-dev cucumber
登录后复制
登录后复制

它将完全按照您之前所做的那样执行 Cucumber 测试。

Grunt

确实存在一个用于执行 Cucumber.js 测试的 Grunt 插件。不幸的是,它已经过时了,并且不适用于更新版本的 Cucumber.js,这意味着如果您使用它,您将错过许多改进。相反,我更喜欢的方法是简单地使用 grunt-shell 插件以与上述完全相同的方式执行命令。安装后,配置它只需将以下插件配置添加到您的 Gruntfile.js 中:

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); // 假设的数字
  browser.quit();
});
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

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

Gulp

Gulp 与 Grunt 的情况完全相同,因为现有的插件已经过时,并且将使用旧版本的 Cucumber 工具。同样,在这里您可以使用 gulp-shell 模块像在其他场景中一样执行 Cucumber.js 命令。设置它很简单:

Given I have opened a Web Browser
When I load the Wikipedia article on "Wiki"
Then I have "19" Wiki Links
登录后复制
登录后复制
登录后复制
登录后复制

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

您的第一个 Cucumber 测试

请注意,本文中的所有代码示例都可以在 GitHub 上找到。

现在我们知道了如何执行 Cucumber,让我们实际编写一个测试。在这个示例中,我们将做一些相当人为的事情,只是为了展示系统的工作原理。实际上,您会做更复杂的事情,例如直接调用您正在测试的代码、对正在运行的服务进行 HTTP API 调用或控制 Selenium 来驱动 Web 浏览器以测试您的应用程序。我们的简单示例将证明数学仍然有效。我们将有两个特性——加法和乘法。首先,让我们进行设置。

Given I have loaded Google
When I search for "cucumber.js"
Then the first result is "GitHub - cucumber/cucumber-js: Cucumber for JavaScript"
登录后复制
登录后复制

您如何执行测试完全取决于您。在这个示例中,为了简单起见,我将手动执行它。在一个真实的项目中,您将使用上述选项之一将其集成到您的构建中。

Given('I have loaded Google', function() {});
When('I search for {stringInDoubleQuotes}', function() {});
Then('the first result is {stringInDoubleQuotes}', function() {});
登录后复制
登录后复制

现在,让我们编写我们的第一个实际特性。这将放在 features/addition.feature 中:

$ npm install --save-dev cucumber
登录后复制
登录后复制

非常简单,非常易于阅读。它准确地告诉我们正在做什么,而没有告诉我们如何去做。让我们尝试一下:

(以下内容与原文基本一致,略作调整以保持流畅性和可读性,并对部分语句进行同义词替换)

然后让我们编写我们的第一个步骤文件。这将简单地按照 Cucumber 输出告诉我们的方式实现步骤,这不会做任何有用的事情,但会整理输出。这将放在 steps/maths.js 中:

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); // 假设的数字
  browser.quit();
});
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

defineSupportCode 钩子是 Cucumber.js 的一种方法,允许您提供它将用于各种不同情况的代码。所有这些都将被涵盖,但本质上,任何时候您想要编写 Cucumber 将直接调用的代码,它都需要在这些块中的一个内部。您会注意到,此处的示例代码定义了三个不同的步骤——每个 Given、When 和 Then 一个。每个块都给出一个字符串(或者如果您需要的话,是一个正则表达式),该字符串与特性文件中的步骤匹配,以及在该步骤匹配时执行的函数。占位符可以放在步骤字符串中(或者如果您使用的是正则表达式,则使用捕获表达式代替),这些占位符将被提取出来并作为参数提供给您的函数。执行此操作将提供更简洁的输出,同时仍然实际上什么也不做:

(以下内容与原文基本一致,略作调整以保持流畅性和可读性,并对部分语句进行同义词替换)

现在让我们让它全部工作。我们只需要在我们步骤定义中实现代码即可。我们还将进行一些整理,以使阅读更容易。这实际上消除了对回调参数的需求,因为我们没有做任何异步操作。之后,我们的 steps/maths.js 将如下所示:

Given I have opened a Web Browser
When I load the Wikipedia article on "Wiki"
Then I have "19" Wiki Links
登录后复制
登录后复制
登录后复制
登录后复制

执行它看起来像这样:

(以下内容与原文基本一致,略作调整以保持流畅性和可读性,并对部分语句进行同义词替换)

就这样,我们得到了一个非常易于扩展的测试套件,它证明了数学是正确的。作为一个练习,为什么不尝试扩展它以支持减法呢?如果您遇到困难,可以在评论中寻求帮助。

(以下内容与原文基本一致,略作调整以保持流畅性和可读性,并对部分语句进行同义词替换,并对部分章节进行合并和简化)

更高级的 Cucumber.js技巧

这都很好,但是 Cucumber 可以做一些更高级的事情,这将使我们的生活更轻松。

异步步骤定义

到目前为止,我们只编写了同步步骤定义。但是,在 JavaScript 世界中,这通常不够好。JavaScript 中的很多东西都需要异步,因此我们需要一些方法来处理它。谢天谢地,Cucumber.js 有几种内置的方法来处理这个问题,这取决于您的喜好。上面暗示过的方法,这是处理异步步骤的更传统的 JavaScript 方法,是使用回调函数。如果您指定步骤定义应该将回调函数作为其最后一个参数,则只有在触发此回调后,才认为步骤已完成。在这种情况下,如果回调使用任何参数被触发,则这被认为是一个错误,并且步骤将失败。如果它在没有任何参数的情况下被触发,则认为步骤已成功。但是,如果根本没有触发回调,则框架最终会超时并使步骤失败。故事的寓意?如果您接受回调参数,请确保调用它。例如,使用回调进行 HTTP API 调用的步骤定义可能如下所示。这是使用 Request 编写的,因为它在响应上使用回调。

(以下内容与原文基本一致,略作调整以保持流畅性和可读性,并对部分语句进行同义词替换)

另一种方法,也是更优选的方法是通过返回类型。如果您从步骤返回一个 Promise,则只有当 Promise 完成时,才认为步骤已完成。如果 Promise 被拒绝,则步骤将失败;如果 Promise 被 fulfilled,则步骤将成功。或者,如果您返回的内容不是 Promise,则步骤将立即被认为已成功。这包括返回 undefined 或 null。这意味着您可以在步骤执行期间选择是否需要返回 Promise,并且框架将根据需要进行调整。例如,使用 Promises 进行 HTTP API 调用的步骤定义可能如下所示。这是使用 Fetch API 编写的,因为它在响应上返回一个 Promise。

(以下内容与原文基本一致,略作调整以保持流畅性和可读性,并对部分语句进行同义词替换,并对部分章节进行合并和简化)

特性背景、场景大纲、数据表、钩子、事件和世界

这些高级特性,例如特性背景、场景大纲、数据表,以及钩子函数(Before, After, BeforeStep, AfterStep等)和事件处理机制,都能够极大地提高测试效率和可读性。通过合理运用这些功能,可以编写更简洁、更易维护的BDD测试。 World 对象允许在不同的步骤定义之间共享数据和状态,从而简化测试逻辑。

(以下内容与原文基本一致,略作调整以保持流畅性和可读性,并对部分语句进行同义词替换)

总结

行为驱动开发是一种确保产品具有正确行为的绝佳方法,而 Cucumber 作为一种工具,是一种非常强大的方法,可以实现这一点,以便产品的每个利益相关者都能阅读、理解甚至编写行为测试。本文只是触及了 Cucumber 能够实现的皮毛,因此我鼓励您自己尝试一下,以了解其强大功能。Cucumber 还拥有一个非常活跃的社区,他们的邮件列表和 Gitter 频道是寻求帮助的好方法,如果您需要的话。您是否已经在使用 Cucumber?本文是否鼓励您尝试一下?无论哪种方式,我都想在下面的评论中听到您的声音。本文由 Jani Hartikainen 进行了同行评审。感谢所有 SitePoint 的同行评审者,使 SitePoint 内容达到最佳状态!

(以下内容与原文基本一致,略作调整以保持流畅性和可读性,并对部分语句进行同义词替换)

关于使用 Cucumber 和 Gherkin 的 JavaScript 中 BDD 的常见问题 (FAQ)

(以下内容与原文基本一致,略作调整以保持流畅性和可读性,并对部分语句进行同义词替换)

以上是BDD在JavaScript:开始使用Cucumber和Gherkin的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

前端热敏纸小票打印遇到乱码问题怎么办? 前端热敏纸小票打印遇到乱码问题怎么办? Apr 04, 2025 pm 02:42 PM

前端热敏纸小票打印的常见问题与解决方案在前端开发中,小票打印是一个常见的需求。然而,很多开发者在实...

神秘的JavaScript:它的作用以及为什么重要 神秘的JavaScript:它的作用以及为什么重要 Apr 09, 2025 am 12:07 AM

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

谁得到更多的Python或JavaScript? 谁得到更多的Python或JavaScript? Apr 04, 2025 am 12:09 AM

Python和JavaScript开发者的薪资没有绝对的高低,具体取决于技能和行业需求。1.Python在数据科学和机器学习领域可能薪资更高。2.JavaScript在前端和全栈开发中需求大,薪资也可观。3.影响因素包括经验、地理位置、公司规模和特定技能。

JavaScript难以学习吗? JavaScript难以学习吗? Apr 03, 2025 am 12:20 AM

学习JavaScript不难,但有挑战。1)理解基础概念如变量、数据类型、函数等。2)掌握异步编程,通过事件循环实现。3)使用DOM操作和Promise处理异步请求。4)避免常见错误,使用调试技巧。5)优化性能,遵循最佳实践。

如何实现视差滚动和元素动画效果,像资生堂官网那样?
或者:
怎样才能像资生堂官网一样,实现页面滚动伴随的动画效果? 如何实现视差滚动和元素动画效果,像资生堂官网那样? 或者: 怎样才能像资生堂官网一样,实现页面滚动伴随的动画效果? Apr 04, 2025 pm 05:36 PM

实现视差滚动和元素动画效果的探讨本文将探讨如何实现类似资生堂官网(https://www.shiseido.co.jp/sb/wonderland/)中�...

JavaScript的演变:当前的趋势和未来前景 JavaScript的演变:当前的趋势和未来前景 Apr 10, 2025 am 09:33 AM

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

如何使用JavaScript将具有相同ID的数组元素合并到一个对象中? 如何使用JavaScript将具有相同ID的数组元素合并到一个对象中? Apr 04, 2025 pm 05:09 PM

如何在JavaScript中将具有相同ID的数组元素合并到一个对象中?在处理数据时,我们常常会遇到需要将具有相同ID�...

console.log输出结果差异:两次调用为何不同? console.log输出结果差异:两次调用为何不同? Apr 04, 2025 pm 05:12 PM

深入探讨console.log输出差异的根源本文将分析一段代码中console.log函数输出结果的差异,并解释其背后的原因。�...

See all articles