使用 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...
}
这要简单得多,而且类型安全...
我想利用 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...
}
这要简单得多,而且类型安全...