深入淺出 JavaScript 軟體測試 — #1 撰寫測試的 Mindset

C.T. Lin
6 min readOct 26, 2021

最近在回顧一些四五年前寫的測試程式碼,其中有一部分還堪用,有一部分的可維護性跟保護力卻是有點堪憂,自己的階段也從「我不太會寫測試」到「追求極高的測試涵蓋率」再到「撰寫適合專案/產品的測試」,感受頗深。

於是這篇文章就誕生啦。

希望接下來能撥出一些時間,慢慢寫完這系列。這系列應該要包括:

  • 面對測試的正確心態,以及談一些常見迷思
  • 測試的原理與細節
  • Jest 的使用、一般使用情境
  • Jest 應用:HTTP Client 測試
  • Jest 應用:HTTP Server 測試
  • Jest 應用:前端測試(React Testing Library)
  • 其他:End to End(Cypress)、Database、GraphQL、TypeScript 測試、Jest 架構與 Source Code 等等,不太確定,會邊寫邊調整

接下來這篇要帶大家看一下測試的常見迷思跟測試的好處。

常見迷思之一:我沒時間寫測試

「我沒時間寫測試啦,專案時程很趕」

這是一個非常常見的說法,在我開始第一份工作的前一兩年我也都是這樣說的,但這其實是「我不太會寫」的藉口,當然當時寫的程式碼大多也是沒有良好介面設計以及依賴隔離的「不可測試的程式碼」。

我不能否認寫測試確實要花上一些時間,熟悉軟體開發的人都知道,不會的東西通常要花上熟悉的人十倍以上的時間才寫的出來,而熟悉的人不只寫得快,也寫的比較好。

寫測試是要學習的,甚至要學習比較好的實作方式,這也是這個系列之所以存在的理由。「可測試性」也是評估程式碼品質的指標之一,在有測試的概念跟實踐後,才能避免輕易地寫出「不可測試的程式碼」。上班學也好,下班補也好,測試的技巧不會突然精通。

雖然依照軟體類型會有差異,但大多有一定規模、需要維護或繼續開發的項目,在熟悉寫測試的前提下,加上一定數量的測試一定會比較好。

用電腦來執行測試、在 CI 持續的檢查下,這樣就能釋放 QA 人力在重複且無聊的驗證上面,在已經執行了數千、數萬次的測試上節省的時間是很可觀的。

常見迷思之二:測試涵蓋率是不是要接近 100%

「既然都會寫測試了,那就寫好寫滿」

剛開始熟悉測試方式的開發者,很容易就會掉入這個陷阱。每個 function、每個 class 都給他加上測試,極端狀況、錯誤訊息也都不放過,這樣有滿滿的安全感。

然而在這樣做的時候只是為了自己爽,但卻忽略了維護以及執行測試的額外成本。

另一個更糟糕的是,因為沒有在系統定義的規範跟邊界寫測試,而是針對那些 function 跟 class,往往導致一個美妙的重構之後,功能正常但測試卻壞光光的狀況,也就是測試缺乏了保護力跟可維護性。

當你把 HTTP 的實作從 XMLHttpRequest 換成 fetch,送出的 HTTP request 一模一樣,但你的測試卻全部宣告失效,那一定就是在使用一個比較不好的方式對實作細節做測試。

Kent C. Dodds 提出,測試的分佈要像一個獎杯一樣,比較多的 Static 檢查(ESLint、TypeScript),比較多的整合測試,少一點的單元測試跟 E2E 測試。

from https://twitter.com/kentcdodds/status/960723172591992832

我是很認同這個概念,Static 檢查平常就要持續累積,太過脆弱的單元測試可以少一點, E2E 測試可以針對產品最多人使用或最重要的環節(例如:註冊、付款)去做就好。

20% 的測試能抓出 80% 的 bug,寫測試時與其追求 100% 涵蓋率,不如在最重要或是最複雜容易出錯的地方多下點功夫。

常見迷思之三:無論在什麼專案都應該寫測試

在學習寫測試一段時間後,去碰觸到測試涵蓋率 0% 的專案你可能會覺得心驚驚。但其實在有些時候,不寫測試就是最好的做法。

就例如你今天去參加黑客松,第一個小時會先選擇安裝 test runner,把測試環境搞定嗎?

在一個專案的成立初期,可能 Spec 也不明確,功能介面朝令夕改,這些彈性跟反應速度都是需要的,但絕非讓你寫測試的良好環境。

在產品的早期也是,如果還在 Proof of Concept、尋找 Product Market Fit 階段,沒有或是只有少數的付費使用者,快速修改程式方向的能力絕對比測試跟穩定性重要許多。

不複雜而且壞了不會有太大損失的地方通常也是沒有立即測試的需求,例如一些簡單的 Landing Page。

該寫測試的專案:

  • 功能複雜,連維護者可能都要看文件才知道在幹嘛
  • 需要多人維護甚至交接的專案
  • 有人在使用、付錢且壞了會有重大影響的專案
  • 開發者非全心開發的專案,常常回來後已經不知道之前在寫什麼

不該寫測試的專案:

  • 還在 POC 或 MVP 階段,Spec 不明確的專案
  • 邏輯不複雜、看看就懂
  • 壞了沒有太大損失

測試好處之一:撰寫測試就是在寫 Spec

當你想知道現在程式的行為,最精準的方式不是去問 PM 也不是去看文件,而是去看撰寫的測試或是實際執行看看結果。

換句話說,如果 Spec 沒被寫成測試,沒有人能擔保這些東西在經過工程師持續的修改後,在往後的版本一直都能保持一樣的行為。

再來就是我們都知道文件常常會因為忘了改而過時,但只要測試還有在持續執行的一天,他一定能反映當下程式碼的行為,而不會輕易過時。

測試好處之二:鼓勵自己與接手的人重構

「它現在在線上運作的好好的,幹嘛改它,改了搞不好壞掉」

如果有這種心態,通常一段程式碼就很難進步了,尤其是身為接手的人,因為缺少許多過往的知識,更是難以去放手修改。

在 GitHub 上,所幸有不少的重要專案都有充足的測試保護,當我有意願幫忙修 bug 或是添加新 feature 時,通常都能先 clone 下來跑過一次測試,確認專案有在保護之下我才能大膽的去發揮。

測試好處之三:修好的 Bug 不會再次出現

一個厲害的軟體專案,通常也過了幾年才成熟。這之中負責的工程師早已處理過數不清使用者回報的問題。

以 HTTP Client 為例,要處理的狀況就非常多,如果今天遇到一個特定 Content-Type (例如說: multipart/form-data)遇到大檔會卡住的問題,負責的工程師把它解決了但沒寫下任何的 test case。一兩年之後另一個工程師接手去做,意氣風發的做了些效能改進,最後這個大檔會卡住的問題卻重出江湖,也就是我們常說的出現了 Regression。

在這個情況下,接手的工程師也沒做錯什麼,但先前負責的人如果能多花點心力加上測試,就能避免掉這種 bug 反反覆覆的狀況。

結語

這篇介紹了一下測試的心態、好處,以及什麼時候該寫測試,這部分不管使用什麼程式語言都是一樣的道理。不過,接下來的幾篇就會進入 JavaScript 測試的環節。

--

--

C.T. Lin

Architect @ Dcard. Author of Electron React Boilerplate and Bottender. JavaScript Developer.