Spec не запускается при запуске mspec.exe, но проходит при запуске TD.NET
Я писал об этой теме в другом вопросе.
Тем не менее, с тех пор я реорганизовал свой код, чтобы избавиться от доступа к конфигурации, что позволило пройти спецификации. Или я так думал. Они отлично работают в Visual Studio, используя TestDriven.Net. Тем не менее, когда я запускаю их во время рейка с помощью средства mspec.exe, они все равно завершаются с исключением сериализации. Итак, я создал полностью автономный пример, который практически ничего не делает, кроме установки поддельных учетных данных безопасности в потоке. Этот тест проходит нормально в TD.Net, но взрывается в mspec.exe. У кого-нибудь есть предложения?
Обновление: я обнаружил обходной путь. После исследования проблемы, кажется, причина в том, что сборка, содержащая мой основной объект, не находится в той же папке, что и mspec.exe. Когда mspec создает новый AppDomain для запуска моих спецификаций, этот новый AppDomain должен загрузить сборку с основным объектом, чтобы десериализовать его. Эта сборка не находится в той же папке, что и EXE-файл mspec, поэтому происходит сбой. Если я скопировал мою сборку в ту же папку, что и 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 ответ
Дэн,
спасибо за предоставленную репродукцию.
Прежде всего, консольный бегун работает иначе, чем бегуны TestDriven.NET и ReSharper. По сути, консольный исполнитель должен выполнить гораздо больше работы по настройке, поскольку он создает новый домен приложений (плюс конфигурация) для каждой выполняемой сборки. Это необходимо для загрузки файла.dll.config для вашей сборки спецификации.
В соответствии со спецификацией сборки создаются два домена приложений:
- Первый AppDomain (
Console
) создается неявно при запуске mspec.exe, - mspec.exe для сборки, содержащей спецификации, создается второй домен приложения.
Spec
).
Оба домена приложений взаимодействуют друг с другом через.NET Remoting: например, когда спецификация выполняется в Spec
AppDomain, он уведомляет Console
AppDomain этого факта. когда Console
Получив уведомление, он действует соответствующим образом, записав информацию о спецификации в консоль.
Это общение между Spec
а также Console
прозрачно реализовано через.NET Remoting. Одним из свойств.NET Remoting является то, что некоторые свойства вызывающего AppDomain (Spec
) автоматически включаются при отправке уведомлений целевому домену приложений (Console
). Thread.CurrentPrincipal
это такая собственность. Вы можете прочитать больше об этом здесь: http://sontek.vox.com/library/post/re-iprincipal-iidentity-ihttpmodule-serializable.html
Предоставленный вами контекст будет работать в Spec
AppDomain. Ты устанавливаешь Thread.CurrentPrincipal
в Because
, После Because
побежал, уведомление будет выдано Console
AppDomain. Уведомление будет включать ваш заказ MyPrincipal
что получение Console
AppDomain пытается десериализовать. Он не может этого сделать, поскольку не знает о вашей сборке спецификаций (так как он не включен в путь к своему приватному бину).
Вот почему вы должны были поместить вашу спецификацию сборки в ту же папку, что и mspec.exe.
Есть два возможных обходных пути:
- получать
MyPrincipal
а такжеMyIdentity
отMarshalByRefObject
чтобы они могли участвовать в кросс-доменных связях через прокси (вместо сериализации) - Задавать
Thread.CurrentPrincipal
временно вBecause
(Текст требуется для форматирования, чтобы работать - пожалуйста, игнорируйте)
Because of = () =>
{
var previousPrincipal = Thread.CurrentPrincipal;
try
{
Thread.CurrentPrincipal = new MyPrincipal(...);
SUT = new MyViewModel();
}
finally
{
Thread.CurrentPrincipal = previousPrincipal;
}
}
ReSharper, например, обрабатывает всю коммуникационную работу за нас. Repecharper Runner от MSpec может подключиться к существующей инфраструктуре (которая, AFAIK, не использует.NET Remoting).