【问题标题】:Difference in code execution when extension method present but not called扩展方法存在但未调用时代码执行的差异
【发布时间】:2014-02-17 09:53:35
【问题描述】:

TL;DR,问题:

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

背景

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

我注意到测试失败,并且偶然注意到只有在加载另一个测试程序集时才会发生失败。在单元测试和集成测试程序集上运行 mstest 将开始执行集成测试,并在 4.5 CLR 下的第 21 次集成测试中失败,而在 4.0 CLR 下不会发生这种情况(其他配置相同)。 我从集成测试程序集中删除了所有测试,但失败的测试除外。执行现在看起来像这样,加载了两个测试程序集,mstest 加载了两个程序集,然后执行集成测试程序集中的单个测试,但失败了。

> mstest.exe /testcontainer:Unittests.dll /testcontainer:IntegrationTests.dll

Loading C:\Proj\Tests\bin\x86\Release\Unittests.dll...
Loading C:\Proj\Tests\bin\x86\Release\Integrationtests.dll...
Starting execution...

Results               Top Level Tests
-------               ---------------
Failed                Proj.IntegrationTest.IntegrationTest21

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

> mstest.exe /testcontainer:IntegrationTests.dll

Loading C:\Proj\Tests\bin\x86\Release\Integrationtests.dll...
Starting execution...

Results               Top Level Tests
-------               ---------------
Passed                Proj.IntegrationTest.IntegrationTest21

我认为它一定是在 UnitTests dll 上执行的 [AssemblyInitialize] 事物,或者可能是 Unittest.dll 中的某种静态状态,或者是在加载测试程序集时修改的常见依赖项。我在 Unittests.dll 中找不到任何静态构造函数或程序集初始化。当包含 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 差异可能会导致差异,例如在浮点数。这就是我所看到的吗?

【问题讨论】:

  • 我不会说 Extensions 类会导致任何问题。它最终是语法糖,在编译时被替换:var1.SymmetricDifference(var2) 被替换为 IEnumerableExtension.SymmetricDifference(var1, var2)
  • 确切的错误信息或异常是什么?事件日志中有任何内容吗?如果您在测试中执行 Assert,报告失败的测试的实际代码是什么。
  • 事件日志中没有任何内容,唯一的例外是AssertFailed,它来自于测试中浮点结果的比较。我无法对问题进行最低限度的重现。
  • 如果在调试模式下编译和运行这些测试会发生什么?导致 Assert 失败的比较值是什么?您是否尝试过将SymmetricDifference 替换为简单的return thatthrow new NotImplementedException
  • 这个问题只出现在优化的构建中,只要加载了它的程序集,扩展方法的内容似乎并不重要。它永远不会被测试调用。

标签: c# extension-methods mstest jit


【解决方案1】:

可能是由于类型加载错误导致的问题。

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

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

这种行为的原因可能会有所不同。第一步是记录您在测试失败时遇到的确切异常。

【讨论】:

  • 感谢您的建议,但我没有看到任何异常(没有 TypeLoaderExceptions 等)。如果需要,我可以使用扩展方法,它可以按预期工作。测试失败看起来像“正常”测试失败,但有比较差异,可能是由浮点差异引起的。
  • mstest.exe 可以生成 .trx 文件。然后,您可以在测试失败后打开生成的 .trx 文件,查看您得到的确切错误和位置。
  • 这是一个简单的“正常”测试断言失败,例如“预期 4 个苹果,但找到 3 个”。测试结果无异常。
【解决方案2】:

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

显然推断出错误的框架。在执行测试时在命令行上传递 /noisolation 是一种解决方法。希望这可以解决您的问题。

【讨论】:

    【解决方案3】:

    两个想法...

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

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

    【讨论】:

    • 我知道优化会导致浮点行为的差异,尤其是在 x86 上(在抖动倾向于使用 SSE 的 x64 中较少)。我的问题不是“我该如何解决这个问题”(我可以避免测试),而是我很好奇为什么仅在 存在 i> 的扩展方法。我可以确定有/没有扩展的差异发生在哪里,那么问题是:为什么?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-02
    • 2014-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-17
    • 1970-01-01
    相关资源
    最近更新 更多