当由mspec.exe运行时,规范失败,但在由TD.NET运行时传递

时间:2010-02-24 14:51:19

标签: resharper mspec testdriven.net

我写了关于这个话题in another question

但是,我已经重构了我的代码以摆脱配置访问,从而允许规范通过。或者我想。它们使用TestDriven.Net在Visual Studio中运行良好。但是,当我使用mspec.exe工具在rake中运行它们时,它们仍会因序列化异常而失败。所以我创建了一个完全自包含的示例,除了在线程上设置虚假安全凭证之外基本上什么也没做。这个测试在TD.Net中正常通过,但在mspec.exe中爆炸了。有人有什么建议吗?

更新:我发现了解决方法。在研究了这个问题后,似乎原因是包含我的主要对象的程序集与mspec.exe不在同一个文件夹中。当mspec创建一个新的AppDomain来运行我的规范时,新的AppDomain必须使用主体对象加载程序集以反序列化它。该程序集与mspec EXE不在同一文件夹中,因此失败。如果我将我的程序集复制到与mspec相同的文件夹中,它可以正常工作。

我仍然不明白为什么ReSharper和TD.Net能够正常运行测试?他们不使用mspec.exe来实际运行测试吗?

using System;
using System.Security.Principal;
using System.Threading;
using Machine.Specifications;

namespace MSpecTest
{
    [Subject(typeof(MyViewModel))]
    public class When_security_credentials_are_faked 
    {
        static MyViewModel SUT;

        Establish context = SetupFakeSecurityCredentials;

        Because of = () =>
            SUT = new MyViewModel();

        It should_be_initialized = () =>
            SUT.Initialized.ShouldBeTrue();

        static void SetupFakeSecurityCredentials()
        {
            Thread.CurrentPrincipal = CreatePrincipal(CreateIdentity());
        }

        static MyIdentity CreateIdentity()
        {
            return new MyIdentity(Environment.UserName, "None", true);
        }

        static MyPrincipal CreatePrincipal(MyIdentity identity)
        {
            return new MyPrincipal(identity);
        }
    }

    public class MyViewModel
    {
        public MyViewModel()
        {
            Initialized = true;
        }

        public bool Initialized { get; set; }
    }

    [Serializable]
    public class MyPrincipal : IPrincipal
    {
        private readonly MyIdentity _identity;

        public MyPrincipal(MyIdentity identity)
        {
            _identity = identity;
        }

        public bool IsInRole(string role)
        {
            return true;
        }

        public IIdentity Identity
        {
            get { return _identity; }
        }
    }

    [Serializable]
    public class MyIdentity : IIdentity
    {
        private readonly string _name;
        private readonly string _authenticationType;
        private readonly bool _isAuthenticated;

        public MyIdentity(string name, string authenticationType, bool isAuthenticated)
        {
            _name = name;
            _isAuthenticated = isAuthenticated;
            _authenticationType = authenticationType;
        }

        public string Name
        {
            get { return _name; }
        }

        public string AuthenticationType
        {
            get { return _authenticationType; }
        }

        public bool IsAuthenticated
        {
            get { return _isAuthenticated; }
        }
    }
}

1 个答案:

答案 0 :(得分:5)

丹,

感谢您提供复制品。

首先,控制台运行程序的工作方式与TestDriven.NET和ReSharper运行程序不同。基本上,控制台运行器必须执行更多的设置工作,因为它为每个运行的程序集创建一个新的AppDomain(加配置)。这是加载规范程序集的.dll.config文件所必需的。

根据规范程序集,创建了两个AppDomain:

  1. 创建了第一个AppDomain(Console) mspec.exe是隐式的 执行时,
  2. mspec.exe为包含规范(Spec)的程序集创建了第二个AppDomain。
  3. 两个AppDomain都通过.NET Remoting相互通信:例如,当在Spec AppDomain中执行规范时,它会通知Console AppDomain该事实。当Console收到通知时,它会通过将规范信息写入控制台来进行相应的操作。

    SpecConsole之间的这种通信是通过.NET Remoting透明地实现的。 .NET Remoting的一个属性是,在向目标AppDomain(Spec)发送通知时,会自动包含调用AppDomain(Console)的某些属性。 Thread.CurrentPrincipal就是这样的财产。您可以在此处详细了解:http://sontek.vox.com/library/post/re-iprincipal-iidentity-ihttpmodule-serializable.html

    您提供的上下文将在Spec AppDomain中运行。您在Thread.CurrentPrincipal中设置了BecauseBecause运行后,将向Console AppDomain发出通知。该通知将包含接收MyPrincipal AppDomain尝试反序列化的自定义Console。它不能这样做,因为它不知道你的规范汇编(因为它没有包含在private bin path中)。

    这就是您必须将规范程序集放在与mspec.exe相同的文件夹中的原因。

    有两种可能的解决方法:

    1. MyPrincipal派生MyIdentityMarshalByRefObject,以便他们可以通过代理参与跨AppDomain通信(而非被序列化)
    2. Thread.CurrentPrincipal
    3. 中暂时设置Because

      (格式化需要文本 - 请忽略)

      Because of = () => 
      {
          var previousPrincipal = Thread.CurrentPrincipal;
          try
          {
              Thread.CurrentPrincipal = new MyPrincipal(...);
              SUT = new MyViewModel();
          }
          finally
          {
              Thread.CurrentPrincipal = previousPrincipal;
          }
      }
      
      例如,ReSharper为我们处理所有的沟通工作。 MSpec的ReSharper Runner可以挂钩到现有的基础架构(即AFAIK,不使用.NET Remoting)。