如今無論大公司還是小公司都越來越重視測試質量。並且前端領域越來越繁榮,前端工程也越來越複雜,純靠人力手工測試已經顯得有些力不從心並且更容易出錯。因此在項目中引入 BDD 理念進行自動化 UI 測試,讓項目質量可以通過自動化工具來保障也被提上日程。本文將介紹攜程度假團隊是如何將其付諸實踐,希望能給大家帶來一些啓發。

一、UI 自動化測試背景以及意義

在日常開發中,我們的程序出現 Bug 是一件非常正常的事情。Bug 本身並不可怕,可怕的是我們把 Bug 帶到真正的生產環境中。爲了減少 Bug 被帶上生產環境的可能性,我們已經做了許多:從代碼提交後 GitLab CI 自動執行單元測試並進行 Sonar 代碼質量掃描,再交付測試同學人工測試,最後灰度發佈上線。這一系列的流程已經很好地幫助我們降低了 Bug 被帶上生產的概率了。

作爲前端開發的我們來說,已經用上了諸如:TypeScript,EsLint 等現代化開發工具來提升代碼的質量。這些工具或框架可以把一些問題在開發階段暴露出來,但是這還遠遠不夠。那麼我們的前端工程是不是也可以使用自動化測試來幫助我們提升項目質量呢 ?

說到自動化測試,其實在後端領域是非常普遍的(主要是單元測試和 API 測試),但是在前端領域卻應用的非常少 (UI 自動化測試)。按照軟件工程自底而上的概念,前端測試一般分爲單元測試(Unit Testing)、集成測試(Integration Testing)和端到端測試(E2E Testing)。

從下面這張圖可以看出:從下往上測試的複雜度(成本)將不斷提高,另一方面測試的收益反而不斷降低。從運行測試速度上來看,三種測試的運行速度是呈倒金字塔結構。即單元測試運行得最快,開發成本也是最低的。隨後是服務測試,最後是 UI 自動化測試。

隨着我們的業務高速迭代,技術不斷革新,我們的系統也變得越來越複雜,需要高質量的代碼設計以及高質量的代碼實現去支撐。相信大家在實際工作中絕大多數遇到的是這樣的場景:遇到比較大的項目,這些項目由於種種原因,前人留下了各種坑。 歷史代碼質量非常糟糕,可能修改一個小點,卻產生了一個影響主流程的毀滅性 Bug。

這也是爲什麼,很多小夥伴發現之前遺留的代碼寫的非常糟糕,只要能跑,便不會主動去重構它的原因。主要是擔心重構後引起新的問題,同時也會加大測試的工作量。即便,你投入了大量的時間和精力進行了重構,可能未必得到比之前更好的效果,甚至可能由於業務的調整,辛苦重構的代碼直接要被廢棄了。遇到這種情況,不僅開發重構的成本是非常高的,而且測試人員對發佈的信心也是不足的。

因此,我們需要引入 UI 自動化測試,針對系統的核心業務流程進行自動化測試用例的編寫。當我們的代碼進行了修改甚至重構,我們的自動化測試就會一次次的去運行,如果通過了,證明我們新修改的代碼沒有影響到主流程,如果失敗了,那我們也可以第一時間發現問題,去修復我們的代碼。

總結如下:

  • UI 自動化測試在測試金字塔模型中處在頂層
  • UI 自動化測試實現起來難度大成本高
  • UI 自動化測試能有效增加開發與測試人員的信心

二、BDD UI 自動化測試理念

在說 BDD-UI-Testing 之前,我們先來看看 TDD、ATDD、BDD、DDD 這 4 個開發模式。

  • TDD:測試驅動開發(Test-Driven Development)
  • ATDD:驗收測試驅動開發(Acceptance Test Driven Development)
  • BDD:行爲驅動開發(Behavior Driven Development)
  • DDD:領域驅動開發(Domain Drive Design)

在我們日常工作中比較常見的其實是 TDD & ATDD 。即:我們在開發真正的代碼前會開各種需求評審,技術評審,測試用例評審等會議。業務人員、產品經理、開發人員、測試人員會充分溝通,以確保需求被充分記錄。在編寫真正實現功能的代碼之前會先要求測試人員提供測試用例。這種開發模式主要思想是:在正式編寫需求功能的代碼之前,先編寫單元測試代碼,再編寫需求功能代碼滿足這些單元測試代碼。

接下來我們來看看,我們日常開發項目時候的傳統開發流程(W 模型):

在 W 模型中,每一份項目文檔(PRD),都對應着一份測試文檔(測試用例)。

那麼我們再來看看 BDD 流程是怎麼樣的:

