測試你的前端程式碼 – part3(端到端測試)

發表於2017-06-05

上一篇文章《測試你的前端程式碼 – part2(單元測試)》中,我介紹了關於單元測試的基本知識,從本文介紹端到端測試(E2E 測試)。

端到端測試

第二部分中,我們使用 Mocha 測試了應用中最核心的邏輯,calculator 模組。本文中我們將使用端到端測試整個應用,實際上是模擬了使用者所有可能的操作進行測試。

在我們的例子中,計算器展示出來的前端即為整個應用,因為沒有後端。所以端到端測試就是說直接在瀏覽器中執行應用,通過鍵盤做一系列計算操作,且保證所展示的輸出結果都是正確的。

是否需要像單元測試那樣,測試各種組合呢?並不是,我們已經在單元測試中測試過了,端到端測試不是檢查某個單元是否 ok,而是把它們放到一起,檢查還是否能夠正確執行。

需要多少端到端測試

首先給出結論:端到端測試不需要太多。

第一個原因,如果已經通過了單元測試和整合測試,那麼可能已經把所有的模組都測試過了。那麼端到端測試的作用就是把所有的單元測試綁到一起進行測試,所以不需要很多端到端測試。

第二個原因,這類測試一般都很慢。如果像單元測試那樣有幾百個端到端測試,那執行測試將會非常慢,這就違背了一個很重要的測試原則——測試迅速反饋結果。

第三個原因,端到端測試的結果有時候會出現 flaky 的情況。Flaky 測試是指通常情況下可以測試通過,但是偶爾會出現測試失敗的情況,也就是不穩定測試。單元測試幾乎不會出現不穩定的情況,因為單元測試通常是簡單輸入,簡單輸出。一旦測試涉及到了 I/O,那麼不穩定測試可能就出現了。那可以減少不穩定測試嗎?答案是肯定的,可以把不穩定測試出現的頻率減少到可以接受的程度。那能夠徹底消除不穩定測試嗎?也許可以,但是我到現在還沒見到過[笑著哭]。

所以為了減少我們測試中的不穩定因素,儘量減少端到端測試。10 個以內的端到端測試,每個都測試應用的主要工作流。

寫端到端測試程式碼

好了,廢話不多說,開始介紹寫端到端程式碼。首先需要準備好兩件事情:1. 一個瀏覽器;2. 執行前端程式碼的伺服器。

因為要使用 Mocha 進行端到端測試,就和之前單元測試一樣,需要先對瀏覽器和 web 伺服器進行一些配置。使用 Mocha 的 before 鉤子設定初始化狀態,使用 after 鉤子清理測試後狀態。before 和 after 鉤子分別在測試的開始和結束時執行。

下面一起來看下 web 伺服器的設定。

設定 Web 伺服器

配置一個 Node Web 伺服器,首先想到的就是 express 了,話不多說,直接上程式碼

程式碼中,before 鉤子中建立一個 express 應用,指向 dist 資料夾,並且監聽 8080 埠,結束的時候在 after 鉤子中關閉伺服器。

dist 資料夾是什麼?是我們打包 JS 檔案的地方(使用 Webpack打包),HTML 檔案,CSS 檔案也都在這裡。可以看一下 package.json 的程式碼:

對於端到端測試,要記得在執行 npm test 之前,先執行 npm run build。其實這樣很不方便,想一下之前的單元測試,不需要做這麼複雜的操作,就是因為它可以直接在 node 環境下執行,既不用轉譯,也不用打包。

出於完整性考慮,看一下 webpack.config.js 檔案,它是用來告訴 webpack 怎樣處理打包:

上面的程式碼指的是,Webpack 會讀取 app.js 檔案,然後將 dist 資料夾中所有用到的檔案都打包到 bundle.js 中。dist 資料夾會同時應用在生產環境和端到端測試環境。這裡要注意一個很重要的事情,端到端測試的執行環境要儘量和生產環境保持一致。

設定瀏覽器

現在我們已經設定完了後端,應用已經有了伺服器提供服務了,現在要在瀏覽器中執行我們的計算器應用。用什麼包來驅動自動執行程式呢,我經常使用 selenium-webdriver,這是一個很流行的包。

首先看一下如何使用驅動:

before 中,準備好驅動,在 after 中把它清理掉。準備好驅動後,會自動執行瀏覽器(Chrome,稍後會看到),清理掉以後會關閉瀏覽器。這裡注意,準備驅動的過程是非同步的,返回一個 promise,所以我們使用 async/await 功能來使程式碼看起來更美觀(Node7.7,第一個本地支援 async/await 的版本)。

