如何高效使用 Gherkin

背景

時間回到 2022 年,我參與了一個使用了 Flutter 技術構建的 Web 前端項目。在這個項目上,我們小組的目標是實施 Flutter 前端自動化測試。

彼時,Flutter 2.x 剛在 Web 端發力不久,Flutter Web 上的應用和生態纔剛剛開始,而在這一切激進的技術棧上構建一套自動化方案的需求又迫在眉睫。

在技術選型上,我們使用了類 Cucumber 測試的方案,使用 Gherkin 語言構建一套自動化語言步驟庫。Gherkin 語言有時候又被稱爲小黃瓜語言,它是第一種有着類似自然語言可讀性的業務語言,用來描述業務行爲,而不必關心具體的實現細節。它也是一種領域特定的語言,用來定義 Cucumber 格式的測試。

通過不斷地改進,我們使得這些步驟既具有自然語言通俗易懂的可讀的特性,又具有自動化步驟的可執行性,用這套步驟,我們最終用它統一了手工測試用例和自動化測試用例的書寫,執行,管理。

文檔式 Gherkin 和動作式 Gherkin 的區別

Gherkin 語言其實可以使用不同國家語言的單詞和語法書寫,但和其他編程語言一樣,我們這裏還是使用英文單詞和文法。

當定義步驟庫的時候,使用 Gherkin 語言去描述業務或者用例可以有不同的風格,典型的有 “文檔式 Gherkin” 和“動作式 Gherkin”這兩大類。

文檔式 Gherkin 往往用來描述 “應該做些什麼”。所以經常用來描述軟件需求,產品期望行爲。

比如一個步驟是:“當創建了一個新用戶的時候,那麼他會出現在新用戶列表中”。這種風格的好處就是可以快速書寫出一個結構合適,方便理解的軟件文檔。當這種文檔式 Gherkin 語言寫的測試執行失敗的時候,往往代表了產品的實際行爲和文檔上的期望行爲發生了背離。當然文檔式 Gherkin 語句也有其缺點,比如自動化實現起來,某些語句需要驗證的範圍會非常大,執行復雜,且隨着測試的增加,步驟庫裏的步驟數量也會快速地增長。

相比文檔式 Gherkin,動作式 Gherkin 描述的是 “如何做些什麼”,由於動作式 Gherkin 關注的是每一步具體做什麼,所以常用作寫測試用例。如一個步驟是 “當點擊含有 Submit 文字的按鈕,那麼 Successfully 文字應該可見”。

動作式 Gherkin 語句的好處就是目的單一,每一步需要驗證的點很小,當然與之而來的缺點就是要完成一個用例的書寫需要很多步驟組成,用例裏的步驟會很多,用例變得很長。且每一個動作對測試場景的覆蓋率都不高,需要完成測試覆蓋率要很多步驟一同拼湊起來。

文檔式 Gherkin 風格詳解

由於我們需要大量自動化測試用例,所以我們更傾向於使用動作式 Gherkin,雖然最終我們使用了動作式的 Gherkin 語言定義了自動化步驟庫,但我們還是先了解一下文檔式 Gherkin 的風格。

文檔式 Gherkin 使用描述性的措辭,聚合了具體的動作。這使讀者能夠快速理解一個場景,並掌握文檔中描述的軟件功能。使用文檔式 Gherkin 語言寫的軟件需求或者測試,並由自動化實現執行後,這個文檔基本上不會過時。因爲一旦軟件實際行爲和需求文檔的描述發生了背離,那麼自動化執行需求文檔上的 Gherkin 語句的時候,測試就會失敗。而這些測試往往都是以天爲粒度去執行的,如果今晚你提交了代碼改變軟件行爲而沒有更新需求文檔,當晚的自動化測試流水線就會失敗紅掉。如果你的項目有流水線” 紅不過夜”(導致流水線失敗變紅的問題不留到第二天而是當天內解決)的規定,那麼恭喜,今晚就必須把文檔上由 Gherkin 語言書寫的測試修正。

使用場景:書寫可測試的軟件需求說明書

例子:

When the admin creates a new user

Then the user list should contain the newly created user

規則 1:使用不大於 5 個步驟的 Scenario 場景

Scenario 是 Gherkin 語言中的關鍵字,通常代表一個場景。作爲一個經驗法則,一個文檔式 Gherkin 寫的場景通常由 3-5 個步驟組成。有時候,就連包含 Given 的步驟也不需要,那麼便只需要 2 個左右步驟了。當然,長的場景可能包含了多於 5 個 Gherkin 步驟,此時便可能說明了這個場景需要拆分了,讓更多更小的場景去覆蓋每一個需要關注的點。

同時,前置條件也是可以隱式說明的。比如,在描述產品行爲的時候,對於每個與登錄頁面本身無關的場景,可以預期用戶已經登錄了。每一個場景一般只應該包含一個 “Then”。

