gpt4 book ai didi

autofixture - 列表中项目的单元测试顺序

转载 作者:行者123 更新时间:2023-12-04 10:52:46 26 4
gpt4 key购买 nike

如何设置确定性测试以验证列表中的项目是否已排序?

首先,我执行以下操作:

public void SyncListContainsSortedItems(
[Frozen] SyncItem[] expected,
SyncItemList sut)
{
Assert.Equal(expected.OrderBy(x => x.Key).First(), sut.First());
}

但是,与所有良好的测试一样,在修改代码之前,我首先会查找失败。当然,这是成功的,如幸运的话,后来又失败了。因此,我最初的失败不是确定性的。

其次,我做了以下思考,“一定会确保失败”:
public void SyncListContainsSortedItems(
[Frozen] SyncItem[] seed,
SyncItemList sut)
{
var expected = seed.OrderByDescending(x => x.Key);
Assert.Equal(expected.OrderBy(x => x.Key).First(), sut.First());
}

令我惊讶的是,它也没有提供确定性的失败。我意识到这是因为卡住的种子可能从一开始就以降序自然生成,所以我真的没有改善这种情况。

现在,我的实现不对通过构造函数传递的项进行排序。如何为测试建立坚实的基准?

附加信息同步项目列表代码如下所示。它与其说是我正在探索的设计,不如说是:
public class SyncItemList : List<SyncItem>
{
public SyncItemList(SyncItem[] input)
{
foreach (var item in input) { this.Add(item); }
}
}

更新我一直在开发测试。以下作品,但具有很高的冗长性。
public void SyncListContainsSortedItems(IFixture fixture, List<SyncItem> seed)
{
var seconditem = seed.OrderBy(x => x.Key).Skip(1).First();
seed.Remove(seconditem);
seed.Insert(0, seconditem);
var seedArray = seed.ToArray();

var ascending = seedArray.OrderBy(x => x.Key).ToArray();
var descending = seedArray.OrderByDescending(x => x.Key).ToArray();
Assert.NotEqual(ascending, seedArray);
Assert.NotEqual(descending, seedArray);

fixture.Inject<SyncItem[]>(seedArray);
var sut = fixture.Create<SyncItemList>();

var expected = ascending;
var actual = sut.ToArray();
Assert.Equal(expected, actual);
}

更改实现以使其通过的一种简单方法是从 SortedSet<SyncItem>继承,而不是从 List<SyncItem>继承。

最佳答案

有多种方法可以解决此问题。

命令式版本

这是比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);
}

虽然 the default number of many items is 3,但我认为最好在此测试用例中明确指出它。这里使用的加扰算法利用了以下事实:对三个(不同)元素的序列进行排序之后,将第一个元素移到后面必须导致无序列表。

但是,这种方法的问题在于它依赖于对 fixture的变异,因此很难重构为更具声明性的方法。

自定义版本

为了重构为更具声明性的版本,您可以首先将加扰算法封装在 a 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();
}
}
}

解决 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);
}

注意 Freeze方法的使用。这是必需的,因为 UnorderedSyncItems自定义仅更改 SyncItem[]实例的创建方式;请参见表2-1。每次收到请求时,它仍会创建一个新数组。 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][Frozen]属性的使用。

该测试非常简洁,但可能不是您要追求的。问题在于行为的变化现在隐藏在 [UnorderedSyncItems]属性中,因此它隐含了正在发生的事情。我更喜欢对整个测试套件使用与一组约定相同的“自定义”,因此我不喜欢在此级别引入测试用例的变体。但是,如果您的约定指出 SyncItem[]实例应始终是无序的,则此约定是好的。

但是,如果只希望对某些测试用例使用无序数组,则使用 [AutoData]属性并不是最佳方法。

声明式测试用例

如果您可以像 [Frozen]属性一样简单地应用一个参数级属性-也许将它们组合在一起,例如 [Unordered][Frozen],那将是很好的。但是,这种方法不起作用。

从前面的示例中注意到,顺序很重要。您必须在卡住之前应用 UnorderedSyncItems,因为否则,可能不能保证卡住的数组无序。
[Unordered][Frozen]参数级属性的问题在于,在编译时,.NET框架无法保证AutoFixture xUnit.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]派生属性中的一组(其他)测试套件范围的约定,并且仍将 [UnorderedFrozen]用作选择加入机制。

关于autofixture - 列表中项目的单元测试顺序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17867566/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com