最後在測試函式中,傳遞網址:http:/localhost:8080,還是使用 await,讓 driver.get 成為非同步函式。

你是否有好奇 prepareDrivercleanupDriver 函式長什麼樣呢?一起來看下:

可以看到,上面這段程式碼很笨重,而且只能在 Unix 系統上執行。理論上,你可以不用看懂,直接複製/貼上到你的測試程式碼中就可以了,這裡我還是深入講一下。

前兩行引入了 webdriver 和我們使用的瀏覽器驅動 chromedriver。Selenium Webdriver 的工作原理是通過 API(第一行中引入的 selenium-webdriver)呼叫瀏覽器,這依賴於被調瀏覽器的驅動。本例中被調瀏覽器驅動是 chromedriver,在第二行引入。

chrome driver 不需要在機器上裝了 Chrome,實際上在你執行 npm install 的時候,已經裝了它自帶的可執行 Chrome 程式。接下來 chromedriver 的目錄名需要新增進環境變數中,見程式碼中的第 9 行,在清理的時候再把它刪掉,見程式碼中第 22 行。

設定了瀏覽器驅動以後,我們來設定 web driver,見程式碼的 11 – 15 行。因為 build 函式是非同步的,所以它也使用 await。到現在為止,驅動部分就已經設定完畢了。

測試吧!

設定完驅動以後,該看一下測試的程式碼了。完整的測試程式碼在這裡,下面列出部分程式碼:

這裡的程式碼呼叫計算器應用,檢查應用標題是不是 “Calculator”。程式碼中第 6 行,給瀏覽器賦地址:http://localhost:8080,記得要使用 await。再看第 9 行,呼叫瀏覽器並且返回瀏覽器的標題,在第 10 行中與預期的標題進行比較。

這裡還有一個問題,這裡引入了 promise-retry 模組進行重試,為什麼需要重試?原因是這樣的,當我們告訴瀏覽器執行某命令,比如定位到一個 URL,瀏覽器會去執行,但是是非同步執行。瀏覽器執行的非常快,這時候對於開發人員來講,確切地知道瀏覽器“正在執行”,要比僅僅知道一個結果更重要。正是因為瀏覽器執行的非常快,所以如果不重試的話,很容易被 await 所愚弄。在後面的測試中 promise-retry 也會經常使用,這就是為什麼在端到端測試中需要重試的原因。

測試 Element

來看測試的下一階段,測試元素:

下一個要測試的是初始化狀態下所顯示的是不是 “0”,那麼首先就需要找到控制顯示的 element,在我們的例子中是 display。見第 7 行程式碼,webdriver 的 findElement 方法返回我們所要找的元素。可以通過 By.id或者 By.css 再或者其他找元素的方法。這裡我使用 By.css,它很常用,另外提一句 By.javascript 也很常用。

(不知道你是否注意到,By 是由最上面的 selenium-webdriver 所引入的)

當我們獲取到了 element 以後,就可以使用 getText()(還可以使用其他操作 element 的函式),來獲取元素文字,並且檢查它是否和預期一樣,見第 10 行。對了,不要忘記:

測試 UI

現在該來從 UI 層面測試應用了,點選數字和操作符,測試計算器是不是按照預期的執行:

程式碼 2 – 4 行,定義數字和操作;6 – 10 行模擬點選。實際上想實現的是 “42 * 2 = ”。最終獲得正確的結果——“84”。

執行測試

已經介紹完了端到端測試和單元測試,現在用 npm test 來執行所有測試:

一次性全部通過!(這是當然的了,不然怎麼寫文章。)

想說點關於使用 await 的一些話

你在可能網路上其他地方看到一些例子,它們並沒有使用 async/await,或者是使用了 promise。實際上這樣的程式碼是同步的。那麼為什麼也能 work 的很好呢?坦白地說,我也不知道,看起來像是在 webdriver 中有些 trick 的處理。正如 selenium 文件中說道,在 Node 支援 async/await 之前,這是一個臨時的解決方案。

Selenium 文件是 Java 語言。它還不完整,但是包含的資訊也足夠了,你做幾次測試就能掌握這個技能。

總結

本文中主要介紹了什麼:

  • 介紹了端到端測試中設定瀏覽器的程式碼;
  • 介紹瞭如何使用 webdriver API 來呼叫瀏覽器,以及如何獲取 DOM 中的 element;
  • 介紹了使用 async/await,因為所有 webdriver API 都是非同步的;
  • 介紹了為什麼端到端測試中要使用 retry。

相關文章