【问题标题】:Explicitly Set up multiple properties of specimen using AutoFixture customizations使用 AutoFixture 自定义显式设置样本的多个属性
【发布时间】:2015-08-09 07:29:50
【问题描述】:

我想利用 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)
{...}

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

【问题讨论】:

  • 能否将TaskStateSetProperty 的源代码也包含在内?
  • TestSomething 方法是如何修饰的?
  • 我更新了初始帖子。 SetProperty 只是使用反射来简单地设置对象的属性,AutoMoqData 属性应用默认的 AutoMoqCustomization
  • 您自定义了两次Task,但其中一个覆盖了另一个。
  • 有什么简单的方法可以“组合”“Do”自定义项吗?

标签: c# xunit autofixture


【解决方案1】:

你为什么不做以下事情?

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

这更简单,而且类型安全...

【讨论】:

  • 主要思想是完全(或多或少)移动方法参数中的排列语句。我也相信通常 [Task(InProgress)] 任务比拆分声明和分配更具可读性/智能。当然,如果要设置的参数超过 2-3 个,则值得使用这种方法:)
  • ...您选择的编程语言在为属性赋值方面非常好。为什么要让它变得更难?
  • 使测试更小。当然,如果测试足够简单并且只需要一项任务来分配这两个属性,我会明确地这样做。但是想象一下,如果您需要通过 4-5 次不同状态和进度的操作来测试处于适当状态的任务。当然,您可以在排列阶段明确地执行此操作,但我相信通过属性执行此操作可能会真正简化进一步的测试阅读和理解。毕竟,C# 语法完全可以在没有 xUnit 属性的情况下使用,只需使用显式的夹具实例即可。但我想试试这种方式
  • 顺便说一句,非常感谢您提供的令人惊叹的 AutoFixture。
  • @RR-Fireball ...但它提供了什么价值?如果您需要在属性中调用显式值,您既没有节省打字也没有节省认知开销,但是您失去了类型安全性。您仍然可以使用[AutoData] 来提供填充了所有其他属性的初始值,但如果您有对测试用例很重要的属性值,则需要在某处声明这些值。
【解决方案2】:

它不起作用,因为通过自定义的每个下一个类型自定义都会完全覆盖前一个(感谢 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;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-06-26
    • 1970-01-01
    • 1970-01-01
    • 2022-07-13
    • 1970-01-01
    • 2020-12-30
    • 1970-01-01
    相关资源
    最近更新 更多