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

Мы написали тест, который выглядит следующим образом. Этот тест требует, чтобы мы создали Equalперегрузка для 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 загрязнение (см. Специфическое равенство теста), которое было написано только для соответствия тесту.

Мы пытались использовать 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? Или есть какой-то другой способ решения этой проблемы?

5 ответов

Почему ты не хочешь делать это так

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

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

Они есть

  • Вместимость
  • подсчитывать

Как указывает Никос Баксеванис в своем ответе, вы можете использовать метод Без, чтобы игнорировать значение свойства Capacity, но это означает, что остается только свойство Count.

Другими словами, если вы сделали это, это:

expectedValutaListWithLikeness.ShouldEqual(actual.ToList());

будет функционально эквивалентно этому:

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

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

Что ты должен делать

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

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

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

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

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

Вы могли бы использовать 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 ) );
    }
}

Оригинальный дубликат вопроса

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

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

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

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

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

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

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

Другие вопросы по тегам