採用 BDD 流程進行開發,由外而內,持續地描述當前系統或模塊的行爲,併爲之實現自動化(即步驟定義)。當產品代碼部分完成後,右側的一系列測試活動都已經自動化了。

從層次上來說,BDD 是基於 TDD 的,或者說在自動化測試中,TDD 所在的位置比較底層,是基礎,而 BDD 則是它的演進版本。

BDD 核心的是,開發人員、QA、非技術人員和用戶都參與到項目的開發中,彼此協作。BDD 強調從用戶的需求出發,最終的系統和用戶的需求一致。BDD 驗證代碼是否真正符合用戶需求,因此 BDD 是從一個較高的視角來對驗證系統是否和用戶需求相符。

看到這裏,大家肯能會對上面的理論知識有點蒙圈。那麼讓我們來看下 BDD 的交互過程:

看到這裏,我們可以來總結一下:

  • BDD 是一種敏捷軟件開發的技術
  • BDD 提供了一種通用的,簡單的,結構化的描述語言
  • BDD 一般是黑盒測試,側重 UI,TDD 一般是白盒測試,側重代碼
  • BDD 一般採用集成測試,TDD 一般採用單元測試
  • BDD 不只是自動化測試

三、我們的 BDD-UI-Testing 實踐模式

上面說了這麼多大家可能並沒有什麼實際的感覺,接下來我就直接放個 BDD-UI-Testing 測試用例。

最終我們將得到類似如下的自動化測試報告:

(截圖中相關信息非真實數據)

看到這裏相信大家一定很疑惑,這一句句的命令描述怎麼就成爲了自動化腳本了呢?這又是如何運行起來還能出現報告和截圖的呢?

在解釋這之前,我要先給大家演示一個樸素的 BDD-UI-Testing 自動化用例。

我們使用一個大家都很熟悉的 ToDoList APP 來帶大家進入 BDD-UI-Testing。

BDD 測試是模擬用戶行爲的測試,而用戶的操作又是連貫的,因此這裏我們不能單純的測試一個組件是否能正常運行,而是要測試整體。

1)用戶打開 TODO App 頁面

2)用戶在輸入框內輸入 BDD-UI-Testing

3)用戶按下回車

4)TODO List 顯示 BDD-UI-Testing,並且輸入框被清空。

那我們的 BDD 測試該如何去實現呢?請看下圖:

如圖所示,大家就看到了一個樸素的 BDD 測試用例,但是現在還算不上自動化。爲什麼呢?細心的朋友已經發現了,模擬用戶的第一步,打開瀏覽器竟然沒有,並且操作也不是在瀏覽器裏點點點的。

目前的測試用例,我們是使用 Jest + Enzyme 像爬蟲一樣解析頁面,找到 DOM 並進行斷言的。雖然用了自然語言去描述我們的測試用例了,但是還要編寫 JS 代碼,這還有一定的學習成本。這對我們的測試同學來說,就是阻礙他們用上自動化測試的絆腳石。

那有沒有辦法能直接使用自然語言編寫,讓我們的測試不寫一行代碼,進一步降低自動化學習成本,並且還能打開真正的瀏覽器,去模擬用戶“點點點“的行爲呢?

答案自然是:有的!

3.1 框架選型:Cucumber + Puppeteer = @ctrip/cucumber_web_common

我們的目標是:自然語言編寫,行爲驅動自動化腳本。讓測試一目瞭然,高效開發測試腳本。

因此,我們選用了 Cucumber.js 作爲 BDD 測試框架,Puppeteer 來操縱瀏覽器模擬用戶行爲。

Cucumber 使用了一種叫 Gherkin 的劇本語法,支持多種自然語言來描述測試用例。

  • 一種 DSL(特定領域的語言)
  • 業務人員也可以讀懂
  • 可以用來描述軟件行爲
  • 支持多語言

Cucumber 項目結構大致是這樣的:

1)Feature 文件(劇本文件)

2)Step Definitons (步驟定義)

3)Support Code (支持代碼)

4)Cucumber Command(測試套件)

Feature 文件(劇本文件)

測試項的目運行文件都在 features 目錄下,以 .feature 結尾的爲劇本文件,一個劇本文件中可以包含多個場景,一個場景包含多個操作步驟。

以下是一個簡單的 /features/trip.feature 文件:

Step Definitons (步驟定義)

.feature 文件中描述的業務步驟要運行起來,需要根據業務場景定義操作行爲。具體的業務行爲是由相對應的自動化腳本來實現。

這部分自動化實現腳本(代碼)主要定義在 step_definitions 目錄下。

