February 16 13:45~15:28
前幾天有一位鄉民甲問Teddy一個問題:
鄉民甲:Singleton pattern要怎麼測?
「甚麼,Singleton pattern這麼簡單都不會測!」先別急著下定論,也許Singleton pattern沒有鄉民們想像的那麼容易測試。看一個Java範例。
上面這個WidgetFactoryV1 class是一個Singleton也算是一個Factory Method Simple Factory,依據平台不同產生不同的IWidgetFactory實作。例如,在Win32平台上使用者會得到一個Win32WidgetFactory實作,在Motif環境中使用者會得到MotifWidgetFactory實作。
這樣的程式碼要如何測試?
以上兩個test methods用來測試這個WidgetFactoryV1類別會因為平台不同而產生不同的實作,這兩個test methods單獨執行都是通過的。
但是如果兩個test methods同時執行的話,就會發生錯誤。
鄉民們知道為什麼嗎?,因為WidgetFactoryV1是一個Singleton,第一張圖中的12-27行只會被執行一次。當這兩個test methods一起被執行的時候,在同一個JVM中就只有一份Singleton (static instance),也就是只產生了Win32WidgetFactory實例(instance),無法再次產生MotifWisgetFactory實例,所以第二個test method就執行失敗了。
這個問題要怎麼解?
***
鄉民甲自己提出了一個解法,就是在原本的WidgetFactoryV1類別裡面加上一個新的reset() method,請參考下圖32-34行。
如此一來只要在原本的test methods中,在呼叫getWidgetFactory()之前先呼叫reset()這樣就可以了。請參考下圖13和24行。
兩個test methods都通過了。
這個解法好不好?
***
這個解法的主要問題是這個新增的reset() method違反了Singleton原本的定義:一個class只會產生一份instance。雖然「同一時間」這個WidgetFactoryV1的確是只會保存一份IWidgetFactory的實作,但是只要使用者呼叫reset()之後,這份實作就被換掉了。在實務上,如果真的有人不小心呼叫到reset(),會造成很難發現的bug。
如此設計衍生三個問題:
- 到底設計class的時候,應不應該加上類似reset()這種「為了方便測試而存在的method呢?」
- 如果應該,那麼要如何避免這些為了測試而存在的methods不小心被其他人誤用?
- 上面這個測試Singleton問題,是否還有其他方法可以在不增加reset() method的情況下達到測試的目的?
問題的答案Teddy先賣個關子,如果鄉民們有什麼想法也歡迎留言討論。
***
友藏內心獨白:想去散散步。
考慮在WidgetFactoryV1中加入各種WidgetFactory的唯一實體並存的方式?,大概是用個array/vector來處理。配合lazy init,應該不違反singleton一個class只產生一個實體的本意,但是要多花一點RAM空間...?或許考慮把非使用中的實體用DAO/serialize的方式寫到disk應該會好點? 嗯...不太確定XD
回覆刪除考慮在WidgetFactoryV1中加入各種WidgetFactory的唯一實體並存的方式?,大概是用個array/vector來處理。配合lazy init,應該不違反singleton一個class只產生一個實體的本意,但是要多花一點RAM空間...?或許考慮把非使用中的實體用DAO/serialize的方式寫到disk應該會好點? 嗯...不太確定XD
回覆刪除