65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 PostSharp 模拟方法和属性

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (1投票)

2012 年 8 月 14 日

CPOL

4分钟阅读

viewsIcon

20163

downloadIcon

128

使用 PostSharp 无需接口,模拟静态方法和属性

引言

首先,背景介绍。引用维基百科的话:“在面向对象编程中,模拟对象是模拟对象的仿真对象,它们以可控的方式模仿真实对象的行为。程序员通常会创建一个模拟对象来测试其他对象的行为”。它是测试驱动开发中一种重要的技术。 然而,要使用这项技术,对象必须是“可模拟的”。在 C# 中,这通常意味着带有接口或抽象的实例对象,例如 NMock 中使用的。静态对象和静态方法通常是不可模拟的。 

借助 PostSharp,模拟任何有值的东西都成为可能,这包括返回值的方法以及实例或静态的属性。即使对象本身不是字面上“模拟”的,如果它的所有或部分属性和方法都被模拟,那么它就等同于被模拟了。 

对于不太熟悉 PostSharp 的人来说,它是一种可以拦截任何方法或属性的使用的工具。在不实际调用被拦截的方法或属性的情况下,可以返回新值。PostSharp 提供了更多功能,这是他们网站的链接:http://www.sharpcrafters.com/。  

使用代码

要使用文章附带的小型框架,典型的测试步骤如下:

  1. 将 MockMethod 属性附加到目标方法,将 MockProperty 附加到目标属性。
  2. 通过添加拦截条件和拦截时的返回值来设置 TestApparatus。
  3. 编写涉及这些被模拟方法和属性的测试,然后运行。

以下是一个非常简单的测试:

1    public class StaticProvider 
2    { 
3        [MockMethod]
4        public static bool Provide(int input)
5        {
6            throw new NotImplementedException("Static Provider Not Implemented");
7        } 

第 3 行将第 4 行的 Provide 方法标记为可模拟方法。

1        [TestMethod]
2        [Description("With both methods mocked, the test should pass")]
3        public void AllSuccess()
4        {
5            TestApparatus.MockStatic<bool, int>(StaticProvider.Provide).Any().Return(true);
6            TestApparatus.MockInstance<InstancedProvider, bool, int>(i => i.Provide).Expecting(1).If(a1 => a1 >= 0).Return(true);
7            SampleObject sample = new SampleObject();
8            sample.Methods();
9        } 

第 5 行告诉框架拦截对静态方法 StaticProvider.Provide(int) 的任何调用,并返回 true。

第 6 行让框架在输入值大于 0 时拦截实例方法 InstancedProvider.Provide(int),然后返回 true。它还告诉框架在测试期间只期望一次调用。

以下示例显示了如何模拟属性。 

1        [TestMethod] 
2        [Description("Mocking a specific property")]
3        public void MockingProperty()
4        {
5            const int mocked = 12034;
6            TestApparatus.MockProperty(() => StaticProvider.Value).Any().Return(mocked);
7            TestApparatus.MockProperty((InstancedProvider i) => i.Value).Expecting(1).Any().Return(mocked);
8            SampleObject sample = new SampleObject();
9            Assert.AreEqual(mocked, sample.Values());
10       } 

第 6 行模拟静态属性 StaticProvider.Value,以便随时返回模拟值。

第 7 行模拟实例属性 InstancedProvider.Value,以便随时运行模拟值。

框架内部发生的事情相当直接。所有被模拟的方法和属性都会被内部拦截,但只有那些符合指定条件的才会被覆盖。该框架所做的就是记录拦截的要求并在运行时执行它们。

该项目是使用 Visual Studio 2008 和 .Net 3.5 完成的。已验证该项目在 Visual Studio 2012 下无需任何更改即可运行。必须按顺序安装 PostSharp,并且可能需要刷新 PostSharp 库的引用。  

关注点

这种基于 PostSharp 的模拟方法需要被模拟的方法和属性的源代码。代码在编译期间被 PostSharp 透明地修改。也就是说,二进制文件与未编译 PostSharp 的二进制文件不同。使用 Reflector 或 ILDASM 可以发现这些差异。好消息是,得益于 PostSharp,修改后的二进制文件运行起来与未修改的版本相同,并且更改的逻辑对调试器是隐藏的。那么,自然地,人们会问如何模拟没有源代码的系统方法,例如 int.Parse()。一种解决方案是围绕系统方法编写一个包装方法,并使新方法可模拟。将所有调用重定向到新方法而不是原始方法。同时围绕新方法而不是原始方法设置测试。下面显示了一个示例。

1       [MockMethod] 
2       public int Parse(string s)
3       {
4           return int.Parse(s);
5       }
6       [TestMethod]
7       public void Test()
8       {
9           TestApparatus.MockInstance<Tests, int, string>(t => t.Parse).Any().Return(0);
10      }  

这种修改二进制文件的方法的另一个问题是,被测试的二进制文件不是要部署的二进制文件。它们彼此等价,正如 PostSharp 所保证的。因此,顾虑或多或少是不要将测试版本部署到生产环境中。在提出的框架中,MockMethodAttribute 和 MockPropertyAttribute 包含条件语句,以便在非调试构建中将它们更改为简单的属性。PostSharp 将不会识别这些简单的属性,因此将保持生产二进制文件不变。这有助于区分测试二进制文件和生产二进制文件。

总之,与基于接口的模拟测试框架(如 NMock 和 Rhino Mock)相比,这种方法可以自由地模拟任何静态内容。测试目的无需接口,这对于最初未针对模拟测试设计的代码来说是一种解脱。与基于性能分析器的模拟测试框架(如 JustMock)相比,这种方法在性能方面具有优势,因为只有带有属性的方法和属性才会触发拦截。

结语

这个发布的示例只是一个小型演示项目,用于展示如何进行模拟。但是,对于任何有兴趣的人来说,它包含了足够的信息来构建一个完整的测试框架。一些有用的补充可能包括:拦截对象创建、在拦截时执行自定义逻辑而不是返回简单值。 

感谢阅读。

© . All rights reserved.