Применение [AutoFixture] SemanticComparison OfLikeness к последовательностям / коллекциям / массивам / IEnumerable

Мы написали тест, который выглядит следующим образом. Этот тест требует, чтобы мы создали en Equal-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-функции, что означает, что если я расширю CodeTableItem-класс еще одним полем и забуду расширить Equals-функцию, модульный тест по-прежнему будет работать зеленым, хотя мы не тестируем для всех полей. Мы хотим избежать этого Equality загрязнения (см. Test Specific 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 не равно. Я написал код, который многократно проходит через рефлексию, и, как правило, в конечном итоге реализовал перегрузки для игнорирования полей. Есть ли способ игнорировать определенные поля с OfLikeness или ShouldEqual? Или есть другой способ решить эту проблему?


person Tom Kise    schedule 30.07.2012    source источник


Ответы (5)


Почему вы не хотите этого делать?

Я не думаю, что создание подобия из какого-либо List<T> делает то, что вы хотите. Насколько я понимаю, вы хотите сравнить содержимое двух списков. Это не то же самое, что сравнивать два списка ...

Посмотрите, что делает Likeness: оно сравнивает значения property. Каковы свойства List<T>?

Они есть

  • Емкость
  • Считать

Как указывает Никос Баксеванис в своем ответе, вы можете использовать метод 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, прошло столько лет, что я не могу вспомнить причуды этого метода ...

person Mark Seemann    schedule 30.07.2012
comment
Большое спасибо, Марк, что сделал это! Элегантно! - person Tom Kise; 02.08.2012

Просто добавьте .Without(x => x.Capacity), и экземпляр Likeness проигнорирует свойство Capacity при сравнении значений.

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

Обновление:

Как отмечает в своем ответе Марк Симанн, вы, вероятно, захотите сравнить каждый элемент друг с другом. Вот несколько другой способ, позволяющий выполнять очень сравнения.

Предполагая, что класс RepoDac возвращает что-то вроде:

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

Для каждого экземпляра на expectedValutaList вы можете создать динамический прокси, который переопределяет Equals, используя Likeness:

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. (Первый игнорирует свойство 2, а второй игнорирует свойство 1.)

Приведенный ниже тест проходит успешно:

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).

Дополнительную информацию об этой функции можно найти здесь.

person Nikos Baxevanis    schedule 30.07.2012

Я хотел сделать это явным для других, у которых есть эта проблема, - используя второй пример кода Рубена, где вы хотите сравнить 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. Затем вы даете настраиваемую реализацию EqualsWith для обработки коллекции Holidays. Затем лямбда holiday => позволяет настраивать дочернее сравнение, как и родительское. Вы можете продолжать вложение, пока вам нравится много скобок.

person russbishop    schedule 18.12.2012
comment
+1 Красиво. И эти дружелюбные обнимающие скобки очень утешают :) - person Ruben Bartelink; 25.01.2013

Следующий ответ исходил от того, что я задавал себе дубликат этого вопроса, увидеть ниже

Вы можете использовать операцию SequenceLike, которая ссылается на оператор LINQ SequenceEqual.

Это позволяет написать: -

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

Блестящая короткая реализация универсального помощника на основе ответа @Mark Seemann благодаря подсказке @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>() );
    }
}

Моя оригинальная реализация:

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

Оригинальный повторяющийся вопрос

Я ищу самый чистый способ управления Test Specific Equality для массивов / IEnumerable<T> / seq<'T> в моих тестах на базе xunit.AutoFixture.

OOTB (я потерял ссылку на то, где я это узнал), версии Ploeh.SemanticComparison Likeness до 2.12 работают только с отдельными элементами.

Как лучше всего применить одни и те же методы к коллекциям элементов (в идеале OOTB, но очень открытым для хорошо продуманного набора методов расширения), чтобы облегчить выражение сходства элементов, которые включают встроенные объекты, составным образом?

Это действительно ответ для себя, так что я могу спрятать помощников и разрешить место для размещения "сделай это так в V nn", если Likeness предложит поддержку последовательности в будущем, но это будет не первый время я был удивлен тонкостью ответа, который может дать AFflicted AFicionados

person Ruben Bartelink    schedule 10.10.2012

Я не уверен, что этот вопрос по-прежнему актуален, но вы ищете https://github.com/jmansar/SemanticComparisonExtensions

Вы можете использовать .WithCollectionInnerLikeness () для сравнения коллекций, и вы получите именно то, что хотите.

person jjm340    schedule 08.09.2017