8

我有IAudioProcessor一个单一方法的接口IEnumerable<Sample> Process(IEnumerable<Sample> samples)。虽然这不是接口本身的要求,但我想确保我的所有实现都遵循一些通用规则,例如:

  1. 使用延迟执行
  2. 不要更改输入样本

为这些创建测试并不难,但我必须为每个实现复制和粘贴这些测试。我想避免这种情况。

我想做这样的事情(注意属性GenericTest和类型参数):

[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();
    };
}

这样的事情可能吗?

4

2 回答 2

2

我很确定您正在寻找行为(另请参阅此行测试与行为文章)。您将在共享 SUT 和支持字段(根据需要)的特殊类中定义每个实现应满足的行为(It字段)。

[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 _;
}

您的基类定义了通用设置(捕获上下文枚举)、行为(通过类型参数使用特定 impl 进行处理),甚至声明了行为字段(再次感谢泛型类型参数,这将为每个具体的)。

[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每个实现类的每个字段的输出。因此,您的上下文/规格报告将是完整的。

于 2011-11-16T05:52:15.883 回答
0

我为你准备了 MSpec 参数化测试:) http://groups.google.com/group/machine_users/browse_thread/thread/8419cde3f07ffcf2?pli=1

虽然它不会显示为单独的绿色/红色测试,但我认为没有什么可以阻止您从单个规范中枚举一系列工厂并断言每个实现的行为。这意味着即使一个实现失败,您的测试也会失败,但是如果您想要参数化,您可以尝试像 NUnit 这样更松散的测试套件。

编辑 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>
{}
于 2011-11-15T12:51:21.427 回答