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

发布于 2024-12-15 04:40:34 字数 1099 浏览 3 评论 0原文

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

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

这样的事情可能吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

嘿咻 2024-12-22 04:40:35

我很确定您正在寻找行为(另请参阅此行为文章的行测试)。您将在共享 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 字段的输出。这样您的背景/规格报告就完整了。

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.

奢欲 2024-12-22 04:40:35

我为您进行了 Binged 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>
{}

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>
{}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文