8

I have an interface IAudioProcessor with a single method IEnumerable<Sample> Process(IEnumerable<Sample> samples). While it is not a requirement of the interface itself, I want to make sure that all my implementations follow some common rules, like for example:

  1. Use deferred execution
  2. Don't change the input samples

It is not hard to create tests for these, but I would have to copy and paste these tests for each implementation. I would like to avoid that.

I would like to do something like this (note the attribute GenericTest and the type parameter):

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

Is something like this possible?

Anthony Mastrean
  • 21,850
  • 21
  • 110
  • 188
Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • Ah yes. Thanks a lot for the downvote, **without explaining what it is for**. I have no possibility to improve my question this way. – Daniel Hilgarth Nov 11 '11 at 21:16
  • 1
    I don't know why you were downvoted either, so my upvote will be for great justice. –  Nov 11 '11 at 22:19
  • 1
    Maybe I am missing something, but it sounds to me that you want to assert something on the interface that is not part of the interface itself. Wouldn't it be easier to move that common behavior / implementation in an actual concrete base implementation? – Mathias Nov 12 '11 at 19:11
  • @Mathias: Good point and what I normally do, but I can't see how this would work in this scenario. As you can see from the sample spec, I want to ensure that all my implementations are not enumerating the input data as long as the result has not been enumerated. As the implementations can do really anything with the input data, it is impossible to create a base implementation. – Daniel Hilgarth Nov 14 '11 at 11:01

2 Answers2

2

I'm pretty sure you're looking for Behaviors (also see this row test with behaviors article). You will define the behaviors that every implementation should satisfy (the It fields) in a special class that share the SUT and supporting fields (as necessary).

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

    protected static Context _; 
}

Each of your implementations need to declare that they behave like this special class. You already had a pretty complicated base class with shared setup and behavior, so I'll use it (I prefer a simpler, more explicit setup).

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

Your base class defines the common setup (capturing the context enumeration), behavior (processing with the specific impl via the type parameter), and even declaring the behaviors field (again, thanks to the generic type parameter, this will be run for every concrete).

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

Additionally, you will get output for each of the It fields for each of the implementing classes. So your context/spec reports will be complete.

Anthony Mastrean
  • 21,850
  • 21
  • 110
  • 188
0

I Binged MSpec paramaterised tests for you :) http://groups.google.com/group/machine_users/browse_thread/thread/8419cde3f07ffcf2?pli=1

Whilst it won't show up as a separate green/red test, I don't think there's anything preventing you from enumerating a sequence of factories from within your single spec and assert the behaviour for each implementation. It'll mean your test fails even if one implementation fails, but if you want parameterisation you might try a looser testing suite like NUnit.

Edit 1: I'm not sure if MSpec supports discovery of inherited fields to determine a spec, but if so the below should at least minimise the amount of "repeated" code without being able to use an attribute:

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>
{}
Alex Norcliffe
  • 2,439
  • 2
  • 17
  • 21
  • I googled myself and found this same thread. I didn't consider my case to be a parametrized test though. Although you could surely argue it is... – Daniel Hilgarth Nov 15 '11 at 13:01
  • Your proposal with one test that enumerates over all implementations is something I would like to avoid, because - as you said - it will fail if even one implementation is wrong. – Daniel Hilgarth Nov 15 '11 at 13:05
  • Added an edit, not sure if MSpec discovers inherited spec details though – Alex Norcliffe Nov 15 '11 at 14:37
  • No, it doesn't. That was the first thing I tried. It inherits `Establish` and `Because` but not `It`. Not sure, why, though... – Daniel Hilgarth Nov 15 '11 at 14:43
  • Well I guess having a field It should_not_have_enumerated_the_original_samples = base.really_should_not_have_enumerated_the_original_samples; would work then, but you're not saving much effort by then – Alex Norcliffe Nov 15 '11 at 15:56
  • Not really. Currently, that's the way I am doing it - I just encapsulated all `It`s in a behavior... – Daniel Hilgarth Nov 15 '11 at 16:57