2011-05-03 102 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(); 
    } 
}