l

2012年2月17日 星期五

如何測試Singleton(1)

February 16 13:45~15:28

前幾天有一位鄉民甲問Teddy一個問題:

鄉民甲:Singleton pattern要怎麼測

「甚麼,Singleton pattern這麼簡單都不會測!」先別急著下定論,也許Singleton pattern沒有鄉民們想像的那麼容易測試。看一個Java範例。

WidgetFactoryV1

上面這個WidgetFactoryV1 class是一個Singleton也算是一個Factory Method Simple Factory,依據平台不同產生不同的IWidgetFactory實作。例如,在Win32平台上使用者會得到一個Win32WidgetFactory實作,在Motif環境中使用者會得到MotifWidgetFactory實作。

這樣的程式碼要如何測試?

TestWidgetFactoryV1

以上兩個test methods用來測試這個WidgetFactoryV1類別會因為平台不同而產生不同的實作,這兩個test methods單獨執行都是通過的。

testv1-1

 

testv1-2

 

但是如果兩個test methods同時執行的話,就會發生錯誤。

testv1-3

testv1-4

 

鄉民們知道為什麼嗎?,因為WidgetFactoryV1是一個Singleton,第一張圖中的12-27行只會被執行一次。當這兩個test methods一起被執行的時候,在同一個JVM中就只有一份Singleton (static instance),也就是只產生了Win32WidgetFactory實例(instance),無法再次產生MotifWisgetFactory實例,所以第二個test method就執行失敗了。

這個問題要怎麼解?

***

鄉民甲自己提出了一個解法,就是在原本的WidgetFactoryV1類別裡面加上一個新的reset() method,請參考下圖32-34行。

reset-2

 

如此一來只要在原本的test methods中,在呼叫getWidgetFactory()之前先呼叫reset()這樣就可以了。請參考下圖13和24行。

testv1-5

 

兩個test methods都通過了。

testv1-6

這個解法好不好?

***

這個解法的主要問題是這個新增的reset() method違反了Singleton原本的定義:一個class只會產生一份instance。雖然「同一時間」這個WidgetFactoryV1的確是只會保存一份IWidgetFactory的實作,但是只要使用者呼叫reset()之後,這份實作就被換掉了。在實務上,如果真的有人不小心呼叫到reset(),會造成很難發現的bug。

如此設計衍生三個問題:

  1. 到底設計class的時候,應不應該加上類似reset()這種「為了方便測試而存在的method呢?
  2. 如果應該,那麼要如何避免這些為了測試而存在的methods不小心被其他人誤用?
  3. 上面這個測試Singleton問題,是否還有其他方法可以在不增加reset() method的情況下達到測試的目的?

問題的答案Teddy先賣個關子,如果鄉民們有什麼想法也歡迎留言討論。

***

友藏內心獨白:想去散散步。

2 則留言:

  1. 考慮在WidgetFactoryV1中加入各種WidgetFactory的唯一實體並存的方式?,大概是用個array/vector來處理。配合lazy init,應該不違反singleton一個class只產生一個實體的本意,但是要多花一點RAM空間...?或許考慮把非使用中的實體用DAO/serialize的方式寫到disk應該會好點? 嗯...不太確定XD

    回覆刪除
  2. 考慮在WidgetFactoryV1中加入各種WidgetFactory的唯一實體並存的方式?,大概是用個array/vector來處理。配合lazy init,應該不違反singleton一個class只產生一個實體的本意,但是要多花一點RAM空間...?或許考慮把非使用中的實體用DAO/serialize的方式寫到disk應該會好點? 嗯...不太確定XD

    回覆刪除