14

次のようなテストを作成しました。このテストでは、Equal-classのen-overloadを作成している必要がありCodeTableItemます。

ICollection<CodeTableItem> expectedValutaList = new List<CodeTableItem>();
expectedValutaList.Add(new CodeTableItem("DKK", "DKK"));
expectedValutaList.Add(new CodeTableItem("EUR", "EUR"));
RepoDac target = new RepoDac(); 

var actual = target.GetValutaKd();

CollectionAssert.AreEqual(expectedValutaList.ToList(),actual.ToList());

テストは正常に機能しますが、残念ながらEquality-functionに依存しています。つまり、CodeTableItem-classをもう1つのフィールドで拡張し、-functionを拡張するのを忘れたEquals場合、すべてのフィールドをテストするわけではありませんが、単体テストは引き続き緑色で実行されます。 。テストに準拠するためだけに作成されたこのEquality汚染(テスト固有の同等性を参照)を回避したいと考えています。

を使用してみOfLikenessましたが、次のようにテストを書き直しました。

ICollection<CodeTableItem> expectedValutaList = new List<CodeTableItem>();
expectedValutaList.Add(new CodeTableItem("DKK", "DKK"));
expectedValutaList.Add(new CodeTableItem("EUR", "EUR"));
var expectedValutaListWithLikeness = 
          expectedValutaList.AsSource().OfLikeness<List<CodeTableItem>>();

RepoDac target = new RepoDac(); 
ICollection<CodeTableItem> actual;

actual = target.GetValutaKd();

expectedValutaListWithLikeness.ShouldEqual(actual.ToList());

しかし、Capacityが等しくないため、テストは失敗します。リフレクションを何度も実行するコードを作成しましたが、通常、フィールドを無視するためのオーバーロードを実装することになりました。またはで特定のフィールドを無視する方法はありますOfLikenessShouldEqual?または、この問題を解決する他の方法はありますか?

4

5 に答える 5

12

なぜそんなことをしたくないのか

私は、任意のものから似顔絵を作成してもList<T>、あなたがやりたいことをするとは思いません。私が理解しているように、2 つのリストの内容を比較したいと考えています。それは2つのリストを比較することと同じではありません...

プロパティの値を比較します。のプロパティはList<T>何ですか?

彼らです

  • 容量
  • カウント

Nikos Baxevanis が彼の回答で指摘しているように、Without メソッドを使用して Capacity プロパティの値を無視できますが、これは Count プロパティだけが残ることを意味します。

言い換えれば、あなたがそれをしたなら、これは:

expectedValutaListWithLikeness.ShouldEqual(actual.ToList());

機能的にはこれと同等です:

Assert.AreEqual(expected.Count, actual.Count)

言い換えれば、リストはまったく異なるデータを持つことができますが、各リストが同じ量の要素を持っている場合でも、テストはパスします。それはおそらくあなたが望むものではありません...

あなたがすべきこと

Likeness を使用して、各要素を相互に比較できます。このようなものが動作するはずです:

var expectedValutaList = new List<CodeTableItem>();
expectedValutaList.Add(new CodeTableItem("DKK", "DKK"));
expectedValutaList.Add(new CodeTableItem("EUR", "EUR"));

var expectedValutaListWithLikeness = from cti in expectedValutaList
                                     select cti
                                         .AsSource()
                                         .OfLikeness<CodeTableItem>();

var target = new RepoDac(); 

var actual = target.GetValutaKd();

Assert.IsTrue(expectedValutaListWithLikeness.Cast<object>().SequenceEqual(
    actual.Cast<object>()));

アサーションにCollectionAssertを使用することもできますが、MSTestを最後に使用してから何年も経っているため、そのメソッドの癖を思い出せません...

于 2012-07-30T11:56:15.350 に答える
8

を追加するだけ.Without(x => x.Capacity)で、Likeness インスタンスはCapacity値を比較するときにプロパティを無視します。

var expectedValutaListWithLikeness = 
      expectedValutaList.AsSource().OfLikeness<List<CodeTableItem>>()
      .Without(x => x.Capacity);

アップデート:

Mark Seemann が彼の回答で指摘しているように、おそらく必要なのは、各要素を互いに比較することです。ここでは、非常に柔軟な比較を実行できる、少し異なる方法を示します。

RepoDac クラスが次のようなものを返すと仮定します。

public class RepoDac
{
    public ICollection<CodeTableItem> GetValutaKd()
    {
        return new[]
        {
            new CodeTableItem("DKK", "DKK"),
            new CodeTableItem("EUR", "EUR")
        };
    }
}

上のインスタンスごとに、expectedValutaListLikeness を使用して Equals をオーバーライドする動的プロキシを作成できます。

var object1 = new CodeTableItem("DKK", "DKK1")
    .AsSource().OfLikeness<CodeTableItem>()
    .Without(x => x.Property2)
    .CreateProxy();

