使用 AutoFixture 自定义显式设置样本的多个属性

Explicitly Set up multiple properties of specimen using AutoFixture customizations

我想利用 xUnit 理论和 AutoFixture 来生成匿名对象,但具有一些显式属性。

这就是我现在拥有的:

被测系统

public class Task
{
    public TaskState TaskState { get; set;}
    public int Progress { get; set; }
}

通用定制

public class PropertyCustomization<T> : ICustomization
{
    private readonly string propertyName;

    private readonly object value;

    public PropertyCustomization(string propertyName, object value)
    {
        this.propertyName = propertyName;
        this.value = value;
    }

    public void Customize(IFixture fixture)
    {
        fixture.Customize<T>(cmp => cmp.Do(obj => obj.SetProperty(this.propertyName, this.value)));
    }
}

..

public static void SetProperty(this object instance, string propertyName, object value)
{
    var propertyInfo = instance.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
    propertyInfo.SetValue(instance, value);
}

和使用它的属性

[AttributeUsage(AttributeTargets.Parameter)]
public sealed class AutoTaskAttribute : CustomizeAttribute
{
    private readonly int progress;

    private readonly TaskState taskState;

    public AutoTaskAttribute(TaskState taskState, int progress = -1)
    {
        this.taskState = taskState;
        this.progress = progress;
    }

    public override ICustomization GetCustomization(ParameterInfo parameter)
    {
        if (parameter == null)
        {
            throw new ArgumentNullException("parameter");
        }

        var result = new List<ICustomization> { new PropertyCustomization<Task>("TaskState", this.taskState) };

        if (this.progress > -1)
        {
            result.Add(new PropertyCustomization<Task>("Progress", this.progress));
        }

        return new CompositeCustomization(result);
    }
}

所以,如果我用它来像那里那样只指定状态,它运行良好并构建匿名任务

[Theory, AutoMoqData]
public void TestSomething([AutoTask(TaskState.InProgress)]Task task)
{...}

但是如果我想同时设置状态和进度,它出于某种原因只设置了第二个 属性,虽然两个 'Do' 代表都被调用了,但是在第二个调用中它接收到任务再次默认状态。

[Theory, AutoMoqData]
public void TestSomething([AutoTask(TaskState.InProgress, 50)]Task task)
{...}

我怀疑具有多个基于 'Do' 的自定义的 CompositeCustomization 是原因,但不明白为什么。

它不起作用,因为通过 Customize 进行的每个下一个 Type 自定义都会完全覆盖前一个(感谢 Mark 的解释)。

所以我将自定义更改为配置类型而不是其 属性:

public class TypeCustomization<T> : ICustomization
{
    private List<Action<T>> actions;

    public TypeCustomization()
    {
        this.actions = new List<Action<T>>();
    }

    public void Customize(IFixture fixture)
    {
        fixture.Customize<T>(
            cmp =>
                {
                    return this.actions.Aggregate<Action<T>, IPostprocessComposer<T>>(cmp, (current, next) => current.Do(next));
                });
    }

    public TypeCustomization<T> With(string propertyName, object value)
    {
        this.actions.Add(obj => obj.SetProperty(propertyName, value));
        return this;
    }
}

并且可以像这样在属性定义中使用:

[AttributeUsage(AttributeTargets.Parameter)]
public sealed class AutoTaskAttribute : CustomizeAttribute
{
    private readonly int progress;

    private readonly TaskState taskState;

    public AutoTaskAttribute(TaskState taskState, int progress = -1)
    {
        this.taskState = taskState;
        this.progress = progress;
    }

    public override ICustomization GetCustomization(ParameterInfo parameter)
    {
        var customization = new TypeCustomization<Task>().With("TaskState", this.taskState);

        if (this.progress > -1)
        {
            customization.With("Progress", this.progress);
        }

        return customization;
    }
}

你为什么不做下面的事情呢?

[Theory, AutoMoqData]
public void TestSomething(Task task)
{
    task.TaskState = TaskState.InProgress;

    // The rest of the test...
}

还是这个?

[Theory, AutoMoqData]
public void TestSomething(Task task)
{
    task.TaskState = TaskState.InProgress;
    task.Progress = 50;

    // The rest of the test...
}

这要简单得多,而且类型安全...