幫一個複雜的 component 寫測試,該測到多細?

和同事討論一個複雜的複合型 component 的測試原則,以及不同層級間 components 責任的劃分,以下是一些粗略想法的筆記。

RTL 作者對元件測試的建議

  • spec 的定義方式,應該用接近使用者實際使用預期的方式去定義
  • 測試應該用接近使用者行為的方式去進行測試

測試的目標

  • 測試是為了保護模組的行為符合 spec,也就是說,要確保測試的對象,能遵照其責任,完成任務
    • 實作上,spec 就是寫測試時我們去 assert 的內容。也就是說,我們其實會把 spec 寫在測試的程式碼中。
      • 由此延伸一個點,反過來說,實際上測試裡的 spec 有沒有涵蓋到「真正的 spec」,是要看 assert 的內容。coverage 只是在測試中有沒有跑過這段程式碼。高涵蓋率為「測試完備」的必要而非充分條件。
        • 「測試涵蓋率是測試完備的必要非充分條件」這個問題,在所有類型的測試都會有,但因為寫比較大的 components 的測試時,一次 render 裡面涵蓋的 spec 量較多(要 assert 的東西比較多),同樣滿足涵蓋率的條件下,比較有可能會有沒 assert 到的 spec。
          • 目前沒有想到有什麼自動化的解法,只能人工確認重要的 test cases 是否都有進行測試和 assert。
          • 因為要人工檢查哪些東西該 assert ,最好有個 check list 參考

不同層級元件的測試劃分

  • React component 是一層 render 一層,上層 component 的測試,有需要測到子 component 的行為嗎?

    • 釐清 component 的責任範圍,同時也就釐清該測試的範圍,同時也是釐清我們把 spec 寫在哪裡
    • 要回答上述問題,就要釐清 parent component 的責任:
      • parent component 的責任是什麼?
        • render 出這個 component 該 render 的東西:也就是處理 input,再以特定的方式呼叫 children component,並組合其結果。
      • children component 的責任,會被包含在 parent component 中
        • 例如:一張有很多 input 的表單,它的責任有在錯誤狀態時在錯誤的 input 顯示錯誤訊息;單一 input component 的責任有在自己錯誤狀態時顯示錯誤訊息。
  • parent component 的責任和 children component 的責任,必然會有重疊的部分,我們應當把重疊的部分交給 children component 的測試,去幫我們保證 parent component 責任的履行嗎?

    • 理論上不一定要把所有 spec 的檢查都在最上層做。但必須確保每一個 spec 在程式中都有地方 assert 到(但可能沒有自動化的方法可以檢查)。(例如上述 input 錯誤訊息內容的特定規格,或許可以只在 input 的測試檢查)
    • 怎麼做?
      • 目前根據前述 RTL 的建議,是選擇第二種測法,也就是讓 interface 變動彈性成本較低
      • 第一種測法:在 parent 測各種情況下 children component 是否被正確的呼叫、給予參數,在 children 測各種 inputs 是否 render 對的結果
        • 好處:
          • parent 不用管 children 的 spec 為何。children spec 修改就由 children 的測試負責。
        • 代價:
          • spec 沒有修改但 children 介面修改時,會要改 parent 的測試。改動 children 介面成本增加。
      • 第二種測法:在 parent 測各種情況下 parent 回傳的結果,側重於測是否被正確的組合成 parent 的回傳結果
        • 好處:
          • parent 測試不需要考慮 children 的介面,只在意 parent 的結果
        • 代價:
          • parent 的測試範圍也會包括到部分 children 的 spec,會在 parent 的測試和 children 的測試重疊到一些 spec。
            • 重疊的 spec 如果有修改就要同時改兩邊的測試,所以在寫 parent 測試的時候會思考怎麼不涉及 children spec 的情況下能 assert parent 的責任範圍
    • 什麼東西寫在 children 測,什麼東西寫在 parent 測,要考慮:
      • children 介面的穩定度?children 介面是否容易被拆解或替換?
        • 穩定度高的 children 才適合把測試分給它
      • 模組的規模?
        • 當一個模組要負責的 spec 內容太多時,可考慮這個模組的責任是不是太多,是否應該拆分模組或委交下層模組測試
    • 其實不只 React component,所有在 function 中執行 function 的程式,都可以用同樣方式思考其測試