由 mspec.exe 运行时 Spec 失败,但由 TD.NET 运行时通过
然而,我已经重构了我的代码以摆脱配置访问,从而允许规范通过。或者说我是这么想的。它们使用 TestDriven.Net 在 Visual Studio 中运行良好。但是,当我在 rake 期间使用 mspec.exe 工具运行它们时,它们仍然会失败并出现序列化异常。因此,我创建了一个完全独立的示例,除了在线程上设置伪造的安全凭证之外,它基本上不执行任何操作。该测试在 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; }
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
丹,
感谢您提供复制品。
首先,控制台运行程序的工作方式与 TestDriven.NET 和 ReSharper 运行程序不同。基本上,控制台运行程序必须执行更多的设置工作,因为它为每个运行的程序集创建一个新的 AppDomain(加上配置)。这是加载规范程序集的 .dll.config 文件所必需的。
每个规范程序集都会创建两个 AppDomain:
Console
)当 mspec.exe 是隐式的
执行后,
Spec
) 的程序集创建第二个 AppDomain。两个 AppDomain 通过 .NET Remoting 相互通信:例如,当在
Spec
AppDomain 中执行规范时,它会将这一事实通知Console
AppDomain。当Console
收到通知时,它会通过将规范信息写入控制台来采取相应的行动。Spec
和Console
之间的通信是通过 .NET Remoting 透明地实现的。 .NET Remoting 的一项属性是,在向目标 AppDomain (Console
) 发送通知时,会自动包含调用 AppDomain (Spec
) 的某些属性。Thread.CurrentPrincipal
就是这样一个属性。您可以在这里阅读更多相关信息: http://sontek .vox.com/library/post/re-iprincipal-iidentity-ihttpmodule-serialized.html您提供的上下文将在
Spec
AppDomain 中运行。您在Because
中设置了Thread.CurrentPrincipal
。Because
运行后,将向Console
AppDomain 发出通知。该通知将包含接收Console
AppDomain 尝试反序列化的自定义MyPrincipal
。它不能这样做,因为它不知道您的规范程序集(因为它不包含在其 私有 bin 路径)。这就是为什么您必须将规范程序集放在与 mspec.exe 相同的文件夹中。
有两种可能的解决方法:
MarshalByRefObject
派生MyPrincipal
和MyIdentity
,以便它们可以通过代理参与跨 AppDomain 通信(而不是正在序列化)中暂时设置
Thread.CurrentPrincipal
因为(格式化工作需要文本 - 请忽略)
例如,ReSharper为我们处理所有通信工作。 MSpec 的 ReSharper Runner 可以连接到现有的基础设施(据我所知,它不使用 .NET Remoting)。
Dan,
thank you for providing a reproduction.
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.
Per spec assembly, two AppDomains are created:
Console
) is createdimplicitly when mspec.exe is
executed,
Spec
).Both AppDomains communicate with each other through .NET Remoting: For example, when a spec is executed in the
Spec
AppDomain, it notifies theConsole
AppDomain of that fact. WhenConsole
receives the notification it acts accordingly by writing the spec information to the console.This communiciation between
Spec
andConsole
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.htmlThe context you provide will run in the
Spec
AppDomain. You setThread.CurrentPrincipal
in theBecause
. AfterBecause
ran, a notification will be issued to theConsole
AppDomain. The notification will include your customMyPrincipal
that the receivingConsole
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).This is why you had to put your spec assembly in the same folder as mspec.exe.
There are two possible workarounds:
MyPrincipal
andMyIdentity
fromMarshalByRefObject
so that they can take part in cross-AppDomain communication through a proxy (instead of being serialized)Thread.CurrentPrincipal
transiently in theBecause
(Text is required for formatting to work -- please ignore)
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).