以下是一個僞代碼實現的 /step_definitions/myStep.js 文件:

Support Code (支持代碼)

自動化腳本在執行的過程中,比如上文中提到的 browser,作爲瀏覽器的驅動,需要抽象出來,單獨放在 support 目錄下。這裏還可以爲統一爲操作步驟定義超時時間,編寫場景執行前後觸發的函數等。

Cucumber Command(CLI 與 測試套件)

上面幾個步驟結合起來就是一個簡單的自動化測試用例。

其中步驟定義中的基礎代碼是 JavaScript,而自動化庫使用 Puppeteer Node 庫。

想要運行這個 BDD 測試用例,則需要用到 Cucumber-CLI 提供的一些命令。

  • 運行匹配到的自動化用例

    複製代碼

    $ cucumber-jsfeatures/**/*.feature
    
  • 運行某個目錄下的自動化用例

    複製代碼

    $ cucumber-js features/dir
    
  • 運行某個自動化用例

    複製代碼

    $ cucumber-jsfeatures/trip.feature
    
  • 運行自動化用例的指定行

    複製代碼

    $ cucumber-jsfeatures/trip.feature:3
    
  • Specify a scenario by its name matching a regular expression

    複製代碼

    $cucumber-js--name"trip 1"
    

當然,這些 CLI 命令可能不夠友好,大家更喜歡使用 GUI ,我們也推薦使用 CukeTest 這款測試軟件,來編寫測試用例,以及使用 GUI 按鈕來運行測試用例。

可視化模式下的測試用例:

文本模式下的測試用例:

Feature:測試 Trip.com 搜索

打開 Trip.com , 輸入目的地,點搜索,搜索結果應該包含目的地

Scenario:Trip.com 搜索

Given 瀏覽器導航到 "trip.com"

Then 在目的地輸入框內輸入 " 上海 "

Then 點擊 " 搜索 "

And 驗證搜索列表頁內包含 " 上海 "

關於 Puppeteer

前面介紹了 Cucumber 這款 BDD 自動化測試工具,大家可以簡單的理解爲:

  • Cucumber 定義了一種 DSL(領域特定語言)
  • Cucumber 可以用自然語言描述測試步驟(非技術人員也能看懂測試用例)
  • Cucumber 幫我們控制流程並執行相關邏輯
  • Cucumber 並不負責驅動瀏覽器,操作瀏覽器的事情交給 Puppeteer

所以 Puppeteer 到底是何方神聖呢 ?

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.

用官方的話解釋:Puppeteer 是一個 Node 庫,它提供了高級的 API 並通過 DevTools 協議來控制 Chrome 或 Chromium 。通俗來說就是一個 Headless Chrome 瀏覽器(也可以配置成有 UI 界面的,默認啓動的是一個沒有界面的)。

Puppeteer 的結構圖如下所示:

簡單的來說:Puppeteer 使用 DevTools 協議與瀏覽器進行通信並操縱他們。

這裏我們直接舉一個簡單例子:

我們使用 Puppeteer 打開無頭瀏覽器,訪問 https://hk.trip.com/ 並截圖。

效果如下圖所示,僅需要 6 行代碼,我們就實現了訪問頁面 + 獲取性能數據 + 截圖。

相信大家看完這個例子,對於前面的 Cucumber Feature 中寫的 Given 瀏覽器導航到 “trip.com” 應該知道如何去實現了吧。

當然對於 Puppeteer 能做的遠遠不止這些,這裏列舉一些 Puppeteer 可以應用的場景:

  • Puppeteer 可以作爲高級爬蟲使用
  • SEO 優化(抓取 SPA 單頁應用,並生成相應的預渲染內容返回)
  • UI 自動化測試
  • 頁面性能測試與分析(捕獲網站的 timeline trace 進行數據分析)
  • 前端監控系統(定時訪問頁面,抓取相關信息,檢查是否有白屏報錯等)

我們是如何組合使用,並封裝成框架的呢?

回到我們的原始需求上:降低自動化測試門檻

  • 測試人員不用或很少去寫代碼
  • 非技術人員也可以讀懂測試用例

這就需要我們把 Cucumber 和 Puppeteer 進行封裝組合使用。

  • 使用 Cucumber 寫的測試用例(自然語言)可以認爲是 DSL
  • 在代碼中對該 DSL 進行解析,映射成具體 JS 代碼
  • Puppeteer 負責執行具體命令(如:打開瀏覽器、點擊某按鈕)
  • 封裝通用的步驟命令,只需要組合,使用者不需要關心具體實現

現在我們來回看下面這張圖:

