嘲笑静态方法

最近,我开始使用Moq进行unit testing。 我使用Moq来嘲笑那些我不需要testing的类。

你通常如何处理静态方法?

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

这个静态方法, StaticClass.GetFile()被嘲弄?

PS我会很感激您在Moq和unit testing中推荐的任何阅读材料。

像Moq或Rhinomocks这样的模拟框架只能创build对象的模拟实例,这意味着模拟静态方法是不可能的。

您也可以searchGoogle以获取更多信息。

另外, 这里和这里还有一些以前在StackOverflow上提出的问题。

@ Pure.Krome:好的回应,但我会添加一些细节

@凯文:你必须根据你可以带给代码的变化来select一个解决scheme。
如果你可以改变它,一些dependency injection使代码更可testing。 如果你不能,你需要一个很好的隔离。
通过免费的嘲讽框架(Moq,RhinoMocks,NMock …),您只能嘲笑委托,接口和虚拟方法。 因此,对于静态,密封和非虚拟方法,您有3个解决scheme:

  • TypeMock隔离器 (可以模拟一切,但价格昂贵)
  • Telerik JustMock (新来者,更便宜,但仍然不免费)
  • 微软 (唯一免费的隔离解决scheme)

我推荐Moles ,因为它是免费的,高效的,使用像Moq这样的lambdaexpression式。 只是一个重要的细节:痣提供存根,而不是嘲笑。 所以你可能仍然使用Moq作为接口和代表;)

模拟(Mock):一个实现了接口的类,允许dynamic设置值的返回/exception,从特定方法抛出,并提供检查特定方法是否被调用/未调用的能力。
存根:就像一个模拟类,除了它没有提供validation方法被调用/未被调用的能力。

在.NET中有可能不包括MOQ和任何其他嘲笑库。 您必须右键单击包含您想要模拟的静态方法的程序集解决scheme资源pipe理器,然后select添加伪装程序集 。 接下来你可以自由地模拟这个程序集的静态方法。

假设你想模拟System.DateTime.Now静态方法。 比如这样做:

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

每个静态属性和方法都有类似的属性。

我知道这有点晚了,但是这个迂回的解决scheme让我嘲笑一个使用Moq的静态方法。

为此,我创build了一个类(我们称之为Placeholder ),其中一个方法称为静态方法StaticClass.GetFile

 public class Placeholder{ //some empty constructor public File GetFile(){ File f = StaticClass.GetFile(filePath); return f; } } 

然后,我没有在foo中调用StaticClass.GetFile ,而是创build了一个Placeholder实例,并调用GetFile函数。

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

现在,在unit testing中,我没有试图模拟StaticClass.GetFile ,而是从Placeholder类中模拟了非静态的GetFile方法。

我一直在玩弄一个重构静态方法的概念来调用一个委托,你可以在外部为testing目的设置一个委托。

这不会使用任何testing框架,将是一个完全定制的解决scheme,但重构不会影响您的调用者的签名,所以这将是一个相对安全的。

为此,您需要访问静态方法,因此它不适用于任何外部库,如System.DateTime

下面是我一直在玩的一个例子,我创build了几个静态方法,一个是带有两个参数的返回types,另一个是没有返回types的generics。

主要的静态类:

 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); } 

unit testing(xUnit和应该)

 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(); } }