Spec由mspec.exe运行时失败,但由TD.NET运行时通过 [英] Spec fails when run by mspec.exe, but passes when run by TD.NET

查看:152
本文介绍了Spec由mspec.exe运行时失败,但由TD.NET运行时通过的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我写了关于这个主题的文章在另一个问题中.

I wrote about this topic in another question.

但是,自那以后,我重构了代码以摆脱配置访问,从而允许规范通过.还是我想.它们可以在Visual Studio中使用TestDriven.Net正常运行.但是,当我在使用mspec.exe工具进行耙操作期间运行它们时,它们仍然失败,并出现序列化异常.因此,我创建了一个完全独立的示例,除了在线程上设置伪造的安全凭证外,基本上没有任何作用.该测试在TD.Net中通过的很好,但是在mspec.exe中却失败了.有人有什么建议吗?

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?

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

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.

我仍然不明白,为什么ReSharper和TD.Net可以很好地运行测试?他们不使用mspec.exe实际运行测试吗?

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

推荐答案

丹,

感谢您提供复制品.

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

First off, the console runner works differently than the TestDriven.NET and ReSharper runners. Basically, the console runner has to perform a lot more setup work in that it creates a new AppDomain (plus configuration) for every assembly that is run. This is required to load the .dll.config file for your spec assembly.

按照规范程序集,将创建两个AppDomain:

Per spec assembly, two AppDomains are created:

  1. 第一个AppDomain(Console)已创建 当mspec.exe是隐式的 被执行,
  2. mspec.exe为包含规范(Spec)的程序集创建了第二个AppDomain.
  1. The first AppDomain (Console) is created implicitly when mspec.exe is executed,
  2. a second AppDomain is created by mspec.exe for the assembly containing the specs (Spec).

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

Both AppDomains communicate with each other through .NET Remoting: For example, when a spec is executed in the Spec AppDomain, it notifies the Console AppDomain of that fact. When Console receives the notification it acts accordingly by writing the spec information to the console.

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

This communiciation between Spec and Console is realized transparently through .NET Remoting. One property of .NET Remoting is that some properties of the calling AppDomain (Spec) are automatically included when sending notifications to the target AppDomain (Console). Thread.CurrentPrincipal is such a property. You can read more about that here: http://sontek.vox.com/library/post/re-iprincipal-iidentity-ihttpmodule-serializable.html

您提供的上下文将在Spec AppDomain中运行.您在Because中设置Thread.CurrentPrincipal.在运行Because之后,将向Console AppDomain发出通知.该通知将包含接收方Console AppDomain尝试反序列化的自定义MyPrincipal.它无法执行此操作,因为它不了解您的规范程序集(因为它不包含在其

The context you provide will run in the Spec AppDomain. You set Thread.CurrentPrincipal in the Because. After Because ran, a notification will be issued to the Console AppDomain. The notification will include your custom MyPrincipal that the receiving Console AppDomain tries to deserialize. It cannot do that since it doesn't know about your spec assembly (as it is not included in its private bin path).

这就是为什么必须将spec程序集与mspec.exe放在同一文件夹中.

This is why you had to put your spec assembly in the same folder as mspec.exe.

有两种可能的解决方法:

There are two possible workarounds:

  1. MarshalByRefObject派生MyPrincipalMyIdentity,以便他们可以通过代理(而不是被序列化)参与跨AppDomain的通信
  2. Because
  3. 中暂时设置Thread.CurrentPrincipal
  1. Derive MyPrincipal and MyIdentity from MarshalByRefObject so that they can take part in cross-AppDomain communication through a proxy (instead of being serialized)
  2. Set Thread.CurrentPrincipal transiently in the Because

(文本需要格式才能正常工作-请忽略)

(Text is required for formatting to work -- please ignore)

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

ReSharper, for example, handles all the communication work for us. MSpec's ReSharper Runner can hook into the existing infrastructure (that, AFAIK, does not use .NET Remoting).

这篇关于Spec由mspec.exe运行时失败,但由TD.NET运行时通过的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