9

リスト内の項目が順序付けられていることを確認する決定論的テストをセットアップするにはどうすればよいですか?

まず、次のことを行いました。

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

しかし、すべての優れたテストと同様に、コードを変更する前にまず失敗を探しました。もちろん、これは運が良ければ成功し、その後失敗しました。したがって、私の最初の失敗は決定論的ではありません。

2番目に、「これは確実に失敗するだろう」と考えて、次のことを行いました。

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

実装を変更してパスさせる簡単な方法の 1 つは、SortedSet<SyncItem>の代わりに から継承することですList<SyncItem>

4

1 に答える 1

12

これにはさまざまな方法があります。

命令バージョン

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ですが、このテスト ケースでは明示的に呼び出した方がよいと思います。ここで使用されるスクランブリング アルゴリズムは、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);
}

Freezeメソッドの使用に注意してください。UnorderedSyncItemsカスタマイズはSyncItem[]インスタンスの作成方法のみを変更するため、これが必要です。要求を受け取るたびに、新しい配列を作成します。インスタンスを作成するFreezeときも、毎回同じ配列が再利用されるようにします。fixturesut

慣習に基づくテスト

上記のテストは、[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]は、コンパイル中に、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]これにより、派生属性にカプセル化された一連の (その他の) テスト スイート全体の規則を使用することも可能になり[UnorderedFrozen]、オプトイン メカニズムとして使用することもできます。

于 2013-07-28T11:29:21.710 に答える