March 12 07:51~09:07
時光一逝永不回
程式中如果有使用到「時間」,對測試來說是一個很傷腦筋的問題。例如,在看板系統中,想測試一個工作項目(work item)的lead time(從開工到交貨的時間)或cycle time(在某個或某些工作階段停留的時間),如果採用手動測試,測試人員需要不斷修改電腦時間,以便模擬出工作項目停留在不同工作階段的狀況。
在〈對付時好時壞的測試案例(5):Time〉Teddy曾經談過這個問題,今天要從程式碼的角度再談一次。
***
程式範例
為了製作「Clean Architecture實作班」課程範例,Teddy最近忙著開發一個看板系統軟體稱為cleanKanban。Teddy參考領域驅動設計(Domain-Driven Design;DDD)的作法,套用Aggregate設計模式。一個aggregate將一小群物件包裝在一起,最上層的物件稱為aggregate root,負責維持不變量(invariant)與交易一致性。
不同Aggregate之間的狀態透過領域事件(domain event)保持同步,下圖AbstractDomainEvent類別代表領域事件的抽象類別,其中occurredOn屬性紀錄事件發生的日期與時間。
上圖中AbstractDomainEvent類別直接透過 new Date()獲得日期物件,這種寫法要撰寫自動測試就很傷腦筋。
學過測試替身〈Test Double(1):什麼是測試替身?〉的鄉民應該會想到以下兩種解法:
- 用相依性注入技巧,AbstractDomainEvent不要自己產生Date物件,而是讓呼叫它的物件傳入。這種做法有兩個潛在問題:
- 需要改程式,破壞了既有程式的介面。如果AbstractDomainEvent已經有很多子類別實作,異動到的程式就比較多。
- 客戶端比較難用。很多領域物件(domain object)都會產生領域事件,代表事件產生時間的occurredOn屬性如果只是因為測試的原因需要外部注入,平添客戶端的麻煩。
- 採用mock object,透過mock object在測試時直接攔截 new Date()呼叫,注入特定日期。這種做法不需要改程式,在測試案例上動手腳即可。詳細討論可參考stackoverflow的這篇文章。
***
透過第三者
除了上述兩種解法,還有一種方式就是透過第三者得到時間物件。撰寫DateProvider類別,透過它間接獲得日期物件。DateProvider 允許使用者注入一個日期物件,在測試模式下可注入特定日期達到模擬不同日期的自動化測試目的。
▼修改AbstractDominEvent的建構函數,透過DateProvide.new() 傳回日期。
▼在測試案例中,將特定日期傳給DateProvider,可以模擬領域事件發生在特定日期的情況。
***
結論
今天談到關於日期測試的三種方法,各有優缺點,視不同情況可能都會派上用場。DateProvider的缺點是,萬一有人要搞破壞在production code裡面呼叫到DateProvider.setDate(),注入一個特定日期,系統狀態可能就整個錯掉。這個問題可以透過把DateProvider寫得更「精緻」一點,只允許在測試環境被注入日期來改善。
關於更多測試技巧,歡迎參考泰迪軟體的【單元測試這樣學就會了實作班】。
***
友藏內心獨白:有月光寶盒才可以穿梭時空。
沒有留言:
張貼留言