引言
設計模式是對被用來在特定場景下解決一般設計問題的類和相互通信的對象的描述,通過在系統設計中引入合適的設計模式可以為系統實現提供更大的靈活性,從而有效地控制變化,更好地應對需求變更或者按需變更系統運行路徑等問題。
#FormatImgID_0# |
|
單元測試是軟件開發的一個重要組成部分,是與編碼實現同步進行的開發活動,這一點已成為軟件開發者的共識。適度的單元測試不但不會影響開發進度,反而可以為開發過程提供很好的控制,為軟件質量、系統重構等提供有力的保障,并且,當后續系統需求發生變更、Bug Fix 或功能擴展時,能很好地保證已有實現不會遭到破壞,從而使得程序更易于維護和修改。 Martin Fowler、Kent Beck、Robert Martin 等軟件設計領域泰斗更是極力倡導測試先行的測試驅動開發(Test Driven Development,TDD)的開發方式。
單元測試主要用于測試細粒度的程序單元,如類的某個復雜方法的正確性,也可以根據需要綜合測試某個操作所涉及的多個相互聯系的類的正確性。在很多情況下,相互聯系的多個類中有些類比較簡單,為這些簡單類單獨編寫單元測試用例往往不如將它們與使用它們的類一起進行測試有意義。
模擬對象(Mock Objects)是為模擬被測試單元所使用的外圍對象、設備(后文統一簡稱為外部對象)而設計的一種特殊對象,它們具有與外部對象相同的接口,但實現往往比較簡單,可以根據測試的場景進行定制。由于單元測試不是系統測試,方便、快速地被執行是單元測試的一個基本要求,直接使用外部對象往往需要經過復雜的系統配置,并且容易出現與欲測試功能無關的問題;對于一些異常的場景,直接使用外部對象可能難以構造,而通過設計合適的 Mock Objects,則可以方便地模擬需要的場景,從而為單元測試的順利執行提供有效的支持。
本文根據筆者經驗,介紹了幾種典型的設計模式在系統設計中的應用,及由此為編寫單元測試帶來的方便。
從對象創建開始
由于需要使用 Mock Objects 來模擬外部對象的功能,因此必須修改正常的程序流程,使得被測試功能模塊與 Mock Objects,而不是外部對象進行交互。要做到這一點,首先要解決的問題就是對象創建,即在原本創建外部對象的地方創建 Mock Objects,因此在設計、實現業務邏輯時需要注意從業務邏輯中分離出對象創建邏輯。
#FormatImgID_1# |
|
Factory Method 是一種被普遍運用的創建型模式,用于將對象創建的職責分離到獨立的方法中,并通過子類化來實現創建不同對象的目的。如果被測試單元所使用的外部對象是通過 Factory Method 創建的,則可以通過從已有被測試的 Factory 類派生出一個新的 MockFactory,以創建 Mock Objects,并在 setUp 測試中創建 MockFactory,從而間接達到對被測試類進行測試的目的。
下面的代碼片段展示了具體的做法:
// BaseObjects.java package com.factorymethod.demo; public interface BaseObjects { voidfunc(); } // OuterObjects.java package com.factorymethod.demo; public class OuterObjects implements BaseObjects { public void func() { System.out.println("OuterObjects.func"); } } // LogicToBeTested.java, code to be tested package com.factorymethod.demo; public class LogicToBeTested { public void doSomething() { BaseObjects b = createBase(); b.func(); } public BaseObjects createBase() { return newOuterObjects(); } } |
以下則是對應的 MockOuterObjects、MockFactory 以及單元測試的實現:
// MockOuterObjects.java package com.factorymethod.demo; public class MockOuterObjects implements BaseObjects { public void func() { System.out.println("MockOuterObjects.func"); } } // MockLogicToBeTested.java package com.factorymethod.demo; public class MockLogicToBeTested extends LogicToBeTested { public BaseObjects createBase() { return new MockOutterObjects(); } } |