1)我們在 Cucumber Hooks 中定義了相關鉤子函數,把打開瀏覽器等每個測試用例需要做的通用工作給做了;

2)在 Cucumber Word 對象上掛載了瀏覽器和頁面的實例。這樣我們就可以通過 this.browser 和 this.page 在我們的步驟定義中方便的操控 Chrome 了;

3)封裝相關通用步驟,如:Given 打開 "xxx" 頁面,Then 截圖;

如何識別打開 "xxx" 頁面 ,點擊 "xxx" 按鈕 ?

看完了上面的介紹,大家已經明白如何打開瀏覽器,並訪問一個頁面了,也能大概知道如何使用 Puppeteer 去模擬點擊了。

但是我們寫的打開 " 跟團遊 " 頁面和點擊 " 去預定 " 按鈕中的 “跟團遊” 和 “去預定” 又是如何識別的?

其實答案非常簡單:我們去編寫一個配置文件去映射相關 URL 或元素。

DOM 元素選擇器配置是按照頁面維度來的:

如何查找元素的問題是解決了,但是不知道大家看到這裏的時候有沒有發現一個問題。

現在我們使用現代化前端開發框架進行開發,例如 React,因此我們可能不再需要 jQuery 時代一樣在元素上加上 id=“name” ,但是這就導致我們元素的 CSS 選擇器 有時候又長又臭。例如:’#root > div > div > section > main > div.relative > div > button.ant-btn.search.ant-btn-primary’。

並且有時候我們的 id 會被埋點佔用,並且是動態生成的,例如: ‘#order-10650004336-filter-order-18 > span’。並且隨着版本的迭代,元素的 DOM 結構可能有所變化。

這就導致我們使用 className 或 Xpath 選擇的元素並不靠譜。

可能由於一個小小的改動,導致按鈕點不到,導致整個 Case 失敗。

增加 test-id,保證選擇器的可靠性

由於普通的 Class 選擇器等並不靠譜,我們需要開發在寫代碼時加入穩定的 data-test-id 自定義屬性作爲我們自動化測試埋點,有了這些我們的自動化用例就不會因爲 DOM 結構的頻繁修改而導致選擇不到相關元素。如下圖:

修改爲:使用 test-id 作爲選擇器後,我們也大大增加了可維護性,並把這些作爲自動化測試用例“資產”的一部分。

至此爲止,我們的自動化框架已經搭建完成,接下來我們進行技術總結:

  • Cucumber 負責翻譯自然語言(DSL)
  • Cucumber 負責控制流程,執行相關邏輯
  • Puppeteer 負責驅動瀏覽器,模擬用戶的操作
  • DOM 元素需要加上 test-id 以供自動化測試使用
  • 提供自動化測試核心框架 @ctrip/cucumber_web_common ,發佈公司 NPM 倉庫
  • 提供詳細的文檔,以供大家查閱步驟如何使用
  • 提供自動化項目模板,以供大家快速搭建一個自動化測試項目
  • 提供的公共步驟可以覆蓋 80% 的通用場景
  • 對於某些複雜的步驟則可能需要自己編寫代碼去實現
  • 對接登錄團隊解決自動化測試登錄出現風控的問題等

如何持續集成(DevOps)?

爲什麼當前我們需要 DevOps,甚至很多大型的互聯網公司也在進行 DevOps 轉型,其中最關鍵是因爲其核心思想能夠滿足當前業務和技術變革的需要,那就是“快速的交付價值,靈活的響應變化”。“快速的交付價值”意味着能先人一步佔領市場,“靈活的響應變化”亦意味着減少變化帶來的不利因素,使企業立於不敗之地。

爲什麼要 DevOps ,以及想了解更多有關 DevOps 的實踐,請查看《 攜程酒店 DevOps 測試實踐 》。

迴歸到我們的 BDD-UI-Testing:自動化測試框架有了,測試用例也有了,那我們只是在自己的開發機上跑跑嗎?

對於持續集成這塊,我們接入了 Ctrip DevOps 流程,使用 GitLab CI 和 Ctrip PaaS 。基本流程如下:

開發側流程:

  • 業務代碼變更,提交到 GitLab
  • 觸發 GitLab CI 進行代碼質量掃描檢測
  • 觸發 Ctrip PaaS CD 自動進行發佈(或手動發佈)
  • 測試環境發佈完成後,PaaS 平臺的 WebHooks 通知我們發佈結果,並寫入消息隊列中
  • 消息推送到我們自動化測試代碼 GitLab 倉庫,觸發 GitLab CI 進行 BDD-UI-Testing
  • 測試數據落地,自動發送測試報告郵件,生成相關測試報告並上傳測試平臺