如果有多個包含 Then 的步驟,那麼就說明這個場景有多個 AC(Acceptance Criteria)。如果 Then 語句執行失敗,那麼此 AC 便校驗失敗了,測試便會停止,那麼後面的 AC 步驟也不會去執行驗證,於是在失敗的路徑下多個 AC 便失去了意義。當然這個規則也不是個教條,比如當兩個 AC 相互依賴,他們最好同時驗證。比如分開驗證兩個 AC 都時候時間成本,外部資源成本非常昂貴,那麼也是可以放在一起寫多個 Then 語句的。

規則 2:使用主動語態

當描述一些行爲的時候,應當使用主動的語態,比如 "the user does X" 而不是 "X is done by the user" 這種被動的語態。

規則 3:使用不同時態

在 Gherkin 中定義包含 Given 語句的時候,要使用過去時時態,因爲這表示測試之前發生的一個前置條件。如:“Given the user was logged-in” 。

在 Gherkin 中定義包含 When 語句的時候,要使用現在時時態,這代表測試執行的時候發生的,如:“When the user cancels the form”

在 Gherkin 中定義包含 Then 語句的時候,用情態動詞寫成期望例如:Then the form should be closed。這強調了我們不是預測 SUT 將如何表現,而是描述我們對它的期望。

規則 4:使用角色名稱

使用這些角色名稱:如 “Users”、“Admin”、“Guest”,而不是 "I"。這可以增加一個場景的重點,讓它專注於某一個角色,以便更容易閱讀理解。在後續步驟中,要麼重複角色名稱,要麼使用代詞 They 來代表這類角色。

例如:

When the Admin starts the creation of a user

Then the Admin should be asked to confirm the creation

又如:

When the Admin starts the creation of a user

Then they should be asked to confirm the creation

規則 5:使用大寫的否定詞

否定詞會大大改變句子的意思,但很容易被忽視。把否定詞寫成大寫字母,便可以強調它們。

例如:

“The the text “Welcome” should NOT be visible” 或 “The user should NOT exists”

符合受衆需求的產出物

作爲技術人員,往往具有很強的工程師思維慣性,產出物也是有鮮明的技術標籤。所以從業務的視角來看,並不是那麼對用戶友好。我們定義的第一版步驟庫便是如此。比如 Flutter 項目中所有的對象都可以加上類似於 id 的 key 屬性,用來查找這個唯一的對象元素,如果在步驟中要用這個屬性來尋找對象,那麼步驟變成了類似:

When the element with key “userNameTextField” is filled with text “john@gmail.com”

這麼定義出來的步驟可能有如下問題:

  1. 具有很強的編碼能力的 Dev/QA 可能更傾向於直接使用程序語言來書寫測試加快執行,那麼這套 Ghkerin 庫會被棄置。

  2. 沒有代碼倉庫訪問權限的 QA 或者 BA 無法通過訪問代碼查看具體某個對象的 Key 是多少,那麼這套 Gherkin 庫他 / 她無法使用。

作爲業務人員,更希望在步驟中隱藏所有技術細節,方便使用。所以我們做了如下改進:

  1. 隱藏所有的 Key 細節,儘可能使用元素可見的屬性,或者目標元素和一個可見元素的相對關係來定位元素,如按鈕的文字,下拉選項上面的 Label 文字,文字輸入框的 ToolTip,表格的標題等等。

  2. 如果一個對象沒有可見的屬性必須使用 Key 定位的話,我們將駝峯式變量名的 Key 映射到友好的自然語言功能名稱上,同時維護一個 Wiki 文檔,這樣業務使用人員可以方便地查找和使用這個步驟。

於是,上面的步驟就變成了:

When the text field “user name” is filled with “john@gmail.com”

動作式 Gherkin 風格詳解

我們使用動作式 Gherkin 定義了用例,通過總結,有如下經驗。動作式 Gherkin 語句用每一個參數化的步驟描述一個行爲,這種風格使得步驟庫的體積不必非常大。因此也減少了自動化框架步驟的開發和維護工作量,每一個步驟儘量和公共組件進行互動,也保證了每個步驟的重用性非常之高,所以,一旦需要新測試,用現有的動作式步驟庫書寫後這個 case 就可以立即運行了,也不需要實現一個新的步驟。

使用場景:寫測試用例

例子:

When the button 'Create User' is clicked

And the text field 'Last name' is filled with 'Jim'

And the text field 'First name' is filled with 'Green'

And the button 'Save' is clicked

Then the 'user list' should contain the text 'Jim, Green'

規則 1:使用單一測試點的 Scenario 場景

由於動作式 Gherkin 不可避免要使用更多的步驟,所以動作式 Gherkin 的測試長度一般都會更長一些,但是這並不代表一個測試可以寫很長很大。同樣地,一個測試還是需要遵循單一原則,最好覆蓋一個測試點,在覆蓋這個測試點的過程中,儘可能減少測試步驟讓這個測試簡短精悍,方便維護。

