如何创建类型的随机具体实现

How to create random concrete implementation of type

我正在清理测试的安排阶段,AutoFixture 帮了我大忙,但我在整个测试过程中都遇到了常见模式。我有一个继承结构,它以多个接口或抽象 class 开始,然后从中派生出具体类型,我想创建一些基本类型的具体实现,我不在乎是哪一个。我知道有类型继电器,但我认为它们不允许我想要的随机行为。我想 AutoFixture 需要以某种方式了解类型层次结构才能选择具体实现。

AutoFixture.AutoMoq 包含在内,所以你知道我可以访问它。

我刚刚安装了 AutoFixture 所以请不要受限于我的方法,请随时提供您自己的解决方案。

using System;
using System.Diagnostics;
using System.Linq;
using AutoFixture;
using AutoFixture.AutoMoq;

// ARRANGE
var fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization());

// ACT
// I want this to be filled with random combination of non abstract implementations of `Command`
var commands  = fixture.CreateMany<Command>(3).ToList();

// ASSERT
// Fails for any value of count `CreateMany(int count)`
Debug.Assert(commands.Any(x => x.Type != CommandType.CREATE_FOO));

public enum CommandType { CREATE_FOO, DELETE_FOO }
 
public abstract class Command
{
  public CommandType Type { get; set; }
}

  public abstract class CreateCommand : Command { }

    public class CreateFoo : CreateCommand
    {
      public CreateFoo() => Type = CommandType.CREATE_FOO;
    }

  public abstract class DeleteCommand : Command { }

    public class DeleteFoo : DeleteCommand
    {
      public DeleteFoo() => Type = CommandType.DELETE_FOO;
    }

为了简洁起见,我将解释一个更简单的场景。我相信您可以推断它并在您的测试中使用该解决方案。

假设嵌套级别只是一个,您有一个基本类型 Command 和两个子类型 CreateFooDeleteFoo。您想随机装箱一个或另一个。

您可以创建一个类似于类型中继的构建器 class,但它不会直接中继您的请求,而是选择一个随机中继,然后调用它。

您的测试应如下所示。

[Fact]
public void GeneratesRandomCommands()
{
    var fixture = new Fixture();
    fixture.Customizations.Add(
        new FilteringSpecimenBuilder(
            new RandomRelayCustomization(
                new TypeRelay(typeof(Command), typeof(DeleteFoo)),
                new TypeRelay(typeof(Command), typeof(CreateFoo))),
            new ExactTypeSpecification(typeof(Command))));

    var items = fixture.CreateMany<Command>(10);

    items.OfType<DeleteFoo>().Should().HaveCount(5);
    items.OfType<CreateFoo>().Should().HaveCount(5);
}

请注意 RandomRelayCustomization,它采用了一组中继。 那就是选择随机中继的 class。

在 AutoFixture 中,我们通常使用 pseudo-randomness,因为这样速度更快,而且通常测试不需要真正的随机性。我相信您可以扩展这个想法并根据需要将其随机化。对于这个例子,我使用了一个 RoundRobinSequence ,它只是一个 IEnumerable<int> ,它在一系列索引上循环,每隔一个索引就会跳过一次。您可以在 Gist.

中查看此构建器的完整示例
public class RandomRelayCustomization : ISpecimenBuilder
{
    private readonly List<ISpecimenBuilder> builders;
    private readonly IEnumerator<int> randomizer;

    public RandomRelayCustomization(params ISpecimenBuilder[] builders)
        : this(builders.AsEnumerable())
    {
    }

    public RandomRelayCustomization(IEnumerable<ISpecimenBuilder> builders)
    {
        if (builders is null)
        {
            throw new ArgumentNullException(nameof(builders));
        }

        this.builders = builders.ToList();
        this.randomizer = new RoundRobinSequence(0, this.builders.Count - 1)
            .GetEnumerator();
    }

    public object Create(object request, ISpecimenContext context)
    {
        this.randomizer.MoveNext();
        var builder = this.builders[this.randomizer.Current];
        return builder.Create(request, context);
    }
}

由于您不希望使用基本类型以外的请求调用此构建,因此我添加了一个 FilteringSpecimenBuilder 来使用 ExactTypeSpecification 过滤传入请求,因此中继仅在您收到时才工作Command.

的基本类型请求

如果有帮助请告诉我。