測試側流程:

  • 測試用例變更,提交到 GitLab
  • 觸發 GitLab CI 進行代碼質量掃描檢
  • 觸發 GitLab CI 進行 BDD-UI-Testing
  • 測試數據落地,自動發送測試報告郵件,生成相關測試報告並上傳測試平臺

平臺側流程:

  • 用戶在測試平臺點擊運行測試用例
  • 調用 API 並寫入消息隊列
  • 消息推送到我們自動化測試代碼 GitLab 倉庫,觸發 GitLab CI 進行 BDD-UI-Testing
  • 測試數據落地,自動發送測試報告郵件,生成相關測試報告並回傳至測試平臺

在 GitLab CI 上使用並行模式,加快測試速度(充分榨乾服務器性能)

參考 Cucumber-CLI 文檔

  • 我們可以使用 --parallel <NUMBER_OF_SLAVES> 來指定並行數量

  • 或在 GitLab CI 環境變量中設置

    CUCUMBER_PARALLEL=true 啓用並行模式

    CUCUMBER_TOTAL_SLAVES=10 使用 10 個進程

    CUCUMBER_SLAVE_ID=0 ID for slave (‘0’, ‘1’, ‘2’, etc.)

實測:在並行 10 個進程的模式下,中型項目可以在 2 分 30 秒內測試完成。

四、小結與展望

本文簡單的介紹了攜程度假團隊是如何將 BDD-UI-Testing 付諸實踐的。使用 Cucumber 作爲 BDD 自動化測試工具,使用 Puppeteer 來操控瀏覽器,使用 GitLab CI 對自動化測試持續集成。

通過本文我們也瞭解瞭如何搭建一個 BDD UI 自動化測試框架並加入 DevOps 流程。希望本文能給大家帶來一些啓發和收穫。

最後是一點小小的提醒:

  • UI 自動化測試達到 70%-80% 的通過率很容易
  • 但是想把通過率提升到 90%-100% 卻很難
  • 每上升 1% 的通過率,我們可能都要爲此付出巨大的代價
  • 因此我們不能盲目追求高通過率,需要思考這是否值得

目前,我們實施的 BDD-UI-Testing 還處於初期階段,很多方面尚未完全達到預期。對於自動化測試我們還有很多的工作需要去做:

  • 加入 AI 圖像對比,對比修改後的代碼是否對頁面產生了不可預期的影響
  • 需找更好的 Mock 數據方案(本地 Mock 數據 和 Mock 平臺返回固定的數據都不夠靈活)

五、大家關心的問題

5.1 爲什麼使用 Puppeteer 而不使用 Selenium ?

  • Puppeteer 是由 Google 官方團隊出品,技術較新前景更好,與 Chrome 有更好的兼容性
  • Puppeteer 更好的支持 SPA(Single-Page Application)頁面的測試
  • 單一語言,我們的 BDD 框架挑選了 Cucumber.js 並且 Puppeteer 也是使用 JavaScript 編寫的 Node.js 庫 。因此這二者可以更好的結合,並且更加方便在瀏覽器中調試。
  • 更簡單的攔截網絡請求(可以更加方便的 Mock 接口等)

5.2 我可不可以使用 Selenium ?

當然可以!甚至你可以不使用 JavaScript 來編寫。Cucumber 這款 BDD 自動化測試框架支持多種編程語言,你可以挑選任意你喜歡的語言去與 Selenium 進行組合。

https://cucumber.io/docs/installation/

5.3 BDD-UI-Testing 只適用 Web 端嗎 ?

並不是這樣的,在 APP 端 (Native 或 CRN)我們通用可以使用同一套命令,使用 Cucumber 結合 AirTest 進行 APP 側的 BDD 自動化測試。甚至我們可以使用 Native 提供的 Bridge 進行命令封裝,達到操控真機的目的。

對於 RN 項目我們也可以使用 RN 轉 RN Web 的辦法,用 Cucumber + Puppeteer 來測試我們業務的核心流程。

作者介紹:

Leo Li,攜程高級軟件工程師,負責度假 BDD-Test UI 自動化測試框架的研發、維護和迭代等工作。

本文轉載自公衆號攜程技術(ID:ctriptech)。

原文鏈接:

https://mp.weixin.qq.com/s?__biz=MjM5MDI3MjA5MQ==&mid=2697270030&idx=1&sn=6be0a2c1a1671e66db89a725ad6af4ce&chksm=8376ec3ab401652cb8c3e21fdb0a3c70825af2820233be19b7e218be09c84394db5ac385ea6e&scene=27#wechat_redirect

相關文章