如何创建类型的随机具体实现
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
和两个子类型 CreateFoo
和 DeleteFoo
。您想随机装箱一个或另一个。
您可以创建一个类似于类型中继的构建器 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
.
的基本类型请求
如果有帮助请告诉我。
我正在清理测试的安排阶段,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
和两个子类型 CreateFoo
和 DeleteFoo
。您想随机装箱一个或另一个。
您可以创建一个类似于类型中继的构建器 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
.
如果有帮助请告诉我。