2010-02-20 68 views
45

我最近讀到,製作一個單身人士課程使得不可能嘲笑課程的對象,這使得難以測試它的客戶端。我無法立即明白其根本原因。有人可以解釋一下,嘲笑一個單身人士課程是不可能的嗎?此外,還有更多的問題與創建一個單獨的類相關嗎?嘲笑一個單身人士課

+1

可能DUP:http://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons – ewernli 2010-02-20 12:45:12

+0

也http://stackoverflow.com/questions/780483/tdd見友好的單一類 – finnw 2010-02-20 16:13:15

+0

@Aadith:當你做其他類型的測試(不一定是單元測試,但它也可能發生單元測試)時,另一個問題可能會出現。單身並不都是不變的。在運行測試套件時,這會讓你頭疼,並且你不需要模擬單身人士,而是真正的單身人士:你將有一次測試修改你的可變單身人士,然後再重新使用那個經過修改的單身人士。大約每次我想:「這裏很安全,我真的可以用一個單身人士」,這倒退了。 – SyntaxT3rr0r 2010-02-20 16:23:37

回答

51

當然,我也喜歡寫東西不使用單,他們是邪惡的,使用吉斯/春/不管但首先,這不會回答你的問題,第二,你有時必須處理單身人士,例如使用遺留代碼。

所以,我們不要討論單身人士的好壞(這裏有另一個question),但讓我們看看在測試過程中如何處理它們。首先,讓我們來看看一個普通實現單例:

public class Singleton { 
    private Singleton() { } 

    private static class SingletonHolder { 
     private static final Singleton INSTANCE = new Singleton(); 
    } 

    public static Singleton getInstance() { 
     return SingletonHolder.INSTANCE; 
    } 

    public String getFoo() { 
     return "bar"; 
    } 
} 

有兩個測試的問題在這裏:

  1. 構造函數是私有的,所以我們不能擴展它(我們可以」 t控制測試中實例的創建,但是,這就是單例的問題)。

  2. getInstance是靜態的,所以很難在使用單代碼注入假的,而不是單獨的對象的。

對於基於繼承和多態的嘲諷框架來說,這兩點顯然都是很大的問題。如果你有代碼的控制權,一種選擇是通過添加一個允許調整內部字段的setter來使你的單例更「可測試」,如Learn to Stop Worrying and Love the Singleton中所述(在這種情況下你甚至不需要模擬框架)。如果你不這樣做,基於攔截和AOP概念的嘲諷框架可以克服前面提到的問題。

例如,Mocking Static Method Calls顯示瞭如何使用JMockit Expectations模擬單身人士。

另一種選擇是使用PowerMock,這是對Mockito或JMock的擴展,它允許模擬通常不像靜態,最終,私有或構造函數方法那樣可嘲笑的東西。你也可以訪問一個類的內部。

+6

感謝Pascal ..這是一個很好的解釋..但是你的Singleton的實現卡住了我......爲什麼有這個內部類持有單例實例?它是否提供任何特定優勢?到目前爲止,我所見過的所有實現在類中都有一個私有靜態字段。 – Aadith 2010-02-21 05:56:53

+8

@Aadith:這是一個類加載技巧,允許您在不使用'synchronized'的情況下實現單例模式。 – skaffman 2010-02-21 10:41:16

+6

