Unit order for items in a list

How to configure a deterministic test to verify that items in a list are ordered?

First I did the following:

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

But, as with all good tests, I first looked for a failure before changing my code. Of course, it succeeded, as luck would have it, and then failed. Therefore, my initial failure is not deterministic.

Secondly, I did the following, thinking: "Of course, this will guarantee a failure":

 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()); } 

To my surprise, this also did not provide deterministic failure. I realized that this is because the frozen seed can be created naturally in descending order, so I really have not improved the situation.

Currently, my implementation does not order the elements passing through the constructor. How to set a solid baseline for my test?

Additional Information The code for the list of synchronization items is shown below. This is not so much as the design I am studying:

 public class SyncItemList : List<SyncItem> { public SyncItemList(SyncItem[] input) { foreach (var item in input) { this.Add(item); } } } 

Update . I was developing a test. The following works, but with high detail.

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

One easy way to change my implementation to pass it is to inherit from SortedSet<SyncItem> instead of List<SyncItem> .

+6
source share
1 answer

There are various ways to do this.

Imperative version

Here's a simpler imperative version than the one given in the 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); } 

While the default number for many elements is 3 , I think it’s better to call this explicitly in this test case. The scrambling algorithm used here takes advantage of the fact that after ordering a sequence of three (different) elements, moving the first element in the opposite direction should lead to an unordered list.

However, the problem with this approach is that it is based on the fixture mutation, so it is difficult to refactor a more declarative approach.

Individual version

To reorganize a more declarative version, you can first encapsulate the scrambling algorithm in Settings :

 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(); } } } 

Resolution a new FiniteSequenceRequest(typeof(SyncItem), 3)) is just a weakly typed (not general) way to create a finite sequence of SyncItem instances; this is what CreateMany<SyncItem>(3) does behind the scenes.

This allows you to reorganize the test for:

 [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); } 

Pay attention to the use of the Freeze method. This is necessary because UnorderedSyncItems Customization changes the way you instantiate SyncItem[] ; it still creates a new array every time it receives a request for it. Freeze ensures that the same array is reused every time - also when fixture instantiates a sut .

Protocol Based Testing

The above test can be reorganized into a declarative, convention-based test by introducing the [UnorderedConventions] attribute:

 public class UnorderedConventionsAttribute : AutoDataAttribute { public UnorderedConventionsAttribute() : base(new Fixture().Customize(new UnorderedSyncItems())) { } } 

This is just declarative glue for applying UnorderedSyncItems Customization. The test now becomes the following:

 [Theory, UnorderedConventions] public void ConventionBasedTest( [Frozen]SyncItem[] unorderedItems, SyncItemList sut) { var expected = unorderedItems.OrderBy(si => si.Key); Assert.Equal(expected, sut); } 

Note the use of the [UnorderedSyncItems] and [Frozen] attributes.

This test is very concise, but it may not be what you need. The problem is that the behavior change is now hidden in the [UnorderedSyncItems] attribute, so it’s more likely what is happening. I prefer to use the same setting as the set of conventions for the entire test suite, so I don’t like to introduce test cases at this level. However, if your conventions indicate that SyncItem[] instances should always be unordered, this convention is good.

However, if you want to use unordered arrays for some test cases, using the [AutoData] attribute is not the best approach.

Declarative test case

It would be nice if you could just apply the parameter level attribute like the [Frozen] attribute, perhaps combining them, for example [Unordered][Frozen] . However, this approach does not work.

From the previous examples it is clear that order matters. You must apply UnorderedSyncItems before freezing, as otherwise the frozen array may not be guaranteed unordered.

The problem with attributes of the [Unordered][Frozen] level is that when compiled, the .NET framework does not guarantee the order of the attributes when the AutoFixture xUnit.net gluing library reads and applies attributes.

Instead, you can define one applicable attribute, for example:

 public class UnorderedFrozenAttribute : CustomizeAttribute { public override ICustomization GetCustomization(ParameterInfo parameter) { return new CompositeCustomization( new UnorderedSyncItems(), new FreezingCustomization(parameter.ParameterType)); } } 

( FreezingCustomization provides a basic implementation of the [Frozen] attribute.)

This allows you to write this test:

 [Theory, AutoData] public void DeclarativeTest( [UnorderedFrozen]SyncItem[] unorderedItems, SyncItemList sut) { var expected = unorderedItems.OrderBy(si => si.Key); Assert.Equal(expected, sut); } 

Note that this declarative test uses the [AutoData] attribute by default without any settings, since scrambling is now applied by the [UnorderedFrozen] attribute at the parameter level.

It will also allow you to use a set of (other) common test [AutoData] enclosed in the [AutoData] -derived attribute, and use [UnorderedFrozen] as a selection mechanism.

+10
source

Source: https://habr.com/ru/post/950304/


All Articles