嘲讽不可嘲讽:在 Gallio 中使用 Microsoft Moles





0/5 (0投票)
更新 29/04/10:与我最初在这篇博文中声明的相反,Moles 不仅作为 Pex 框架的一部分可用,而且还可以在 Visual Studio Gallery 上独立使用,并且完全免费(无需 MSDN 订阅)。 - 感谢 Peli 的更正……
更新 29/04/10:
与我最初在这篇博文中声明的相反,Moles 不仅作为 Pex 框架的一部分可用,而且还可以在 Visual Studio Gallery 上独立使用,并且完全免费(无需 MSDN 订阅)。 - 感谢 Peli 的更正……
通常的开源模拟框架(例如 Moq 或 Rhino.Mocks)只能模拟接口和虚方法。与此相反,Microsoft 的 Moles 框架可以‘模拟’几乎任何东西,因为它使用运行时插桩在被模拟方法的 MSIL 主体中注入回调。因此,可以拦截任何 .NET 方法,包括密封类型中的非虚/静态方法。当处理调用 .NET 框架、某些第三方或遗留代码等的代码时,这可能会非常有帮助……
可以在我的 Delicious 工具箱的此链接中找到一些有用的收集资源(指向网站、文档材料和一些视频):
http://delicious.com/thomasweller/toolbox+moles
Moles 的 Gallio 扩展
最初,Moles 是 Microsoft Pex 框架的一部分,因此与 Visual Studio Unit Tests (MSTest) 集成得最好。然而,Moles 示例下载包含一些额外的程序集,也支持其他单元测试框架。它们提供了一个Moled特性,以简化与相应框架的 mole 类型的用法(示例中包含 NUnit、xUnit.net 和 MbUnit v2 的扩展)。由于没有适用于 Gallio 平台的此类扩展,我亲自动手编写了几行代码 - 产生的Gallio.Moles.dll包含在示例下载中。
有了这个小程序集,就可以像这样在 Gallio 中使用 Moles 了
[Test, Moled]
public void SomeTest()
{
...
你可以用它做什么
如果你需要‘模拟’除虚方法或实现接口的方法之外的东西,Moles 会非常有帮助。这可能是在处理某些第三方组件、遗留代码时,或者如果你想‘模拟’ .NET 框架本身。
通常,你需要使用MoledType特性在程序集级别声明要使用的每个被模拟类型。例如
[assembly: MoledType(typeof(System.IO.File))]
以下是一些 Moles 的典型用例。有关更详细的概述(包括命名约定和创建所需 moles 程序集的说明),请参阅上面的参考资料。
拦截 .NET 框架
想象一下,你想测试一个如下所示的方法,该方法内部调用了一个框架方法:
public void ReadFileContent(string fileName)
{
this.FileContent = System.IO.File.ReadAllText(fileName);
}
使用 mole,你会像这样用一个运行时委托替换对File.ReadAllText(string)方法的调用
[Test, Moled]
[Description("This 'mocks' the System.IO.File class with a custom delegate.")]
public void ReadFileContentWithMoles()
{
// arrange ('mock' the FileSystem with a delegate)
System.IO.Moles.MFile.ReadAllTextString =
(fname => fname == FileName ? FileContent : "WrongFileName");
// act
var testTarget = new TestTarget.TestTarget();
testTarget.ReadFileContent(FileName);
// assert
Assert.AreEqual(FileContent, testTarget.FileContent);
}
拦截静态方法和/或类
像下面这样的静态方法……
public static string StaticMethod(int x, int y)
{
return string.Format("{0}{1}", x, y);
}
……可以用以下方式‘模拟’
[Test, Moled]
public void StaticMethodWithMoles()
{
MStaticClass.StaticMethodInt32Int32 = ((x, y) => "uups");
var result = StaticClass.StaticMethod(1, 2);
Assert.AreEqual("uups", result);
}
拦截构造函数
你甚至可以用类构造函数执行这种委托操作。它的语法不是很直观,因为你必须设置 mole 的内部状态,但总的来说它运行得非常好。例如,要替换这个构造函数……
public class ClassWithCtor
{
public int Value { get; private set; }
public ClassWithCtor(int someValue)
{
this.Value = someValue;
}
}
……你会这样做
[Test, Moled]
public void ConstructorTestWithMoles()
{
MClassWithCtor.ConstructorInt32 =
((@class, @value) => new MClassWithCtor(@class) {ValueGet = () => 99});
var classWithCtor = new ClassWithCtor(3);
Assert.AreEqual(99, classWithCtor.Value);
}
拦截抽象基类
你也可以使用这种方法来‘模拟’你在测试中调用的类的抽象基类。假设你有这样的东西
public abstract class AbstractBaseClass
{
public virtual string SaySomething()
{
return "Hello from base.";
}
}
public class ChildClass : AbstractBaseClass
{
public override string SaySomething()
{
return string.Format(
"Hello from child. Base says: '{0}'",
base.SaySomething());
}
}
然后你会像这样设置子类的底层基类
[Test, Moled]
public void AbstractBaseClassTestWithMoles()
{
ChildClass child = new ChildClass();
new MAbstractBaseClass(child)
{
SaySomething = () => "Leave me alone!"
}
.InstanceBehavior = MoleBehaviors.Fallthrough;
var hello = child.SaySomething();
Assert.AreEqual("Hello from child. Base says: 'Leave me alone!'", hello);
}
将 moles 的行为设置为MoleBehaviors.Fallthrough如果未显式提供相应的委托,则会导致调用‘原始’方法 - 在这里,它会导致ChildClass’覆盖的SaySomething()方法被调用。
Moles 框架可能在许多场景中都很有帮助(例如,它也可以拦截接口实现,如IEnumerable<T>等等……)。我想到的另一个可能性是(因为我目前正在处理它),是用委托替换仓库类对 ADO.NET Entity Framework O/R 映射器的调用,以将仓库类与底层数据库隔离开,否则这将是不可能的……
用法
由于 Moles 依赖于运行时插桩,因此 mole 类型必须在 Pex 探查器下运行。如果你使用 MSTest (Visual Studio Unit Test) 编写测试,这在 Visual Studio 中才有效。虽然通常可以使用其他单元测试框架与 Moles 一起使用,但它们要求通过命令行运行相应的测试,通过moles.runner.exe工具执行。典型的测试执行将类似于此
moles.runner.exe <mytests.dll> /runner:<myframework.console.exe> /args:/<myargs>
因此,被模拟的测试可以通过 NCover 等工具或脚本工具(如 MSBuild)运行(这使得它们易于在持续集成环境中运行),但它们在常规 TDD 工作流程中(我在此详细描述过)运行起来有些不方便。为了让这个过程更流畅一些,我编写了一个 ReSharper 实时模板来生成相应的命令行(它也包含在示例下载中 -moled_cmd.xml)。 - 这只是一个快速简单的‘解决方案’。也许编写一个额外的 Gallio 适配器插件(类似于已提供的许多其他插件)并将其包含在 Gallio 下载包中是有意义的,如果对此有足够的需求的话。
目前,从 Visual Studio 中运行带有 Moles 框架的测试的唯一方法是将其与 MSTest 一起使用。从命令行,任何带有托管控制台运行器的东西都可以使用(前提是已安装相应的扩展)……
一个典型的 Gallio/Moles 命令行(由提到的 R# 模板生成)看起来像这样
"%ProgramFiles%\Microsoft Moles\bin\moles.runner.exe" /runner:"%ProgramFiles%\Gallio\bin\Gallio.Echo.exe" "Gallio.Moles.Demo.dll" /args:/r:IsolatedAppDomain /args:/filter:"ExactType:TestFixture and Member:ReadFileContentWithMoles"
-- 注意:在使用 Echo(Gallio 的控制台运行器)的命令行时,请务必始终包含IsolatedAppDomain 选项,否则测试将无法使用插桩回调! --
许可问题
正如我已经说过的,免费的模拟框架只能模拟接口和虚方法。如果你想模拟其他东西,你需要 Typemock Isolator 工具,它需要支付许可费用(尽管与此类工具为软件项目带来的价值相比,这些‘成本’非常低,但在现实生活中,花钱通常是一个相当大的障碍)。Moles 框架也不是完全免费的,但它与(密切相关的)Pex 框架具有相同的许可条件:仅限学术/非商业用途免费,要在‘真实’软件项目中使用它需要 MSDN 订阅(从 VS2010pro 开始)。
演示解决方案
示例解决方案(VS 2008)可在此处下载。它包含Gallio.Moles.dll提供此处所述的Moled特性,以及上述 R# 模板(moled_cmd.xml) 和包含上述用例场景的测试夹具。要运行它,你需要安装 Gallio 框架(下载)和 Microsoft Moles(下载)在默认位置。
祝测试愉快……
<iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&Task=Get&PageID=31016&SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No> <script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&Task=Get&Browser=NETSCAPE4&NoCache=True&PageID=31016&SiteID=1"></script> <noscript>