Как утверждать, что два списка содержат элементы с теми же общедоступными свойствами в 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 для разных объектов с одинаковым значением). В моем тесте я забочусь только о значениях публичной собственности, а не о том, равны ли объекты. Что я могу сделать, чтобы сделать свое утверждение?
Ответы
Ответ 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
В качестве альтернативы вы можете использовать рекурсивное отражение, которое менее желательно.
Ответ 6
Один из вариантов заключается в создании пользовательских ограничений для сравнения элементов. Вот хорошая статья на эту тему: http://www.davidarno.org/2012/07/25/improving-nunit-custom-constraints-with-syntax-helpers/
Ответ 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": "г" }]
... __________________ ^ _____