var object2 = new CodeTableItem("EUR2", "EUR")
    .AsSource().OfLikeness<CodeTableItem>()
    .Without(x => x.Property1)
    .CreateProxy();

object1 と object2 が動的に生成された異なる Equals を持っていることに注目してください。(1 つ目は Property2 を無視し、2 つ目は Property1 を無視します。)

以下のテストに合格します。

var expected = new List<CodeTableItem>();
expected.Add(object1);
expected.Add(object2);

var target = new RepoDac();
var actual = target.GetValutaKd();

Assert.IsTrue(expected.SequenceEqual(actual));

ノート:

動的に生成されたプロキシを含むインスタンスから開始する必要がありexpectedます (Equals をオーバーライドします)。

この機能の詳細については、こちらを参照してください。

于 2012-07-30T10:07:37.100 に答える
3

The following answer emanated from me asking myself a duplicate of this question, see below

You could use a SequenceLike operation which alludes to LINQ's SequenceEqual operator.

This allows one to write:-

[Theory, AutoData]
public void ShouldMap(  Dto inputDto )
{
    var mapped = inputDto.ToModel();

    inputDto.AsSource().OfLikeness<Model>()
        .Without( x => x.IgnorableProperty )
        .With( x => x.Tags ).EqualsWhen( ( dto, model ) => 
            model.Tags.SequenceLike( dto.Tags ) )
        .ShouldEqual( mapped );
}

Shiny short implementation of all-in-one helper based on @Mark Seemann's answer thanks to prompting from @Nikos Baxevanis:-

static class LikenessSequenceExtensions
{
    public static bool SequenceLike<T, TSource>( this IEnumerable<T> that, IEnumerable<TSource> source )
    {
        return SequenceLike<T, TSource>( that, source, x => x );
    }

    public static bool SequenceLike<T, TSource>( this IEnumerable<T> that, IEnumerable<TSource> source, Func<Likeness<TSource, T>, IEquatable<T>> customizeLikeness )
    {
        return source.Select( x => customizeLikeness( x.AsSource().OfLikeness<T>() ) ).SequenceEqual( that.Cast<object>() );
    }
}

My original implementation:

static class LikenessSequenceExtensions0
{
    public static bool SequenceLike0<T, TSource>( this T[] that, TSource[] source )
    {
        return source.SequenceLike0( that, likeness => likeness );
    }

    public static bool SequenceLike0<T, TSource>( this T[] that, TSource[] source, Func<Likeness<TSource, T>, IEquatable<T>> customizeLikeness )
    {
        return source.SequenceEqual( that, ( x, y ) => customizeLikeness( x.AsSource().OfLikeness<T>() ).Equals( y ) );
    }

    public static bool SequenceEqual<T, TSource>( this T[] that, TSource[] source, Func<T, TSource, bool> equals )
    {
        return that.Length == source.Length && that.Zip( source, Tuple.Create ).All( x => equals( x.Item1, x.Item2 ) );
    }
}

Original duplicate question

I'm looking for the cleanest way to manage Test Specific Equality for arrays/IEnumerable<T>/seq<'T> in my xunit.AutoFixture-based tests.

OOTB (I've lost the reference to where I learned this), Ploeh.SemanticComparison's Likeness versions up to 2.12 works on individual items only.

What is the best way to apply the same techniques to collections of items (ideally OOTB but very open to well-judged suite of extension methods) to facilitate expressing likenesses of items that include embedded objects in a composable manner?

This really is a self-answer so I can stash the helpers and allow a place to put a "you do it this way in V n.n" should Likeness offer sequence support in the future, but it wouldn't be the first time I've been surprised with the subtlety of answer possible from AFflicted AFicionados

于 2012-10-10T12:21:50.957 に答える
3

私は、この問題を抱えている他の人のためにこれを明示したいと思いました.Rubenの2番目のコード例を使用して、CalendarとCalendar.Holidaysの両方を比較し、両方の比較をカスタマイズします:

var expectedCalendar = newCalendar.AsSource()
   .OfLikeness<Calendar>()
   .Without(c=>c.Id) //guid, will never be equal
   .With(c=>c.Holidays).EqualsWhen((source, dest) => 
      source.Holidays.SequenceLike(dest.Holidays, holiday => 
          holiday.Without(h=>h.SecondsUntil) //changes every second
   ));

この例では、最初に Calendar オブジェクトで除外するプロパティなどを設定します。次に、Holidays コレクションを処理するためのカスタム EqualsWith 実装を提供します。holiday=> ラムダを使用すると、親と同様に、子の比較をカスタマイズできます。たくさんの括弧を楽しんでいる限り、入れ子を続けることができます。

于 2012-12-18T06:01:27.297 に答える