2011-05-03 104 views
51

最近,我已經開始使用Moq進行單元測試。我使用Moq來模擬出我不需要測試的類。嘲弄靜態方法

你通常如何處理靜態方法?

public void foo(string filePath) 
{ 
    File f = StaticClass.GetFile(filePath); 
} 

這個靜態方法StaticClass.GetFile()怎麼會被嘲弄?

P.S.我很感謝您在Moq和Unit Testing上推薦的任何閱讀材料。

回答

38

像Moq或Rhinomocks這樣的模擬框架只能創建對象的模擬實例,這意味着模擬靜態方法是不可能的。

您還可以search Google瞭解更多信息。

另外,以前在StackOverflow hereherehere上提出了幾個問題。

32

@ Pure.Krome:不錯的反響,但我會添加一些細節

@Kevin:你必須選擇取決於你能夠給代碼的變化的解決方案。
如果你可以改變它,一些依賴注入使代碼更加可測試。 如果你不能,你需要一個很好的隔離。
使用免費的嘲諷框架(Moq,RhinoMocks,NMock ...),您只能嘲笑委託,接口和虛擬方法。因此,對於靜態的,封閉和非虛擬方法,你有3個解決方案:

  • TypeMock隔離(可以嘲笑一切,但它的價格昂貴)
  • Telerik的的JustMock(新來的,比較便宜,但仍不 免費)
  • 微軟Moles(唯一免費的隔離解決方案)

我推薦Moles,因爲它是免費,高效的並且使用像Moq這樣的lambda表達式。只有一個重要的細節:痣提供存根,而不是嘲笑。所以,你仍然可以使用起訂量爲接口和代表;)

模擬:實現一個接口,允許動態設置,返回/異常的具體方法扔值的能力,並提供能力的類檢查是否有特定的方法被調用/未被調用。
存根(stub):就像一個模擬類,除了它不提供驗證方法被調用/未調用的能力。

+7

作爲更新此。痣現在被稱爲Fakes,並內置到Visual Studio 2012中:http://msdn.microsoft.com/en-us/library/hh549175.aspx – gscragg 2013-11-27 21:55:27

+2

在Moles中,你可以驗證一個方法是否被調用,只是添加一個布爾變量和在存根函數內設置它也可以驗證調用此工作週期中的所有參數。 – mart 2013-12-20 13:28:34

+0

嗨Jeco ...我從你上面的答案中得到了一些想法。您可以請回答一個問題在http://stackoverflow.com/questions/27621085/moles-shims-frameworks-other-than-introduced-by-microsoft – 2014-12-24 12:20:14

13

有可能在.NET中排除MOQ和任何其他嘲諷庫。您必須右鍵單擊包含要模擬的靜態方法的程序集解決方案資源管理器,然後選擇添加僞造程序集。接下來,你可以自由地模擬這是程序集靜態方法。

假設你想模擬System.DateTime.Now靜態方法。比如這樣做:

using (ShimsContext.Create()) 
{ 
    System.Fakes.ShimDateTime.NowGet =() => new DateTime(1837, 1, 1); 
    Assert.AreEqual(DateTime.Now.Year, 1837); 
} 

你對每個靜態屬性和方法都有類似的屬性。

+6

發現此問題的人的警告:MS Fakes僅內置於Visual Studio 2012+ Premium/Ultimate中。您可能無法將其整合到持續集成鏈中。 – thomasb 2015-06-05 16:26:30

+2

這應該是被接受的答案。 MS現在帶有墊片的假貨使得它很可能嘲笑靜態方法。 – Jez 2016-03-10 11:47:52

+0

非常小心使用MS假貨!假貨是一個重定向框架,它的使用很快導致糟糕的架構。只有在絕對必要時才使用Fakes,以攔截對第三方或(非常)遺留庫的調用。最好的解決方案是創建一個帶有接口的類封裝器,然後通過構造器注入或通過注入框架傳遞接口。模擬接口,然後將其傳遞到測試中的代碼中。如果可能的話,遠離假貨。 – 2017-01-17 17:53:26

-1

我知道這有點晚了,但是這個迂迴的解決方案讓我嘲笑使用Moq的靜態方法。

爲此,我創建了一個類(我們稱之爲Placeholder),其中一個方法稱爲靜態方法StaticClass.GetFile

public class Placeholder{ 

    //some empty constructor 

    public File GetFile(){ 

     File f = StaticClass.GetFile(filePath); 
     return f; 
    } 
} 

然後,而不是調用fooStaticClass.GetFile,我創建的Placeholder一個實例並稱爲GetFile功能。

public void foo(string filePath) 
{ 
    Placeholder p = new Placeholder(); 
    File f = p.GetFile(filePath); 
} 

現在,在單元測試,而不是試圖嘲笑StaticClass.GetFile,我可以嘲笑從Placeholder類的非靜態GetFile方法。

+0

缺少界面以及如何模擬 – 2018-02-19 11:04:44

0

我一直在玩一個重構靜態方法的概念來調用委託,您可以在外部爲測試目的設置一個委託。

這不會使用任何測試框架,而是一個完全定製的解決方案,但重構不會影響您的調用者的簽名,因此它將是一個相對安全的。

爲此,您需要訪問靜態方法,因此它不適用於任何外部庫,如System.DateTime

繼承人一個例子我一直在玩的地方,我創建了幾個靜態方法,一個返回類型接受兩個參數和一個沒有返回類型的泛型。

主要靜態類:

public static class LegacyStaticClass 
{ 
    // A static constructor sets up all the delegates so production keeps working as usual 
    static LegacyStaticClass() 
    { 
     ResetDelegates(); 
    } 

    public static void ResetDelegates() 
    { 
     // All the logic that used to be in the body of the static method goes into the delegates instead. 
     ThrowMeDelegate = input => throw input; 
     SumDelegate = (a, b) => a + b; 
    } 

    public static Action<Exception> ThrowMeDelegate; 
    public static Func<int, int, int> SumDelegate; 

    public static void ThrowMe<TException>() where TException : Exception, new() 
     => ThrowMeDelegate(new TException()); 

    public static int Sum(int a, int b) 
     => SumDelegate(a, b); 
} 

單元測試(的xUnit和Shouldly)

public class Class1Tests : IDisposable 
{ 
    [Fact] 
    public void ThrowMe_NoMocking_Throws() 
    { 
     Should.Throw<Exception>(() => LegacyStaticClass.ThrowMe<Exception>()); 
    } 

    [Fact] 
    public void ThrowMe_EmptyMocking_DoesNotThrow() 
    { 
     LegacyStaticClass.ThrowMeDelegate = input => { }; 

     LegacyStaticClass.ThrowMe<Exception>(); 

     true.ShouldBeTrue(); 
    } 

    [Fact] 
    public void Sum_NoMocking_AddsValues() 
    { 
     LegacyStaticClass.Sum(5, 6).ShouldBe(11); 
    } 

    [Fact] 
    public void Sum_MockingReturnValue_ReturnsMockedValue() 
    { 
     LegacyStaticClass.SumDelegate = (a, b) => 6; 
     LegacyStaticClass.Sum(5, 6).ShouldBe(6); 
    } 

    public void Dispose() 
    { 
     LegacyStaticClass.ResetDelegates(); 
    } 
}