1

I wrote about this topic in another question.

However, I've since refactored my code to get rid of configuration access, thus allowing the specs to pass. Or so I thought. They run fine from within Visual Studio using TestDriven.Net. However, when I run them during rake using the mspec.exe tool, they still fail with a serialization exception. So I've created a completely self-contained example that does basically nothing except setup fake security credentials on the thread. This test passes just fine in TD.Net, but blows up in mspec.exe. Does anybody have any suggestions?

Update: I've discovered a work-around. After researching the issue, it seems the cause is that the assembly containing my principal object is not in the same folder as the mspec.exe. When mspec creates a new AppDomain to run my specs, that new AppDomain has to load the assembly with the principal object in order to deserialize it. That assembly is not in the same folder as the mspec EXE, so it fails. If I copied my assembly into the same folder as mspec, it works fine.

What I still don't understand is why ReSharper and TD.Net can run the test just fine? Do they not use mspec.exe to actually run the tests?

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; }
        }
    }
}
4

1 回答 1

5

担,

感谢您提供复制品。

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

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

  1. 第一个 AppDomain ( Console) 是在执行 mspec.exe 时隐式创建的,
  2. 第二个 AppDomain 由 mspec.exe 为包含规范 ( Spec) 的程序集创建。

两个 AppDomain 都通过 .NET Remoting 相互通信:例如,当在SpecAppDomain 中执行规范时,它会将这一事实通知ConsoleAppDomain。当Console收到通知时,它会通过将规范信息写入控制台来相应地采取行动。

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

您提供的上下文将在SpecAppDomain 中运行。你设置Thread.CurrentPrincipalBecause. 运行后,会向AppDomainBecause发出通知。Console通知将包含您的自定义MyPrincipal,即接收ConsoleAppDomain 尝试反序列化。它不能这样做,因为它不知道您的规范程序集(因为它不包含在其私有 bin 路径中)。

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

有两种可能的解决方法:

  1. 派生MyPrincipalMyIdentity从,MarshalByRefObject以便他们可以通过代理参与跨 AppDomain 通信(而不是被序列化)
  2. 暂时设置Thread.CurrentPrincipalBecause

(格式化工作需要文本——请忽略)

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)。

于 2010-02-28T16:35:32.840 回答