在開始 TDD 前, 我得先決定如何準備 fixture (輸入資料)。大概有兩種選擇:
- 規劃好目標函式的測試情境, 準備測試用的資料庫, 塞對應的資料進去。
- 使用 Mock (我用 pymox)
配合 Django TestCase 和 ORM 操作, 塞入測試資料已簡單許多, 不用自己產生一個同 schema 空的資料庫, 並在每個 test case 前先清空資料, 也不用自己下 SQL 填資料, 所以實作門檻已低了不少。但是仍有幾個問題:
- 當測試資料有 relation 時, 要多塞不少相關資料。
- 要規劃清楚那些案例需要那些資料。規劃完後, 也需要寫註解畫個表格之類的, 不然很難了解測試資料內容。即使畫了表格, 也不太直覺易懂。
- 日後發現新 bug 或改功能時, 不容易修改這堆測試資料。
最讓我受挫的是, 寫完後很難閱讀和維護, 測試案例一多, 根本沒興趣慢慢讀輸入資料的細節。其它像是和外部資源綁在一起、實作目標函式時得先寫相依函式等議題, 在這個例子裡到是沒那麼嚴重。
這回我改試著用 Mock, 將目標函式的介面寫成大概如下的形式:
def target_function(arg1, arg2, getter=db.some_function): ...db 是我另開的模組, 用來放包著 SQL 操作的函式。getter 指向真正用的函式, 測試時則視測試案例傳不同的 mock objects。
使用 Mock 的話, 準備測試資料變得很容易, 而且直接寫明目標函式如何和這些資料互動, 不用看資料庫的資料想像它轉換後的資料。要加測試案例時簡單許多, 不用思考如何在不同表格組出前置資料, 再用這些資料經由資料庫操作轉成中間資料給目標函式用。整體評估下來, 為了測試而多加參數 getter 是很划算的設計, 在註解裡註明一下 getter 的用意即可。
用 Mock 帶來的問題則是:
用 Mock 帶來的問題則是:
- 視使用的方式, 對目標函式的實作細節需有不同深度的理解。像是 getter 被呼叫幾次, 傳入那些參數。若程式有部份是別人寫的, 需要另外時間讀原本已被隱藏的細節。
- 目標函式改變使用 getter 的方式時, 也需更新準備 mock 的測試程式。
- 無法保證目標函式正確, 實際使用目標函式時會有正確成果, 因為 getter 有可能出錯。導致有可能要另測 getter。相對來說, 直接存取資料庫的例子, 不測 getter 的風險較小。
換個角度來看, 測試程式使用 Mock 相當於切開和資料庫的連繫, 卻增加和 getter 操作的連繫。
沒有留言:
張貼留言