Skip to main content

[note] 前端測試的概念與類型

React 本身是基於 JavaScript 的前端框架,因此在實際針對 React 元件進行測試前,需要先暸解在 JavaScript 中常用的測試套件有哪些。另外,測試的種類也非常多,從最大家最常聽到的 Unit Testing、到會整合不同 API 或元件互動的 Integration Testing,最後則是模擬使用者操作的 End to End Testing。

Jest 是 Node-based 的執行器,主要是用來進行元件的單元測試(unit test)而非 DOM 本身,若有需要針對 React 元件進行整合測試,檢驗 DOM 渲染的結果是否正確,可以使用 react-testing-library。但若是需要的進行更貼近瀏覽器環境的 end-to-end 測試的話,則可以使用 cypresspuppeteer

測試的不同類型

根據不同的測試目的,會分成不同的測試類型,簡單來說可以分成三類,但我認為這三類的區隔並一定壁壘分明的,是其中一種就不會是另一種。不論是哪一類型的測試,概念都是透過「預期結果(expect)」和「真實結果」去做比對,看看得到的真實結果是不是如同開發者所預期的,因此,如果你連預期會是如何都無法想像的話,那是完全沒辦法進行測試的。

單元測試(Unit Test)

測試的對象會是程式碼中最小的單元,通常會是自己撰寫的函式(function)、方法(method)、類別(classes)等等,也就是你預期這個 input 進去後,應該會得到什麼樣的 output。舉例來說,根據 LeetCode 的題目寫出 function 後,LeetCode 就會對你寫的 function 做許多的驗證,確保你寫的 function 在各種情況下都能滿足題目的需求,而針對個別 function 進行測試的情況就是所謂的單元測試(Unit Test)。

這部分前端來說最常使到的工具是由 Facebook 推出的 Jest;後端則很常使用 mochajs 搭配 chai

整合測試(Integration Test)

整合測試顧名思義就是需要「整合」,這表示測試的過程不是單一函式就能滿足,過程中可能會需要呼叫 API 獲取資料、使用其他的 library、或者和 DOM 進行整合,預期 DOM 上應該會呈現特定的 element。以 React Component 的測試來說,就比較接近這類型的測試,因為在 React Component 中,可能會去 fetch API 取得資料,取得資料後需要將資料呈現在 DOM 上。這時候如果是撰寫整合測試的話,就需要寫 mock data 取假設 API 回傳的資料內容,在取得內容後,檢測 DOM 有沒有如同預期的呈現出 element;這個過程中,也可以以程式的方式模擬使用者點擊、輸入內容的動作。

這部分以 React 來說最常提到的應該是 Testing Library 搭配 React Testing Library;或 enzyme

End-to-end (E2E) tests

相較於 Unit Test 是測試單一邏輯、Integration Test 是測試整合多個邏輯下的情況、End-to-end (E2E) 則算是最模擬使用者操做實際產品的過程,透過 E2E Test,你可以撰寫使用者操作的流程,並可以透過一個瀏覽器的畫面實際看到頁面被操作的過程,你可以想像成就是有一個使用者真的打開了瀏覽器,從瀏覽器輸入網址,接著進入網頁後進行後續對應的流程。

這部分前端最常聽到的是 cypress 或是 Google 推出的 Puppeteer

不同測試類型的考量

不同類型的測試自然對應到不同的使用時機和情境,也不是說總是把測試補到最齊最滿一定是最好的做法,簡單來說,資源有限而測試案例常常是無窮的,為了在有限的資源中(開發能力、開發時程)能確保程式碼的品質,取捨要做哪些測試往往是更實際的。

在上述不同的測試類型中,Unit Test 可以算是第一道防線,也是通過測試後,比較不會因為其他功能更動而會壞掉的。你可以想像有一個用來驗證表單欄位是否為空的 function,這個 function 的驗證邏輯一旦寫好後,並不會欄位名稱不同、或表單的 UI 改動後,驗證的邏輯就有不同。

但以同樣確認表單欄位必填的功能來說,如果你做的是 integration test 或 E2E test,就有可能因為畫面改變、後端 API 回傳的資料改變,而導致測試結果失敗,因為在 integration test 或 E2E test 中,除了會想要需要驗證使用者該欄位是否漏填外,可能還會同時檢查漏填時,畫面應該要跳出的提示訊息。這時候,一旦 UI 修改後,最後提示的文字內容有異動(例如,原本顯示 Please enter your name,UI 修改後希望顯示 Require to enter your name),或者是 API 回傳的資料有變更時,都可能導致 integration test 和 E2E test 有錯誤。

Unit Test

另外,Unit Test 作為第一道防線,也表示這通常是最容易找到問題「核心」的地方。舉例來說,如果想要驗證的是登入功能,使用的是 E2E Test,這時當 E2E Test 失敗,但若沒有搭配 Unit Test 的話,你就會像一般的使用者一樣只知道無法成功登入,但卻不知道為什麼不能登入;但若有搭配 Unit Test 的話,則會比較容易發現無法成功登入的原因。

不同的測試除了容易發現的問題不同之外,執行的時間(execution time)也不同,E2E Test 通常在測試上會花上的時間也做多,投入的開發成本(Development Cost)也最高,因為只要設計或 UI 一有變動,E2E Test 必然會需要修改。

然而,雖然 E2E Test 的開發成本、投入時間相對來說都比 Unit Test 來得高,但這並不表示 E2E Test 就不重要或不值得都入,因為很多時候,使用者在操作時之所以會碰到問題,是因為各個模組之間的交互作用導致,也就是個別模組獨立運作時是沒問題的,但一但整合再一起就有開發者意想不到的情況;又或者,是使用者實際的操作下可能產生的問題,例如,使用者因為忘了某些資訊而先按了上一頁後,接著在回到下一頁(原本的頁面)時,可能會因為快取等狀況而發生一些在做 Unit Test 時意想不到的情況。

Testing Pyramid

上面的這些概念可以整理成一個稱作「測試金三角(Testing Pyramid)」的圖示。三角形的越上方所需耗費的成本越高、執行的時間通常會更長,但卻更有機會找到意料之外的錯誤。

testing pyramid

圖片來源:Automation Panda

參考