如何为MSpec的所有接口实现编写通用测试?

时间:2011-11-11 17:45:17

标签: c# unit-testing bdd mspec

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

  1. 使用延期执行
  2. 请勿更改输入样本
  3. 为这些创建测试并不难,但我必须为每个实现复制并粘贴这些测试。我想避免这种情况。

    我想做这样的事情(注意属性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();
        };
    }
    

    这样的事情可能吗?

2 个答案:

答案 0 :(得分:2)

我很确定你正在寻找Behaviors(另见row test with behaviors article)。您将在共享SUT和支持字段的特殊类中定义每个实现应满足的行为(It字段)。(/ p>

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

您的基类定义了公共设置(捕获上下文枚举),行为(通过type参数使用特定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字段的输出。因此,您的上下文/规范报告将完整。

答案 1 :(得分: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>
{}