4

我有以下两个单元测试:一个使用 MSTest,另一个使用机器规格。据我所知,它们的行为应该相同。但是,虽然第一个在 NCrunch 和 ReSharper 测试运行程序中都通过了,但第二个在 ReSharper 中失败了。

using Machine.Specifications;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;

public class TestModel
{
    public string Name { get; set; }
    public int Number { get; set; }
}

// MSTest
[TestClass]
public class DeserializationTest
{
    [TestMethod]
    public void Deserialized_object_is_the_same_type_as_the_original()
    {
        TestModel testModel = new TestModel() {Name = "John", Number = 42};
        string serialized = JsonConvert.SerializeObject(testModel, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects });

        object deserialized = JsonConvert.DeserializeObject(serialized, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects });

        // This passes in both test runners
        Assert.IsInstanceOfType(deserialized, typeof(TestModel));
    }
}

// MSpec
public class When_an_object_is_deserialized
{
    static TestModel testModel;
    static string serialized;
    static object deserialized;

    Establish context = () =>
    {
        testModel = new TestModel() { Name = "John", Number = 42 };
        serialized = JsonConvert.SerializeObject(testModel, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects });
    };

    Because of = () => deserialized = JsonConvert.DeserializeObject(serialized, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects });

    // This passes in NCrunch but fails in ReSharper.
    It should_be_the_same_type_as_the_original = () => Assert.IsInstanceOfType(deserialized, typeof(TestModel));
}

失败消息是:Assert.IsInstanceOfType failed. Expected type:<UnitTestProject2.TestModel>. Actual type:<UnitTestProject2.TestModel>.奇怪的是,以下确实通过了:

It should_be_the_same_type_as_the_original = () => Assert.IsTrue(testModel.GetType() == typeof(TestModel));

我正在以这种方式进行反序列化,因为有问题的实际代码需要能够处理其类型在运行时之前未知的对象。我认为 Json.NET 进行这种反序列化的方式有些奇怪,但是为什么两个测试运行器的行为会有所不同呢?我在 Visual Studio 2013 中使用 ReSharper 9.1。

4

2 回答 2

3

显然,由于 NCrunch 和 ReSharper 之间的行为存在一些细微差别,因此会产生奇怪的运行时效果。失败肯定是在告诉您出了点问题,您不应将其视为 ReSharper 或 NCrunch 中的错误。

当我在调试器中单步执行 MSpec 测试时,deserialized对象在调试器中显示以下错误:

deserialized  Cannot fetch the value of field 'deserialized' because information about the containing class is unavailable.   object

如果没有看到完整的解决方案,很难确定,但我已经看到当构建输出目录包含多个程序集副本时会发生这种情况,可能在子目录中。如果不同组件在不同时间引用了一个程序集的不同副本,那么即使它实际上是程序集的相同副本,有时也会认为该程序集的类型不相等。解决方案是确保在构建输出中每个程序集只有一个副本,从而确保所有内容都引用完全相同的文件。可能是 JSON 转换器正在动态加载您的类型并获取错误的程序集,或者可能将其加载到不同的加载上下文中,这意味着它的类型与在不同上下文中加载的副本不相等。

在 MSpec 案例中,您的构建环境可能会导致程序集的重复副本。特别是 NCruch,默认情况下不执行构建后事件(并且通常会显示警告),因此如果您在构建后步骤中复制文件,那么这可能是对不同行为的一种解释。您可以通过在 NCrunch 中启用构建后事件并查看是否发生故障来检查这一点。

另一个可能的故障排除步骤是使用 Fusion Log Viewer (fuslogvw.exe) 记录程序集绑定,您应该能够准确确定正在加载哪些程序集以及在什么加载上下文中。

更新我很确定这是由 JSON 转换器在运行时使用程序集引起的程序集绑定问题。在融合日志中,我找到了这个条目:

*** 装配活页夹日志条目 (05/06/2015 @ 02:01:38) ***

手术成功。
绑定结果:hr = 0x0。操作成功完成。

从以下位置加载程序集管理器:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
在可执行文件 C:\Users\Tim\AppData\Local\JetBrains\Installations\ReSharperPlatformVs12_001\JetBrains.ReSharper.TaskRunner.CLR45.x64.exe 下运行
--- 详细的错误日志如下。

LOG:IJW 显式绑定。文件路径:c:\users\tim\VS-Projects\StackOverflow\StackOverflow.30643046\bin\Debug\StackOverflow.30643046.dll。
日志:IJW 程序集绑定返回了不同的路径:C:\Users\Tim\AppData\Local\Temp\k3dpwn5u.uii\Machine Specifications Runner\assembly\dl3\6c41c492\c7eea8ec_279fd001\StackOverflow.30643046.dll。使用提供的文件。

