在面向对象编程中,模拟对象是一种重要的测试技术,它允许开发者在不依赖于复杂环境的情况下测试代码。模拟对象通常用于测试驱动开发(TDD)中,以便在开发过程中验证其他对象的行为。然而,为了使用这种技术,对象必须是“可模拟的”。在C#中,这通常意味着具有接口或抽象的实例对象,例如在NMock中使用的。静态对象和静态方法通常不可模拟。
PostSharp是一个强大的工具,它可以拦截任何方法或属性的使用。通过PostSharp,即使是静态方法和属性也可以被模拟,这在传统的模拟框架中是不可能的。PostSharp可以在不实际调用被拦截的方法或属性的情况下返回新的值。
PostSharp的工作原理是在编译时透明地修改代码,这意味着使用PostSharp编译的二进制文件与未使用PostSharp编译的二进制文件不同。这种差异可以通过工具如Reflector或ILDASM来揭示。幸运的是,PostSharp确保修改后的二进制文件与未修改的版本运行相同,并且更改的逻辑对调试器隐藏。
以下是一个简单的测试示例:
public class StaticProvider
{
[MockMethod]
public static bool Provide(int input)
{
throw new NotImplementedException("Static Provider Not Implemented");
}
}
[TestMethod]
[Description("With both methods mocked, the test should pass")]
public void AllSuccess()
{
TestApparatus.MockStatic<bool, int>(StaticProvider.Provide).Any().Return(true);
TestApparatus.MockInstance<InstancedProvider, bool, int>(i => i.Provide).Expecting(1).If(a1 => a1 >= 0).Return(true);
SampleObject sample = new SampleObject();
sample.Methods();
}
在上述代码中,第5行告诉框架拦截对静态方法StaticProvider.Provide(int)的任何调用,并返回true。第6行让框架拦截实例方法InstancedProvider.Provide(int),当输入值大于0时返回true。它还告诉框架在测试期间只期望一次调用。
以下示例展示了如何模拟属性:
[TestMethod]
[Description("Mocking a specific property")]
public void MockingProperty()
{
const int mocked = 12034;
TestApparatus.MockProperty(() => StaticProvider.Value).Any().Return(mocked);
TestApparatus.MockProperty((InstancedProvider i) => i.Value).Expecting(1).Any().Return(mocked);
SampleObject sample = new SampleObject();
Assert.AreEqual(mocked, sample.Values());
}
第6行模拟静态属性StaticProvider.Value,使其在任何时候都返回模拟值。第7行模拟实例属性InstancedProvider.Value,使其在任何时候都运行模拟值。
在框架内部,所有模拟的方法和属性都被内部拦截,但只有符合指定条件的那些被覆盖。框架所做的只是记录拦截的要求,并在运行时执行。
该项目使用Visual Studio 2008和.Net 3.5完成,已验证在Visual Studio 2012下无需任何更改即可工作。必须安装PostSharp,并且可能需要刷新对PostSharp库的引用。
使用PostSharp进行模拟的一个关键点是,它需要模拟的方法和属性的源代码。这意味着二进制文件在编译时被PostSharp修改。然而,PostSharp保证了修改后的二进制文件与未修改的版本运行相同,并且更改的逻辑对调试器隐藏。
另一个问题是,被测试的二进制文件与要部署的二进制文件不同。它们是等效的,这是PostSharp保证的。因此,主要的担忧是不要将测试版本部署到生产环境中。在提供的框架中,MockMethodAttribute和MockPropertyAttribute包含条件语句,以在非调试构建中将自己更改为简单的属性。PostSharp不会识别这些简单的属性,因此生产二进制文件保持不变。这有助于将测试二进制文件与生产二进制文件分开。
与基于接口的模拟测试框架(如NMock和Rhino Mock)相比,这种方法可以自由地模拟任何静态对象。测试目的不需要接口,这对于最初没有为模拟测试设计的代码来说是一个解脱。与基于分析器的模拟测试框架(如JustMock)相比,这种方法在性能上有优势,因为只有带有属性的方法和属性才会触发拦截。