@Aadith這是IODH初始化成語(IODH)成語(http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#dcl),它允許實現一個如同@skaffman所提到的,帶有零同步開銷的延遲加載單例式 – 2010-02-21 18:09:03

9

它非常依賴於單例實現。但主要是因爲它有一個私有構造函數,因此你不能擴展它。但是,你有以下選項

  • 將界面 - SingletonInterface
  • 讓你的單例類實現該接口
  • Singleton.getInstance()回報SingletonInterface
  • 提供模擬實現的SingletonInterface在你的測試
  • 集它在private static字段上使用反射Singleton

但是,你最好避免單身人士(代表全球國家)。 This lecture從可測試性的角度解釋了一些重要的設計概念。

12

根據定義,一個單例只有一個實例。因此它的創作受到班級本身的嚴格控制。通常它是一個具體的類,而不是一個接口,並且由於它的私有構造函數,它不是可分類的。此外,它的客戶(通過撥打Singleton.getInstance()或同等服務)積極發現,因此您無法輕鬆使用例如Dependency Injection用一個模擬實例來替換它的「真實」的實例:

class Singleton { 
    private static final myInstance = new Singleton(); 
    public static Singleton getInstance() { return myInstance; } 
    private Singleton() { ... } 
    // public methods 
} 

class Client { 
    public doSomething() { 
     Singleton singleton = Singleton.getInstance(); 
     // use the singleton 
    } 
} 

對於嘲笑,你會非常需要可自由子類的接口,並且其具體的實現是通過依賴注入提供給其客戶(S) 。

你可以放鬆辛格爾頓實施,使其可測試通過

  • 提供能夠通過一個模擬的子類實現,以及「真正的」一個
  • 添加setInstance方法,以允許更換接口在單元中的實例測試

實施例:

interface Singleton { 
    private static final myInstance; 
    public static Singleton getInstance() { return myInstance; } 
    public static void setInstance(Singleton newInstance) { myInstance = newInstance; } 
    // public method declarations 
} 

// Used in production 
class RealSingleton implements Singleton { 
    // public methods 
} 

// Used in unit tests 
class FakeSingleton implements Singleton { 
    // public methods 
} 

class ClientTest { 
    private Singleton testSingleton = new FakeSingleton(); 
    @Test 
    public void test() { 
     Singleton.setSingleton(testSingleton); 
     client.doSomething(); 
     // ... 
    } 
} 

正如你所看到的,你只能通過損害Singleton的「清潔度」來使你的單例使用代碼單元可測試。最後,如果可以避免,最好不要使用它。

更新:這裏是Michael Feathers對Working Effectively With Legacy Code的強制參考。

+1

是的 - 這被稱爲「介紹靜態二傳手」在有效使用傳統代碼工作的書。 – TrueWill 2010-02-20 16:39:46

+0

@TrueWill感謝您提及,它是一本很棒的書。我幾乎有機會推薦它 - 這次我忘了:-) – 2010-02-20 20:43:20

+0

感謝彼得的解釋和真正的指針 – Aadith 2010-02-21 06:06:40

3

並非Singleton模式本身就是純粹的邪惡,但即使在它不合適的情況下,它也被大量使用。許多開發人員認爲「哦,我可能只需要其中的一個,所以讓我們把它變成一個單身人士」。事實上,你應該考慮「我可能只需要其中的一個,所以讓我們在我的程序開始時構造一個,並在需要時傳遞引用。「

單身人士和測試的第一個問題不是因爲單身人士,而是因爲懶惰。由於獲得單身人士的方便,對單身人士對象的依賴通常直接嵌入到使其成爲單身人士的方法中很難改變的單身到另一個對象具有相同的接口,但不同的實現(例如,一個模擬對象)

相反的:

void foo() { 
    Bar bar = Bar.getInstance(); 
    // etc... 
} 

喜歡:

void foo(IBar bar) { 
    // etc... 
} 

現在您可以測試功能foo帶有可以控制的模擬bar對象。您已刪除相關性,以便您可以在不測試bar的情況下測試foo

單身人士和測試的另一個問題是測試單身人士本身。單身人士(設計上)很難重建,所以例如你只能測試一次單身人士構造。還有可能Bar的單個實例在測試之間保持狀態,這取決於運行測試的順序而導致成功或失敗。

14

嘲笑單身人士的最好方法是根本不使用它們,或者至少不是傳統意義上的。你可能想查找一些做法是:

  • 編程
  • 依賴注入
  • 反轉控制接口

因此而不是一個單一的訪問是這樣的:

Singleton.getInstance().doSometing(); 

...定義你的「單身」作爲一個接口,並有其他東西管理它的生命週期而在你需要它,例如作爲一個私有的實例變量注入它:

@Inject private Singleton mySingleton; 

然後,當你是單元測試的類/組件/等,這取決於單,你可以輕鬆地將它的模擬版本。

大多數依賴注入容器將允許您將組件標記爲「單例」,但是由容器來管理它。

使用上述實踐使單元測試代碼變得更加容易,並讓您專注於功能邏輯而不是佈線邏輯。這也意味着你的代碼真正開始成爲真正的面向對象,因爲任何靜態方法(包括構造函數)的使用都是有爭議的過程。因此,您的組件也開始變得真正可重用。

退房谷歌吉斯作爲首發10:

http://code.google.com/p/google-guice/

你也可以看看Spring和/或OSGi的它可以做這種事情。那裏有很多IOC/DI的東西。:)

+1

IoC容器生命週期管理的大+1! – TrueWill 2010-02-20 16:42:16

1

有一種方法來模擬單身人士。使用powermock模擬靜態方法並使用Whitebox調用構造函數YourClass mockHelper = Whitebox .invokeConstructor(YourClass.class); Whitebox.setInternalState(mockHelper, "yourdata",mockedData); PowerMockito.mockStatic(YourClass.class); Mockito.when(YourClass.getInstance()).thenReturn(mockHelper);

發生什麼事情是Singleton字節碼在運行時發生了變化。

享受