Как написать общие тесты для всех реализаций интерфейса с MSpec? - PullRequest
8 голосов
/ 11 ноября 2011

У меня есть интерфейс IAudioProcessor с одним методом IEnumerable<Sample> Process(IEnumerable<Sample> samples).Хотя это не является обязательным требованием для самого интерфейса, я хочу убедиться, что все мои реализации следуют некоторым общим правилам, например, например:

  1. Использовать отложенное выполнение
  2. Неизменить входные выборки

Создать тесты для них несложно, но мне придется скопировать и вставить эти тесты для каждой реализации.Я бы хотел этого избежать.

Я бы хотел сделать что-то вроде этого (обратите внимание на атрибут GenericTest и параметр type):

[GenericTest(typeof(AudioProcessorImpl1Factory))]
[GenericTest(typeof(AudioProcessorImpl2Factory))]
[GenericTest(typeof(AudioProcessorImpl3Factory))]
public class when_processed_audio_is_returned<TSutFactory>
    where TSutFactory : ISutFactory<IAudioProcessor>, new()
{
    static IAudioProcessor Sut = new TSutFactory().CreateSut();
    protected static Context _ = new Context();

    Establish context = () => _.Original = Substitute.For<IEnumerable<ISample>>();

    Because of = () => Sut.Process(_.Original);

    It should_not_have_enumerated_the_original_samples = () =>
    {
        _.Original.DidNotReceive().GetEnumerator();
        ((IEnumerable)_.Original).DidNotReceive().GetEnumerator();
    };
}

Возможно ли что-то подобное?

Ответы [ 2 ]

2 голосов
/ 16 ноября 2011

Я почти уверен, что вы ищете Поведения (см. Также этот тест строки со статьями поведения ). Вы определите поведение, которому должна удовлетворять каждая реализация (поля It) в специальном классе, который разделяет SUT и вспомогательные поля (при необходимости).

[Behaviors]
public class DeferredExecutionProcessor
{
    It should_not_have_enumerated_the_original_samples = () =>
    {
        _.Original.DidNotReceive().GetEnumerator();
        ((IEnumerable)_.Original).DidNotReceive().GetEnumerator();
    };

    protected static Context _; 
}

Каждая из ваших реализаций должна объявить, что они ведут себя как этот специальный класс. У вас уже был довольно сложный базовый класс с общей настройкой и поведением, поэтому я буду его использовать (я предпочитаю более простую, более явную настройку).

public abstract class AudioProcessorContext<TSutFactory>
    where TSutFactory : ISutFactory<IAudioProcessor>, new()
{
    // I don't know Behaves_like works with field initializers
    Establish context = () => 
    {
        Sut = new TSutFactory().CreateSut();

        _ = new Context();
        _.Original = Substitute.For<IEnumerable<ISample>>();
    }

    protected static IAudioProcessor Sut;
    protected static Context _;
}

Ваш базовый класс определяет общую настройку (захват перечисления контекста), поведение (обработка с определенным значением через параметр типа) и даже объявление поля поведений (опять же, благодаря параметру универсального типа, это будет выполнено за каждый конкретный).

[Subject("Audio Processor Impl 1")]
public class when_impl1_processes_audio : AudioProcessorContext<AudioProcessorImpl1Factory>
{
    Because of = () => Sut.Process(_.Original);
    Behaves_like<DeferredExecutionProcessor> specs;
}

[Subject("Audio Processor Impl 2")]
public class when_impl2_processes_audio : AudioProcessorContext<AudioProcessorImpl2Factory>
{
    Because of = () => Sut.Process(_.Original);
    Behaves_like<DeferredExecutionProcessor> specs;
}

[Subject("Audio Processor Impl 3")]
public class when_impl3_processes_audio : AudioProcessorContext<AudioProcessorImpl3Factory>
{
    Because of = () => Sut.Process(_.Original);
    Behaves_like<DeferredExecutionProcessor> specs;
}

Кроме того, вы получите вывод для каждого из полей It для каждого из реализующих классов. Таким образом, ваши отчеты по контексту / спецификации будут полными.

0 голосов
/ 15 ноября 2011

Я парализовал тесты Binged MSpec для вас :) http://groups.google.com/group/machine_users/browse_thread/thread/8419cde3f07ffcf2?pli=1

Хотя он не будет отображаться как отдельный зеленый / красный тест, я не думаю, что есть что-то, что мешает вам перечислить последовательностьфабрики изнутри вашей спецификации и утверждают поведение для каждой реализации.Это будет означать, что ваш тест не пройден, даже если одна реализация не пройдена, но если вы хотите параметризацию, вы можете попробовать более слабый набор тестов, такой как NUnit.

Edit 1: Я не уверен, что MSpecподдерживает обнаружение унаследованных полей для определения спецификации, но в этом случае приведенное ниже описание должно как минимум минимизировать количество «повторяющегося» кода без возможности использования атрибута:

private class base_when_processed_audio_is_returned<TSutFactory>
    where TSutFactory : ISutFactory<IAudioProcessor>, new()
{
    static IAudioProcessor Sut = new TSutFactory().CreateSut();
    protected static Context _ = new Context();

    Establish context = () => _.Original = Substitute.For<IEnumerable<ISample>>();

    public Because of = () => Sut.Process(_.Original);

    public It should_not_have_enumerated_the_original_samples = () =>
    {
        _.Original.DidNotReceive().GetEnumerator();
        ((IEnumerable)_.Original).DidNotReceive().GetEnumerator();
    };
}

public class when_processed_audio_is_returned_from_AudioProcessorImpl1Factory()
  : base_when_processed_audio_is_returned<AudioProcessorImpl1Factory>
{}

public class when_processed_audio_is_returned_from_AudioProcessorImpl2Factory()
  : base_when_processed_audio_is_returned<AudioProcessorImpl2Factory>
{}
...