Как запустить код перед каждым запуском теста в MSpec?

Я пытаюсь запустить код инициализации перед тестом. Я пробовал предложения по другим вопросам, но это не работает. Моя модель предметной области вызывает события через следующий класс:

public static class DomainEvents
{
    private static readonly object @lock = new object();
    private static Action<IDomainEvent> raiseEvent;

    public static void Raise<TEvent>(TEvent @event) where TEvent : class, IDomainEvent 
    {
         // omitted for brevity
    }

    public static void RegisterEventPublisher(Action<IDomainEvent> eventPublisher)
    {
        lock (@lock)
        {
            raiseEvent = eventPublisher;
        }
    }
}

В целях тестирования я хотел бы зафиксировать эти события в статическом списке. Как лучше всего это сделать?

Обновлять

Проблема была вызвана порядком выполнения тестов (что, как указывает Александр ниже, не гарантируется). В одной из своих спецификаций я зарегистрировал фиктивного издателя событий. Тот факт, что спецификация часто запускалась в разном порядке, означал, что а) сначала я не знал, что у меня есть проблема («проблемная» спецификация всегда выполнялась последней) и б) как только у меня возникла проблема, количество неудачные тесты часто различались между запусками (что еще больше запутывало).

Усвоенный урок — очищайте все статические ресурсы после запуска каждого контекста. Вы можете сделать это, реализовав ICleanupAfterEveryContextInAssembly.


person Ben Foster    schedule 18.04.2012    source источник
comment
Вы привыкли использовать зарезервированные слова для имен переменных? Вероятно, это не очень хорошая идея.   -  person mellamokb    schedule 19.04.2012
comment
Не совсем. Хотя у меня никогда не было проблем с этим в прошлом.   -  person Ben Foster    schedule 19.04.2012


Ответы (2)


Это работает для меня:

using System;
using System.Collections.Generic;

using Machine.Specifications;

namespace AssemblyContextSpecs
{
  public static class DomainEvents
  {
    static readonly object @lock = new object();

    static Action<IDomainEvent> raiseEvent;

    public static void Raise<TEvent>(TEvent @event) where TEvent : class, IDomainEvent
    {
      raiseEvent(@event);
    }

    public static void RegisterEventPublisher(Action<IDomainEvent> eventPublisher)
    {
      lock (@lock)
      {
        raiseEvent = eventPublisher;
      }
    }
  }

  public interface IDomainEvent
  {
  }

  class FooEvent : IDomainEvent
  {
  }

  public class DomainEventsContext : IAssemblyContext
  {
    internal static IList<IDomainEvent> Events = new List<IDomainEvent>();

    public void OnAssemblyStart()
    {
      DomainEvents.RegisterEventPublisher(x => Events.Add(x));
    }

    public void OnAssemblyComplete()
    {
    }
  }

  public class When_a_domain_event_is_raised
  {
    Because of = () => DomainEvents.Raise(new FooEvent());

    It should_capture_the_event =
      () => DomainEventsContext.Events.ShouldContain(x => x.GetType() == typeof(FooEvent));
  }
}

Разве RegisterEventPublisher не должно быть RegisterEventSubscriber?

person Alexander Groß    schedule 19.04.2012
comment
Это работает для меня в одной спецификации. Проблема, с которой я, кажется, сталкиваюсь, заключается в запуске нескольких спецификаций, которые запускают события. У меня есть 20 или около того спецификаций, которые запускают события, но у меня только 5 событий в списке. Возможно, это проблема с потоками? - person Ben Foster; 19.04.2012
comment
MSpec не создает потоки сама по себе. В настоящее время существует только одно ограничение относительно ReSharper, то есть IAssemblyContext запускается для каждого контекста. Обычно они запускаются один раз на сборку (TD.Net, консоль). - person Alexander Groß; 19.04.2012
comment
Возможно ли, что это проблема заказа? MSpec не гарантирует порядок, в котором выполняются контексты, поэтому может случиться так, что не все предыдущие события будут в списке. - person Alexander Groß; 19.04.2012
comment
Меня не обязательно заботит порядок, если я могу очистить список перед запуском каждой спецификации. Проблема действительно проявляется в нескольких контекстах. Когда я удаляю все контексты, кроме одного (содержащие 5 спецификаций, вызывающих события), все они выполняются успешно. Когда я снова добавляю контексты, те же 5 терпят неудачу. - person Ben Foster; 19.04.2012
comment
Каким бегуном пользуетесь? Можете ли вы опубликовать код, который воспроизводит проблему? - person Alexander Groß; 19.04.2012
comment
В конце концов я нашел проблему. Спасибо за вашу помощь, в частности, я понял, что должен использовать утверждения MSpec «Должен» для списка, поскольку отчет затем предоставит вам содержимое (что помогло диагностировать проблему). Раньше я возвращал логическое значение и утверждал это. В качестве примечания: есть ли способ запускать метод перед каждым контекстом (противоположность ICleanupAfterEventContextInAssembly)? - person Ben Foster; 19.04.2012
comment
К сожалению, в настоящее время нет IRunBeforeEveryContextInAssembly. Мы, безусловно, могли бы добавить что-то подобное, я жду вашего запроса на включение :-) Рад, что вы смогли решить проблемы с вашими спецификациями! - person Alexander Groß; 19.04.2012

Может быть, я неправильно понимаю проблему, но основной шаблон:

public class WhenSomeDomainEventIsRaised
{
    private IList<IDomainEvent> EventsRaised = new List<IDomainEvent>();

    Establish context = () => 
    {
        // subscribe to events; when raised, add to EventsRaised list
    }
}

Если вы хотите сделать это для всех тестов или подмножества тестов:

public abstract class DomainSpecification
{
    protected IList<IDomainEvent> EventsRaised = new List<IDomainEvent>();

    Establish context = () => 
    {
        // subscribe to events; when raised, add to EventsRaised list
    }
}

Вы можете наследовать все спецификации, которым требуется такое поведение, от этого класса, а MSpec позаботится об управлении всеми Establish блоками в иерархии наследования.

person Jay    schedule 18.04.2012