最近,我开始使用Moq进行单元测试。我使用Moq来模拟不需要测试的类。
您通常如何处理静态方法?
public void foo(string filePath)
{
File f = StaticClass.GetFile(filePath);
}
怎么会StaticClass.GetFile()
嘲笑这个静态方法?
附言:对于您建议的最小起订量和单元测试,我将不胜感激。
最近,我开始使用Moq进行单元测试。我使用Moq来模拟不需要测试的类。
您通常如何处理静态方法?
public void foo(string filePath)
{
File f = StaticClass.GetFile(filePath);
}
怎么会StaticClass.GetFile()
嘲笑这个静态方法?
附言:对于您建议的最小起订量和单元测试,我将不胜感激。
Answers:
Moq或Rhinomocks之类的模拟框架只能创建对象的模拟实例,这意味着无法模拟静态方法。
您也可以在Google上搜索更多信息。
@ Pure.Krome:反应良好,但我会补充一些细节
@Kevin:您必须根据可以对代码进行的更改选择解决方案。
如果可以更改它,则进行一些依赖注入可以使代码更具可测试性。如果不能,则需要良好的隔离。
使用免费的模拟框架(Moq,RhinoMocks,NMock ...),您只能模拟委托,接口和虚拟方法。因此,对于静态,密封和非虚拟方法,您有3种解决方案:
我推荐Moles,因为它是免费,高效的,并使用像Moq这样的lambda表达式。只是一个重要的细节:les鼠提供存根,而不是模拟。因此,您仍然可以使用Moq作为接口和委托;)
Mock:实现接口的类,并允许动态设置值以返回/从特定方法抛出异常,并提供检查是否已调用/不调用特定方法的能力。
存根:与模拟类类似,不同之处在于它不提供验证方法是否已被调用的能力。
.NET中有可能排除MOQ和任何其他模拟库。您必须在包含要模拟的静态方法的程序集上右键单击解决方案资源管理器,然后选择“添加假程序集”。接下来,您可以自由地模拟汇编静态方法。
假设您要模拟System.DateTime.Now
静态方法。例如,以这种方式执行此操作:
using (ShimsContext.Create())
{
System.Fakes.ShimDateTime.NowGet = () => new DateTime(1837, 1, 1);
Assert.AreEqual(DateTime.Now.Year, 1837);
}
每个静态属性和方法都具有相似的属性。
您可以使用nuget提供的Pose库来实现。它使您可以模拟静态方法。在您的测试方法中编写以下代码:
Shim shim = Shim.Replace(() => StaticClass.GetFile(Is.A<string>()))
.With((string name) => /*Here return your mocked value for test*/);
var sut = new Service();
PoseContext.Isolate(() =>
result = sut.foo("filename") /*Here the foo will take your mocked implementation of GetFile*/, shim);
有关更多信息,请参见此处https://medium.com/@tonerdo/unit-testing-datetime-now-in-c-without-using-interfaces-978d372478e8
我一直在研究重构静态方法以调用委托的概念,您可以在外部为测试目的设置该委托。
这不会使用任何测试框架,而是完全定制的解决方案,但是重构不会影响您呼叫者的签名,因此这是相对安全的。
为此,您需要访问静态方法,因此它对于诸如的任何外部库均无效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和应有)
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();
}
}