測試驅動開發 (TDD) 的好處已廣為人知,它能提升產品質量和開發效率。每次編寫代碼測試時,都能確保代碼的正確性,並能及時發現未來可能出現的代碼錯誤。
行為驅動開發 (BDD) 在此基礎上更進一步,它測試的是產品的行為,而非僅僅是代碼,確保產品行為符合預期。本文將介紹如何使用 Cucumber 框架編寫 BDD 風格的自動化驗收測試。 Cucumber 的優勢在於,測試用例可以用簡潔的自然語言編寫,方便項目中非技術人員理解。閱讀本文後,您可以判斷 Cucumber 是否適合您的團隊,並開始編寫自己的驗收測試。準備好了嗎?讓我們開始吧!
關鍵要點
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 文件都包含一個特性,每個特性都包含一個或多個場景。場景然後由步驟組成,這些步驟按特定順序排列:
理想情況下,每個場景都應該是一個單獨的測試用例,因此 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中文網其他相關文章!