Применение [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 -функцию, unit test все еще работает зеленым цветом, хотя мы не проверяем все поля. Мы хотим избежать этого загрязнения 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? Или есть ли другой способ решения этой проблемы?

Ответ 1

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

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

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

Они

  • Емкость
  • Count

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

Ответ 2

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

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

Update:

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

Предполагая, что класс 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();

Обратите внимание, что у объекта 1 и объекта2 есть даже разные динамически генерируемые равные. (Первый игнорирует Property2, а второй игнорирует 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, который содержит динамически сгенерированные прокси (переопределяя Равные).

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

Ответ 3

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

Вы можете использовать операцию 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

Ответ 4

Я хотел сделать это явным для других, имеющих эту проблему, - используя второй пример кода Ruben, где вы хотите сравнить календарь и 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. Праздник = > лямбда затем позволяет настроить детское сравнение, как и родительский. Вы можете продолжить вложенность, пока вы наслаждаетесь множеством круглых скобок.

Ответ 5

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

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