存在扩展方法但未调用时代码执行的差异



TL;DR,问题:

扩展方法在.NET中的存在会对代码的执行产生什么影响(例如JIT/优化)?

背景

我在MSTest中遇到了一个测试失败,这取决于是否也测试了一个看似不相关的程序集。

我注意到了测试失败,并意外地注意到,只有在加载另一个测试组件时才会发生故障。在Unittests和Integration测试程序集上运行mstest将开始执行集成测试,并在4.5 CLR下的第21次集成测试中失败,而在4.0 CLR下则不会发生这种情况(其他配置相同)。我从集成测试程序集中删除了所有测试,但没有删除失败的测试。两个测试程序集都已加载,mstest加载两个程序集,然后在集成测试程序集中执行单个测试,结果失败。

> mstest.exe /testcontainer:Unittests.dll /testcontainer:IntegrationTests.dll
Loading C:ProjTestsbinx86ReleaseUnittests.dll...
Loading C:ProjTestsbinx86ReleaseIntegrationtests.dll...
Starting execution...
Results               Top Level Tests
-------               ---------------
Failed                Proj.IntegrationTest.IntegrationTest21

如果执行中没有Unittests程序集,则测试通过。

> mstest.exe /testcontainer:IntegrationTests.dll
Loading C:ProjTestsbinx86ReleaseIntegrationtests.dll...
Starting execution...
Results               Top Level Tests
-------               ---------------
Passed                Proj.IntegrationTest.IntegrationTest21

我认为它一定是在UnitTests dll上执行的[AssemblyInitialize],或者Unittest.dll中的某种静态状态,或者加载测试程序集时修改的公共依赖项。我在Unittests.dll中既没有找到任何静态构造函数,也没有找到程序集init。当包含Unittests程序集时,我怀疑存在部署差异(在不同版本中部署的依赖程序集等),但我比较了通过/失败的部署目录,它们是二进制等效的。

那么Unittests程序集的哪个部分导致了测试差异呢?从单元测试中,我一次删除一半的测试,直到将其深入到单元测试程序集中的源文件。除了测试类,还声明了一个扩展方法:

除了这个扩展类之外,Unittest程序集现在在一个伪测试类中包含一个测试用例。只有当我有一个伪测试方法声明的扩展方法时,才会发生测试失败。我可以删除所有剩余的测试逻辑,直到Unittest dll成为一个单独的文件,包含以下内容:

// DummyTest.cs in Unittests.dll
[TestClass]
public class DummyTest
{
[TestMethod]
public void TestNothing()
{
}
}
public static class IEnumerableExtension
{
public static IEnumerable<T> SymmetricDifference<T>(
this IEnumerable<T> @this,         
IEnumerable<T> that) 
{
return @this.Except(that).Concat(that.Except(@this));
}
}

如果测试方法扩展类被删除,则测试通过。两者都存在,并且测试失败。

两个程序集都没有对扩展方法进行调用,在执行集成测试之前(据我所知),Unittests程序集中也没有执行任何代码。

我确信集成测试足够复杂,优化中的JIT差异可能会导致差异,例如浮点。这就是我看到的吗?

问题可能是由于类型加载错误引起的。

CLR运行时加载类或方法时,它总是检查这些项中使用的所有类型。是否实际调用了类型/方法并不重要。重要的是申报的事实。返回到您的示例,扩展方法SymmetricDifference声明它使用System.Core程序集中的ExceptConcat方法。

从System.Core程序集加载类型System.Linq.Enumerable时出错。

这种行为的原因可能各不相同。要采取的第一步是记录测试失败时出现的确切异常。

我确实发现了这个:https://connect.microsoft.com/VisualStudio/feedback/details/792429/some-unit-tests-fail-with-visual-studio-2012-agent-update3-on-windowsxp-sp3

显然推断出了错误的框架。在执行测试时,在命令行上传递/noisolation被认为是一种变通方法。希望这能解决你的问题。

两个想法。。。

  1. 浮点数学和比较实际上可以在有/没有优化的情况下给出不同的结果。至少它们在C++中可以。它们在不同的CPU架构上也可以,而且肯定会有所不同。一般来说,让单元测试基于浮点比较来评估通过/失败听起来是个坏主意。例如,你的朋友在运行x64 windows时没有通过单元测试,而你成功了。

  2. 这个扩展方法的问题简直是小题大做。那么,如果你去掉它,它会神秘地起作用呢?优化后的代码有时会做一些疯狂的事情,最终的问题是你的浮点数与未优化时不同。如果差异不可避免,则添加一个epsilon,否则在计算过程中的每一步都记录其值并跟踪它。

最新更新