規則 2:使用被動語態

由於動作式 Gherkin 是對 UI 對象的操作,爲了方便閱讀,加強對互動的 UI 元素的關注所以一般是 "X is clicked" 這種被動的語態。

例子:

When the text field 'Last name' is filled with 'CAO'

反例:

When fill 'CAO' into the text field 'Last name'

規則 3:使用不同時態

和文檔式 Gherkin 一樣,使用不同的時態來區分先決條件、行爲、期望。

Given 步驟用過去時時態,例如:Given user was logged-in.

When 步驟用現在時來描述動作, 例如:When button 'Login' is clicked.

Then 步驟用情態動詞描述期望,Then the text 'Welcome' should be visible.

規則 4:專注於 UI 元素

動作式 Gherkin 步驟專注於和 UI 界面互動,所以儘可能隱藏用戶角色信息,一般來說,在 Given 步驟中給定了一個用戶角色即可,而之後,便不在語句中強調用戶角色,把重點放在用戶界面元素上,這樣可以縮短自然語句中的措辭,突出用戶界面,這是動作式 Gherkin 語句最關注的部分。

比如:

例子:

Given the 'Admin' was logged-in

When the button 'Delete user' is clicked

反例:

Given the 'Admin' was logged-in

When the 'Admin' clicks the 'Delete user' button

規則 5:使用可見的 UI 屬性

UI 元素有不同的屬性,一些屬性是可見的,這樣方便用戶區別他們,比如一個按鈕可以 text,class,id 來查找到,爲了讓對象快速被人識別,那麼便應該使用人類可見的屬性來識別這些自動化 UI 對象,那麼對於這個按鈕就應該用按鈕的文本來識別,這樣便建立了測試語句和軟件 UI 對象視覺上的強有力的聯繫。

例子:

When the button '' is clicked

反例:

When the button with id '' is clicked

有時候,一些對象沒有可視的屬性,有時候一些對象是其他對象的分組,或者其他對象的描述,比如一個區域,一個層,這時候,便可以使用 id。此時,便需要開發團隊給特定的元素添加 id 來支持自動化測試。

例子:

Then the widget with the id 'header' should contain text 'today’s announcement'

但是要注意的是,這套步驟庫的受衆是誰,如果是非技術性用戶,那麼儘可能隱藏掉 id 這種技術細節。

例子:

Then the element 'homepage header' should contain text 'today’s announcement'

規則 6:前置條件中隱式假設的使用

例子 1:

Given 'admin' was logged-in

And the dashboard page is visible

例子 2:

Given the dashboard page is visible

我們可以假設,當 dashboard 可見的時候,管理員必須要登錄,那麼文檔式 Gherkin 使用例 2 便可以聚焦到 dashboard 相關的信息了。然而,在使用代碼實現步驟的時候,將多個動作聚集到一個步驟的定義中,會大大降低一個步驟的可重用性,一個複雜的動作不能像原子動作那樣與其他步驟結合。一旦這些步驟鏈中任何一個地方改變,那麼整個步驟就要修改維護。

但另一方面,如果一個場景可以將一些步驟聚合在一起,那麼便可以大大提高這個場景的可讀性,忽略無關信息,如:Given owner exists. 這步其實聚合一系列創建用戶的動作,一句話就表達了整個意思。所以編寫方案時,需要在這兩種需求之間找到一個平衡。

統一手工測試和自動化測試

文檔式 Gherkin 和動作式 Gherkin 都有它們的適用之處,在寫軟件需求或者測試時候選擇最合適的即可。遵守以上法則,會讓定義出來的 Gherkin 語言符合語言習俗,讓英語測試工程師更快速地使用這套步驟快速建立文檔和用例,也讓自動化框架開發工程師更方便地維護和對接步驟庫的使用者。

在提供了基於動作式 Gherkin 的步驟庫後,通過不斷地反饋和優化,我們隱藏了對象的 ID 細節,提供了友好的元素定位方式,以及方便記憶的對象命名庫,客戶的 QA 終於可以方便地使用我們的步驟庫來書寫測試用例了。由於 Gherkin 步驟本身就是以英語自然語言來書寫的,所以它也就自然而然可以用來書寫手工測試用例了。一套用例,測試工程師可以看着通俗易懂的 Gherkin 語言來手動執行用例,Flutter 上的類 Cucumber 自動化框架也可以用自動化執行用例出具報告。這樣一來,傳統的 “手動測試維護一套手工用例,自動化測試再維護一套從手工測試轉化成的自動化用例” 的工作流不再存在。終於可以大大減少用例的維護和執行開銷了。


本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/bNr76eNUmoDzhyEnbl2xIA