l

2012年2月24日 星期五

如何測試Singleton(3)

February 23 14:09~

第二集中提到下列五種不加入reset()便可測試原本WidgetFactoryV1這個Singleton的方法,今天要把這個問題做一個結尾。

  • 每次執行單一測試案例(test method)時啟動一個新的JVM
  • 利用Reflection或是MockObject工具重設mFactory為null
  • 利用繼承
  • 使用Nested Classes
  • 在分散式持續整合系統(CI)上跑測試案例

除了這五種方法之外,Teddy的另一位學弟(那冒出來的那麼多學弟啊…XD)提出了一個根本的問題:學長,你的程式根本寫錯了啊。看一下學弟所建議的版本:

WidgetFactoryV2

對照一下之前的版本:

螢幕快照 2012-02-23 下午2.27.09

學弟的看法是,原本的WidgetFactoryV1本身結合了Singleton與Simple Factory這兩個patterns,所以讓WidgetFactoryV1的測試變得困難了。應該要把Singleton的責任放到Win32WidgetFactory與MotifyWidgetFactory身上,讓WidgetFactory只負責傳回不同平台的Singleton即可。

所以,只要在測試案例只要寫成這樣就可以了:

螢幕快照 2012-02-23 下午2.38.55

 

全部綠燈。

螢幕快照 2012-02-23 下午2.40.15

 

學弟還很熱心的寫了一篇文章來回答這個問題,有興趣的鄉民可參考這裡「問答(2) -- Testing simple factory」。

***

程式改成這樣的確是好測很多,但是俗話說,有一好沒兩好,修改過的程式引發一個問題:

把平台判斷的責任丟給client判斷:Client呼叫IWidgetFactory getWidgetFactory(String aPlatform)來得到不同平台的IWidgetFactory實作,這些實作「理論上」在同一的JVM中只會有一個,也就是說你不應該在軟體畫面上同時看到Win32的Widget元件與Modify的Widget元件。修改之後的程式允許用者寫出這樣的程式:

螢幕快照 2012-02-23 下午2.52.34

雖然Win32WidgetFactory與MotifWidgetFactorty本身都是Singleton,但是從應用程式的problem domain來看,這兩個Singleton在runtime是彼此互斥的,同一時間只會存在一個。修改之後的程式因為放寬這樣的限制,所以自然也變得比較容易測試了

***

那如果把程式改成下面這樣呢?

螢幕快照 2012-02-23 下午3.00.28

讓Simple Factory(WidgetFactoryV3)自己判斷平台,client必須藉由改變環境變數來得到不同平台的的IWidgetFactory實作。耶,這個做法好像是介於V1與V2版本之間,容易測試但是根本上還是允許client端可以同時得到Win32WidgetFactory與MotifWidgetFactorty,所以還是有著V2版本的問題:

螢幕快照 2012-02-23 下午3.08.19

 

最後一個版本:

螢幕快照 2012-02-23 下午3.12.58

這個版本跟V1比較像,client端透過WidgetFactoryV4的getWidgetFactory在同一個JVM中只能得到同樣一份IWdigetFactory的實作。寫到這邊有種鬼打牆的感覺,總之Teddy想說的是,這個問題的本質比較像是:

如何測試一個static block,static method或是受final static變數所控制的不同程式路徑。

***

為了這麼一個小不拉機的問題,劈哩啪啦扯了這麼多,好像有點無病呻吟之感,又有一點「用程式繞口令」的味道…Orz。最後回答原本Teddy提出來三個問題的前兩個問題…啊,這麼快就把這三個問題給忘了:

問:到底設計classes的時候,應不應該加上類似reset()這種「為了方便測試而存在的method呢?」

答:這個問題有人贊成也有人反對。贊成的人認為,為了所謂的testability的原因,可以「有條件的」經過深思熟慮之後,在classes裡面加入為了方便測試而存在的methods甚至是nested classes。反對的人認為這種作法會汙染原本classes的介面,可能會導致誤用與誤解(有潔癖的人通常屬於這一類的…XD)。Teddy的看法是,如果有其他的手段,例如可以用reflection或是mock object frameworks來解決不易測試的問題,就儘量避免這種為了測試而存在的methods或是nested classes。如果不行,那麼為了測試理由加入一些methods或是nested classes也尚可接受。Teddy當年在念書的時候,也曾經研究過design for testability的問題,但是因為資質不足,研究了幾個月之後搞不出個鳥來,後來就放棄了…Orz ^ 3。

問:如果應該,那麼要如何避免這些為了測試而存在的methods不小心被其他人誤用?

答:在Java中可以把這種僅供測試使用的methods宣告為只有同一個package的其他classes才存取的到,然後把test cases和待測程式放在同一個package裡面。總之就是想辦法控制一下只有測試程式可以存取到這些僅供測試使用的methods。另外就只能靠寫文件,註解,或是用annotation在程式中標示一下那些methods或是classes是僅供測試使用的。

以上,報告完畢,稍息後不敬禮解散;稍息。

***

友藏內心獨白:有把這系列三集都看完且眼睛沒扭到或抽筋的鄉民們在此幫你們拍拍手。

沒有留言:

張貼留言