我还发现了这个:

*** 装配活页夹日志条目 (05/06/2015 @ 02:01:38) ***

手术成功。
绑定结果:hr = 0x0。操作成功完成。

从以下位置加载程序集管理器:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
在可执行文件 C:\Users\Tim\AppData\Local\JetBrains\Installations\ReSharperPlatformVs12_001\JetBrains.ReSharper.TaskRunner.CLR45.x64.exe 下运行
--- 详细的错误日志如下。

警告:同一个程序集被加载到应用程序域的多个上下文中:
警告:上下文:默认 | 域 ID:2 | 程序集名称:StackOverflow.30643046,版本=1.0.0.0,文化=中性,PublicKeyToken=null
警告:上下文:两者都不是 | 域 ID:2 | 程序集名称:StackOverflow.30643046,版本=1.0.0.0,文化=中性,PublicKeyToken=null
警告:这可能会导致运行时失败。
警告:建议检查您的应用程序是否是故意的。
警告:有关此问题的详细信息和常见解决方案,请参阅白皮书 http://go.microsoft.com/fwlink/?LinkId=109270。


*** 装配活页夹日志条目 (05/06/2015 @ 02:04:41) ***

手术成功。
绑定结果:hr = 0x0。操作成功完成。

从以下位置加载程序集管理器:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
在可执行文件 C:\Users\Tim\AppData\Local\JetBrains\Installations\ReSharperPlatformVs12_001\JetBrains.ReSharper.TaskRunner.CLR45.x64.exe 下运行
--- 详细的错误日志如下。

警告:同一个程序集被加载到应用程序域的多个上下文中:
警告:上下文:默认 | 域 ID:2 | 程序集名称:StackOverflow.30643046,版本=1.0.0.0,文化=中性,PublicKeyToken=null
警告:上下文:两者都不是 | 域 ID:2 | 程序集名称:StackOverflow.30643046,版本=1.0.0.0,文化=中性,PublicKeyToken=null
警告:这可能会导致运行时失败。
警告:建议检查您的应用程序是否是故意的。
警告:有关此问题的详细信息和常见解决方案,请参阅白皮书 http://go.microsoft.com/fwlink/?LinkId=109270。


*** 装配活页夹日志条目 (05/06/2015 @ 02:04:42) ***

手术成功。
绑定结果:hr = 0x0。操作成功完成。

从以下位置加载程序集管理器:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll
在可执行文件 C:\Users\Tim\AppData\Local\JetBrains\Installations\ReSharperPlatformVs12_001\JetBrains.ReSharper.TaskRunner.CLR45.x64.exe 下运行
--- 详细的错误日志如下。

警告:同一个程序集被加载到应用程序域的多个上下文中:
警告:上下文:默认 | 域 ID:2 | 程序集名称:StackOverflow.30643046,版本=1.0.0.0,文化=中性,PublicKeyToken=null
警告:上下文:两者都不是 | 域 ID:2 | 程序集名称:StackOverflow.30643046,版本=1.0.0.0,文化=中性,PublicKeyToken=null
警告:这可能会导致运行时失败。
警告:建议检查您的应用程序是否是故意的。
警告:有关此问题的详细信息和常见解决方案,请参阅白皮书 http://go.microsoft.com/fwlink/?LinkId=109270。

我还发现禁用 ReSharper 单元测试选项“正在测试的影子复制程序集”会导致测试通过。

所以我认为我们有“确凿证据”。由于您让 JSON 反序列化器在运行时发现类型的方式,您的程序集加载存在冲突。

更新 2015-06-11

我在 MSpec 邮件列表中注意到这一点,它可能与您的问题有关:[machine.specifications] Shadow copying broken - 在测试中产生非常微妙的错误

于 2015-06-05T00:54:39.717 回答
3

正如@tim-long 所提到的,这个问题(和MSpec #278)的原因是 MSpec 运行器中的一个错误。该错误是由 ReSharper 设置触发的,以卷影复制测试程序集(默认情况下为“开启”)。

Json.NET的TypeNameHandling.Objects选项导致通过程序集名称加载程序集,因此使用影子复制的测试程序集加载TestModel类型,这与 MSpec 运行器用于加载和运行测试程序集的非影子复制版本不同。这会导致“预期类型:<UnitTestProject2.TestModel>。实际类型:<UnitTestProject2.TestModel> ”失败。请参阅#279了解更多详细信息,如果启用了卷影复制,为什么会加载两次测试程序集。

我能够重现此故障并检查我的修复#279是否也解决了此问题。

于 2015-06-11T00:36:35.720 回答