C# 使用自动夹具自定义显式设置试样的多个特性

C# 使用自动夹具自定义显式设置试样的多个特性,c#,xunit,autofixture,C#,Xunit,Autofixture,我想利用xUnit理论和AutoFixture来生成匿名对象,但具有一些明确的属性 这就是我现在拥有的: 测试中的系统 public class Task { public TaskState TaskState { get; set;} public int Progress { get; set; } } 通用定制 public class PropertyCustomization<T> : ICustomization { private reado

我想利用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)));
    }
}
属性来使用它

[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);
    }
}
但是如果我想同时设置state和progress,出于某种原因,它只设置了第二个属性,尽管调用了两个“Do”委托,但在第二次调用中它会再次接收默认状态的任务

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

我怀疑使用多个基于“Do”的自定义项进行复合自定义是原因,但不了解原因。

它不起作用,因为通过自定义进行的下一个类型自定义完全覆盖了上一个类型自定义(感谢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;
    }
}
公共类类型自定义:ICustomization
{
私人名单行动;
公共类型自定义()
{
this.actions=新列表();
}
公共空间自定义(iTexture装置)
{
夹具。定制(
cmp=>
{
返回这个.actions.Aggregate(cmp,(当前,下一个)=>current.Do(下一个));
});
}
使用(字符串propertyName,对象值)的公共类型自定义
{
Add(obj=>obj.SetProperty(propertyName,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)
    {
        var customization = new TypeCustomization<Task>().With("TaskState", this.taskState);

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

        return customization;
    }
}
[AttributeUsage(AttributeTargets.Parameter)]
公共密封类AutoTaskAttribute:自定义特性
{
私有只读进程;
私有只读任务状态任务状态;
公共自动taskattribute(TaskState TaskState,int progress=-1)
{
this.taskState=taskState;
这个。进步=进步;
}
公共覆盖ICCustomization GetCustomization(ParameterInfo参数)
{
var customization=new TypeCustomization().With(“TaskState”,this.TaskState);
如果(this.progress>-1)
{
定制。带有(“Progress”,此为“Progress”);
}
退货定制;
}
}

你为什么不做以下事情呢

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

这要简单得多,而且类型也很安全……

你能把
TaskState
SetProperty
的源代码也包括进来吗?TestSomething方法是如何修饰的?我更新了最初的帖子。SetProperty只是使用反射来简单地设置对象的属性,AutoMoqData属性应用默认的AutoMoqCustomization您正在自定义的
任务
两次,但其中一次会覆盖另一次。有没有简单的方法来“组合”'做'自定义?主要思想是完全(或多或少地)在方法参数中移动排列语句。另外,我认为通常[Task(InProgress)]任务比拆分声明和分配更具可读性/智能性。当然,如果要设置2-3个以上的参数,那么使用这种方法是值得的:)…您选择的编程语言非常擅长为属性赋值。为什么要让它变得更难呢?让测试更小。当然,若测试足够简单,并且只需要一个任务来分配这两个属性,我将显式地这样做。但想象一下,如果您需要在适当的状态下测试任务,并使用4-5个不同状态和进度的操作。当然,您可以在排列阶段显式地执行此操作,但我相信通过属性执行此操作可以真正简化进一步的测试阅读和理解。毕竟,C#语法足够好,完全可以在没有xUnit属性的情况下使用,只需要显式的fixture实例。但是我想试试这种方式顺便说一句,非常感谢你的神奇自动装置。@RR Fireball。。。但它有什么价值呢?如果需要在属性中调用显式值,则既不会节省键入开销,也不会节省认知开销,但会失去类型安全性。您仍然可以使用
[AutoData]
在填充所有其他属性的情况下提供初始值,但如果您有对测试用例很重要的属性值,则需要在某个地方声明这些值。
[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...
}