Spec не запускается при запуске mspec.exe, но проходит при запуске TD.NET - PullRequest
1 голос
/ 24 февраля 2010

Я писал об этой теме в другом вопросе .

Тем не менее, с тех пор я реорганизовал свой код, чтобы избавиться от доступа к конфигурации, что позволило пройти спецификации. Или я так думал. Они отлично работают из 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 Ответ

5 голосов
/ 28 февраля 2010

Dan

спасибо за предоставленную репродукцию.

Прежде всего, консольный бегун работает иначе, чем бегуны TestDriven.NET и ReSharper. По сути, консольный исполнитель должен выполнить гораздо больше работы по настройке, поскольку он создает новый домен приложений (плюс конфигурация) для каждой выполняемой сборки. Это необходимо для загрузки файла .dll.config для вашей сборки спецификации.

Для каждой сборки создается два домена приложений:

  1. Первый AppDomain (Console) создан неявно, когда mspec.exe выполняется,
  2. второй домен приложений создается mspec.exe для сборки, содержащей спецификации (Spec).

Оба домена приложений связываются друг с другом через .NET Remoting: например, когда спецификация выполняется в Spec AppDomain, она уведомляет Console AppDomain об этом факте. Когда Console получает уведомление, оно действует соответствующим образом, записывая информацию о спецификации в консоль.

Это взаимодействие между Spec и Console осуществляется прозрачно через .NET Remoting. Одним из свойств .NET Remoting является то, что некоторые свойства вызывающего домена приложений (Spec) автоматически включаются при отправке уведомлений целевому домену приложений (Console). Thread.CurrentPrincipal это такая собственность. Подробнее об этом можно прочитать здесь: http://sontek.vox.com/library/post/re-iprincipal-iidentity-ihttpmodule-serializable.html

Контекст, который вы предоставляете, будет работать в Spec AppDomain. Вы устанавливаете Thread.CurrentPrincipal в Because. После запуска Because на домен приложений Console будет отправлено уведомление. Уведомление будет включать ваш пользовательский MyPrincipal, который получающий Console AppDomain пытается десериализовать. Он не может этого сделать, поскольку не знает о вашей сборке спецификаций (поскольку он не включен в свой путь к приватному бину ).

Вот почему вы должны были поместить вашу спецификацию сборки в ту же папку, что и mspec.exe.

Есть два возможных решения:

  1. Получите MyPrincipal и MyIdentity из MarshalByRefObject, чтобы они могли принимать участие в кросс-доменной связи через прокси (вместо сериализации)
  2. Установить 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).

...