Подтвердить что ты не робот

Как утверждать, что два списка содержат элементы с теми же общедоступными свойствами в NUnit?

Я хочу утверждать, что элементы из двух списков содержат значения, которые я ожидал, например:

var foundCollection = fooManager.LoadFoo();
var expectedCollection = new List<Foo>() 
{
    new Foo() { Bar = "a", Bar2 = "b" },
    new Foo() { Bar = "c", Bar2 = "d" }
};

//assert: I use AreEquivalent since the order does not matter
CollectionAssert.AreEquivalent(expectedCollection, foundCollection);

Однако приведенный выше код не будет работать (я думаю, потому что .Equals() не возвращает true для разных объектов с одинаковым значением). В моем тесте я забочусь только о значениях публичной собственности, а не о том, равны ли объекты. Что я могу сделать, чтобы сделать свое утверждение?

4b9b3361

Ответ 1

ЗАВЕРШЕННЫЙ ОТВЕТ

Существует перегрузка CollectionAssert.AreEqual(IEnumerable, IEnumerable, IComparer), чтобы утверждать, что две коллекции содержат одни и те же объекты в том же порядке, используя реализацию IComparer для проверки эквивалентности объекта.

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

Enumerable.OrderBy обеспечивает перегрузку, которая принимает аргумент IComparer<T>. Чтобы гарантировать, что две коллекции сортируются в том же порядке, более или менее необходимо, чтобы типы идентифицирующих свойств реализовали IComparable. Вот пример класса-компаратора, который реализует интерфейсы IComparer и IComparer<Foo>, и где предполагается, что Bar имеет преимущество при заказе:

public class FooComparer : IComparer, IComparer<Foo>
{
    public int Compare(object x, object y)
    {
        var lhs = x as Foo;
        var rhs = y as Foo;
        if (lhs == null || rhs == null) throw new InvalidOperationException();
        return Compare(lhs, rhs);
    }

    public int Compare(Foo x, Foo y)
    {
        int temp;
        return (temp = x.Bar.CompareTo(y.Bar)) != 0 ? temp : x.Bar2.CompareTo(y.Bar2);
    }
}

Чтобы утверждать, что объекты из двух коллекций одинаковы и входят в равные числа (но не обязательно в том же порядке), следующие строки должны делать трюк:

var comparer = new FooComparer();
CollectionAssert.AreEqual(
    expectedCollection.OrderBy(foo => foo, comparer), 
    foundCollection.OrderBy(foo => foo, comparer), comparer);    

Ответ 2

Нет, NUnit не имеет такого механизма, как текущего состояния. Вам придется перевернуть свою собственную логику утверждения. Или как отдельный метод, либо используя Has.All.Matches:

Assert.That(found, Has.All.Matches<Foo>(f => IsInExpected(f, expected)));

private bool IsInExpected(Foo item, IEnumerable<Foo> expected)
{
    var matchedItem = expected.FirstOrDefault(f => 
        f.Bar1 == item.Bar1 &&
        f.Bar2 == item.Bar2 &&
        f.Bar3 == item.Bar3
    );

    return matchedItem != null;
}

Это, конечно, предполагает, что вы знаете все соответствующие свойства upfront (иначе IsInExpected придется прибегать к отражению), и этот порядок элементов не имеет значения.

(И ваше предположение было правильным, в коллекции NUnit используются сопоставления по умолчанию для типов, которые в большинстве случаев определяемых пользователем будут объектом ReferenceEquals)

Ответ 3

Использование Has.All.Matches() отлично подходит для сравнения коллекции найденных с коллекцией ожидаемой. Однако нет необходимости определять предикат, используемый Has.All.Matches() в качестве отдельной функции. Для относительно простых сравнений предикат может быть включен как часть выражения лямбда, подобного этому.

Assert.That(found, Has.All.Matches<Foo>(f => 
    expected.Any(e =>
        f.Bar1 == e.Bar1 &&
        f.Bar2 == e.Bar2 &&
        f.Bar3= = e.Bar3)));

Теперь, хотя это утверждение гарантирует, что каждая запись в коллекции найденная также существует в коллекции ожидаемая, она не доказывает обратное, а именно, что каждая запись в ожидаемая коллекция содержится в коллекции найденной. Поэтому, когда важно знать, что найденные и ожидаемые содержат семантически эквивалентные (т.е. Содержат одни и те же семантически эквивалентные записи), мы должны добавить дополнительное утверждение.

Самый простой выбор - добавить следующее.

Assert.AreEqual(found.Count() == expected.Count());

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

Assert.That(expected, Has.All.Matches<Foo>(e => 
    found.Any(f =>
        e.Bar1 == f.Bar1 &&
        e.Bar2 == f.Bar2 &&
        e.Bar3= = f.Bar3)));

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

Ответ 4

Вы пробовали что-то подобное?

Assert.That(foundCollection, Is.EquivalentTo(expectedCollection))

Ответ 5

Для выполнения операций equivilance для сложных типов вам необходимо реализовать IComaprable.

http://support.microsoft.com/kb/320727

В качестве альтернативы вы можете использовать рекурсивное отражение, которое менее желательно.

Ответ 7

У меня была аналогичная проблема. Список участников, в котором содержатся "комментаторы" и другие ppl... Я хочу получить все комментарии и получить от создателей, но я заинтересован только в уникальных создателях. Если кто-то создал 50 комментариев, мне нужно только, чтобы ее имя появилось один раз. Поэтому я пишу тест, чтобы увидеть, что комментаторы - это результат GetContributors().

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

Я решил это так:

Assert.IsTrue(commenters.All(c => actual.Count(p => p.Id == c.Id) == 1));

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

Assert.IsTrue(commenters.length == actual.Count());

Я надеюсь, что это будет полезно, если так, я был бы очень благодарен, если бы вы оценили мой ответ.

Ответ 8

Я рекомендую против использования отражения или чего-то сложного, он просто добавляет больше работы /maintenace.

Сериализуйте объект (я рекомендую json) и строка сравнивают их. Я не уверен, почему вы возражаете против заказа, но я по-прежнему рекомендую его, так как он сохранит пользовательское сравнение для каждого типа.

И он автоматически работает с изменением объектов домена.

Пример (SharpTestsEx для свободного использования)

using Newtonsoft.Json;
using SharpTestsEx;

JsonConvert.SerializeObject(actual).Should().Be.EqualTo(JsonConvert.SerializeObject(expected));

Вы можете написать его как простые расширения и сделать его более читаемым.

   public static class CollectionAssertExtensions
    {
        public static void CollectionAreEqual<T>(this IEnumerable<T> actual, IEnumerable<T> expected)
        {
            JsonConvert.SerializeObject(actual).Should().Be.EqualTo(JsonConvert.SerializeObject(expected));
        }
    }

а затем, используя ваш пример, вызовите его так:

var foundCollection = fooManager.LoadFoo();
var expectedCollection = new List<Foo>() 
{
    new Foo() { Bar = "a", Bar2 = "b" },
    new Foo() { Bar = "c", Bar2 = "d" }
};


foundCollection.CollectionAreEqual(foundCollection);

Вы получите сообщение assert следующим образом:

...: "а", "BAR2": "Ъ" }, { "Бар": "д", "BAR2": "г" }]

...: "а", "BAR2": "Ъ" }, { "Бар": "с", "BAR2": "г" }]

... __________________ ^ _____