2010年10月11日 星期一

簡單的 mock 使用情境

今天在寫新功能時, 又陷入小小的苦惱。我要寫一個小函式, 這個函式會依參數從資料庫取出資料, 並依參數決定做那些操作, 再傳回操作完的結果。函式本身需求明確不複雜, 但有不少組合情況要處理好, 有 unit test 比較保險。

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

用 Mock 帶來的問題則是:
  1. 視使用的方式, 對目標函式的實作細節需有不同深度的理解。像是 getter 被呼叫幾次, 傳入那些參數。若程式有部份是別人寫的, 需要另外時間讀原本已被隱藏的細節。 
  2. 目標函式改變使用 getter 的方式時, 也需更新準備 mock 的測試程式。
  3. 無法保證目標函式正確, 實際使用目標函式時會有正確成果, 因為 getter 有可能出錯。導致有可能要另測 getter。相對來說, 直接存取資料庫的例子, 不測 getter 的風險較小。
換個角度來看, 測試程式使用 Mock 相當於切開和資料庫的連繫, 卻增加和 getter 操作的連繫。

沒有留言:

張貼留言

C++ 能否用 memcpy 複製 class / struct 的資料?

答案是: POD (plain old data) type 可以。POD type 可和 C 互通, CPP Reference POD Type 的介紹: Specifies that the type is POD (Plain Old Data) type. Thi...