有多种方法可以解决这个问题。
命令式版本
这是一个比 OP 中提供的更简单的命令式版本:
[Fact]
public void ImperativeTest()
{
var fixture = new Fixture();
var expected = fixture.CreateMany<SyncItem>(3).OrderBy(si => si.Key);
var unorderedItems = expected.Skip(1).Concat(expected.Take(1)).ToArray();
fixture.Inject(unorderedItems);
var sut = fixture.Create<SyncItemList>();
Assert.Equal(expected, sut);
}
虽然许多项目的默认数量是3,但我认为最好在这个测试用例中明确地调用它。这里使用的加扰算法利用了这样一个事实,即在对三个(不同的)元素的序列进行排序之后,将第一个元素移到后面必然会产生一个无序列表。
但是,这种方法的问题在于它依赖于对 . 进行变异fixture
,因此很难重构为更具声明性的方法。
定制版
为了重构为更具声明性的版本,您可以首先将加扰算法封装在Customization中:
public class UnorderedSyncItems : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new UnorderedSyncItemsGenerator());
}
private class UnorderedSyncItemsGenerator : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
var t = request as Type;
if (t == null ||
t != typeof(SyncItem[]))
return new NoSpecimen(request);
var items = ((IEnumerable)context
.Resolve(new FiniteSequenceRequest(typeof(SyncItem), 3)))
.Cast<SyncItem>();
return items.Skip(1).Concat(items.Take(1)).ToArray();
}
}
}
解析 a只是创建有限实例new FiniteSequenceRequest(typeof(SyncItem), 3))
序列的弱类型(非泛型)方法;SyncItem
这就是CreateMany<SyncItem>(3)
幕后的工作。
这使您能够将测试重构为:
[Fact]
public void ImperativeTestWithCustomization()
{
var fixture = new Fixture().Customize(new UnorderedSyncItems());
var expected = fixture.Freeze<SyncItem[]>().OrderBy(si => si.Key);
var sut = fixture.Create<SyncItemList>();
Assert.Equal(expected, sut);
}
注意冻结方法的使用。这是必要的,因为UnorderedSyncItems
定制只会改变SyncItem[]
创建实例的方式;每次收到请求时,它仍然会创建一个新数组。Freeze
确保每次都重用相同的数组 - 在fixture
创建sut
实例时也是如此。
基于约定的测试
[UnorderedConventions]
通过引入一个属性,上述测试可以重构为声明性的、基于约定的测试:
public class UnorderedConventionsAttribute : AutoDataAttribute
{
public UnorderedConventionsAttribute()
: base(new Fixture().Customize(new UnorderedSyncItems()))
{
}
}
这只是应用UnorderedSyncItems
定制的声明粘合剂。现在测试变成:
[Theory, UnorderedConventions]
public void ConventionBasedTest(
[Frozen]SyncItem[] unorderedItems,
SyncItemList sut)
{
var expected = unorderedItems.OrderBy(si => si.Key);
Assert.Equal(expected, sut);
}
注意[UnorderedSyncItems]
and[Frozen]
属性的使用。
这个测试非常简洁,但可能不是你想要的。问题是行为的变化现在隐藏在[UnorderedSyncItems]
属性中,所以它相当隐含正在发生的事情。我更喜欢对整个测试套件使用相同的定制作为一组约定,所以我不喜欢在这个级别引入测试用例变体。但是,如果您的约定规定SyncItem[]
实例应该始终是无序的,那么这个约定是好的。
但是,如果您只想对某些测试用例使用无序数组,则使用[AutoData]
属性并不是最佳方法。
声明式测试用例
如果您可以简单地应用参数级属性,就像[Frozen]
属性一样 - 也许将它们组合起来,例如[Unordered][Frozen]
. 但是,这种方法行不通。
请注意前面的示例,顺序很重要。您必须UnorderedSyncItems
在 Freezing 之前申请,否则可能无法保证被冻结的数组是无序的。
参数级属性的问题[Unordered][Frozen]
在于,当它编译时,当 AutoFixture xUnit.net 粘合库读取和应用属性时,.NET 框架不保证属性的顺序。
相反,您可以定义一个要应用的属性,如下所示:
public class UnorderedFrozenAttribute : CustomizeAttribute
{
public override ICustomization GetCustomization(ParameterInfo parameter)
{
return new CompositeCustomization(
new UnorderedSyncItems(),
new FreezingCustomization(parameter.ParameterType));
}
}
(FreezingCustomization
提供[Frozen]
属性的底层实现。)
这使您能够编写此测试:
[Theory, AutoData]
public void DeclarativeTest(
[UnorderedFrozen]SyncItem[] unorderedItems,
SyncItemList sut)
{
var expected = unorderedItems.OrderBy(si => si.Key);
Assert.Equal(expected, sut);
}
请注意,此声明性测试使用了[AutoData]
没有任何自定义的默认属性,因为加扰现在由[UnorderedFrozen]
属性在参数级别应用。
这也将使您能够使用封装在[AutoData]
-derived 属性中的一组(其他)测试套件范围的约定,并且仍然[UnorderedFrozen]
用作选择加入机制。