Moq和Interop类型:在VS2012中工作,在VS2010中失败?

我有一个.NET库项目,大约有500个unit testing。 所有这些测试在Visual Studio 2012中运行良好。但是,我的一些测试在Visual Studio 2010中失败。在这些失败的测试中,我使用MoqMicrosoft.Office.Interop.Excel模拟几个互操作类型。 尝试访问这些模拟的互操作类型时,测试立即失败:

 Error: Missing method 'instance class Microsoft.Office.Interop.Excel.Range [ExcelAddIn.Core] Microsoft.Office.Interop.Excel.ListRow::get_Range()' from class 'Castle.Proxies.ListRowProxy'. 

这个exception意味着我忘了在mock上设置适当的属性getter。 事实并非如此:

 _listRowMock.Setup(m => m.Range).Returns(_rangeMock.Object); 

现在我可以想象Moq对于Interop类型可能效果不佳。 但我发现最令人费解的是,这些测试在Visual Studio 2012中运行良好,但在Visual Studio 2010中失败。

为什么我的Visual Studio会影响我的代码行为?

更新:2012年3月11日

好的,所以我明白了:

  • 我有两个项目; Core和Core.UnitTest。 Core是实际的库,而Core.UnitTest是Core库的unit testing项目。
  • 这两个项目都引用了Microsoft.Office.Interop.Excel并启用了嵌入互操作类型。
  • 因为启用了EIT,所以这两个项目都包含自己的Microsoft.Office.Interop.Excel库“视图”。 该视图包括其各自项目中使用的所有类,方法和属性。
  • 由于两个项目都使用Microsoft.Office.Interop.Excel的不同类,方法和属性,因此两个库的嵌入类型不同。 例如,Core中的ListRow具有Index和Range属性,而Core.UnitTest中的ListRow仅具有Range属性。
  • 虽然两种类型都不同,并且不共享公共接口或超类,但它们是等效的 。 这意味着CLR会将它们视为相同,并允许您跨组装边界使用这些类型。 例如,Core.UnitTest中的ListRow实例在传递给Core库中的方法时可以正常工作。 共享的Range属性将起作用,而缺少的Index属性将在访问时抛出MissingMethodException。
  • 上述行为甚至适用于模拟类型。 Mock [Excel.ListRow]的模拟对象在穿过assembly边界时可以正常工作。
  • 不幸的是,前一点中描述的行为仅在我在Visual Studio 2012中构建程序集时才有效。 当我在Visual Studio 2010中构建程序集并调试我的代码时,我可以看到模拟的ListRow实例被传递到我的Core项目的方法中。 实例越过程序集边界的那一刻,ListRow的所有方法和属性都会丢失它们的实现并抛出MissingMethodExceptions。
  • 现在,对于有趣的部分,我实际上通过确保两个嵌入类型的ListRow对齐来设法缓解此问题。 例如,为了让编译器在两个项目中创建相同的ListRow视图,我确保在UnitTest项目中使用了完全相同的方法和属性。 这意味着添加虚拟行,如:var dummy = listRow.Index。 一旦我让编译器创建了我的嵌入式ListRow类型的相同视图,就允许该实例跨越程序集边界而不会丢失其实现。

问题仍然存在:导致Visual Studio 2010和Visual Studio 2012之间的行为差​​异的原因是什么?

更新:2012年9月11日

演示解决方案 : http : //temp-share.com/show/KdPf6066h

我已经创建了一个小解决方案来演示效果。 该解决方案包含一个库和一个UnitTest项目。 两者都引用了Microsoft.Office.Interop.Excel.Range并启用了EIT。 该测试在VS2012中运行正常但在VS2010中抛出MissingMethodException。 在测试中取消注释虚拟线将使其在VS2010中工作。

最终更新:2012年12月29日

我为最新的更新道歉。 我的一位同事找到了解决方案,但我无法在我的机器上重现它。 与此同时,我们公司已经转向TFS2012,所以这不再是我的阻塞问题。 我的同事做出的两个最重要的结论是:

  • “Any CPU”平台的语义已从Visual Studio 2010更改为Visual Studio 2012.这将导致生成不同的.DLL,具体取决于您使用的是VS2010还是VS2012。
  • 这两个项目都引用了Microsoft.Office.Interop.Excel的不同版本。

我检查了我的项目并理顺了参考文献,但没有任何区别。 之后,我在VS2010和VS2012中尝试了不同版本的平台,但无法产生令人满意的结果。 我会接受杰里米的回答,因为这是最有帮助的。 谢谢大家的帮助。

编辑:当我在Visual Studio 2012中尝试并且目标.Net 4.0时,它适用于我,仅使用.Net PIA而不是COM参考。 相同的解决方案在VS2010中不起作用。

VS2010加载版本10.0.30319.1的Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll's和VS2012加载版本11.0.50727.1。 您可以在“模块”窗口中看到不同的版本。


我设法让它在VS2010中运行:

在此处输入图像描述

这是我的解决方案http://temp-share.com/show/Pf3Ypip62 ,方便大家。 它包含所有Moq参考。 我有Excel 2007(即v12) – 所以请调整对Office 14的引用。

具有待测试方法的项目必须通过.Net参考选项卡使用PIA Microsoft.Office.Interop.Excel

在unit testing项目中,您必须通过COM参考选项卡使用Microsoft Excel 1X.0 Object Library – 它是一个ActiveX。

令人困惑的是,在解决方案资源管理器中,它们都被称为:Microsoft.Office.Interop.Excel

还有一个警告,我不知道如何解决方法 – 你必须使用.Net 3.5框架,我实际上希望微软在2012年修复它,因为我找不到如何在.Net 4.0中使用所有项目。 针对.Net 3.5和4.0的混合项目的一些解决方案是可以的。

我遇到了很多麻烦,请参阅此处如何在模拟Excel.worksheet时避免使用动态? 并且我也问过这个问题: Mocked对象没有Intellisense中显示的所有属性 – 在一个项目中但在另一个项目中有它们 。

无论如何,这是如何让它在VS 2010中运行。我很高兴它在2012年得到解决!

我试图重现这一点,对我来说,即使在VS 2012中它也不起作用。

当您使用“嵌入互操作类型”编译项目时,C#编译器会生成一个只有您正在访问的成员的内部类型,并且实现似乎实际上似乎使用IDispatch通过id调用COM对象的方法。

根据您的描述,我了解到您在VS 2012 中的测试项目访问属性(甚至不是为了模拟它们),但测试仍然成功,测试项目生成的类型确实有这些成员。

如果这确实是您遇到的问题,请查看测试内容.dll并查看互操作类型是如何生成的? 您可以使用ildasm.exe等工具。

如果测试.dll中的互操作类型包含所有成员,甚至是那些在测试中不访问的成员,这可能会给出一个线索,即差异与VS 2012中生成互操作类型的方式有关。

此外,如果您可以附加一个小的最小VS 2012解决方案来重现问题,它可能会有助于诊断这个问题。

首先检查在VS2010中向项目添加库时,确保创建了模拟对象

 Mock _mock = new Mock(); 

此外,.NET 4.0允许将主互操作程序集嵌入到程序集中,这样您就不需要将它们与应用程序一起部署。在VS2010中打开程序集中的属性选项卡并检查嵌入式互操作类型.mkae确定它是真的。

并实例化excel, Excel.Application xlapp = new Excel.Application();

希望它能起作用..

我发现,至少在VS2015中,我仍然可以在我测试的程序集中嵌入interop类型,但是在我的测试项目中将PIA程序集引用上的“Embed”设置为false,我没有重复这个。