Как утверждать, что два списка содержат элементы с одинаковыми общедоступными свойствами в 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 для разных объектов с одинаковым значением). В моем тесте я забочусь только о значениях публичных свойств, а не о том, равны ли объекты. Что я могу сделать, чтобы сделать свое утверждение?

9 ответов


ПЕРЕРАБОТАННЫЙ ОТВЕТ

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

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

Enumerable.OrderBy обеспечивает перегрузку, которая принимает


нет, 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;
}

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

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


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

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

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

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

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

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


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

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

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

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

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


один из вариантов-написать пользовательские ограничения для сравнения элементов. Вот хорошая статья на эту тему: http://www.davidarno.org/2012/07/25/improving-nunit-custom-constraints-with-syntax-helpers/


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

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

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

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

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

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

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


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

Сериализуйте объект (я рекомендую 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, как Итак:

...: "a","Bar2":"b"}, {"Bar":"d","Bar2": "d"}]

...: "a","Bar2":"b"}, {"Bar":"c","Bar2": "d"}]

...__________________^_____


простой код, объясняющий, как использовать IComparer

using System.Collections;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CollectionAssert
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            IComparer collectionComparer = new CollectionComparer();
            var expected = new List<SomeModel>{ new SomeModel { Name = "SomeOne", Age = 40}, new SomeModel{Name="SomeOther", Age = 50}};
            var actual = new List<SomeModel> { new SomeModel { Name = "SomeOne", Age = 40 }, new SomeModel { Name = "SomeOther", Age = 50 } };
            NUnit.Framework.CollectionAssert.AreEqual(expected, actual, collectionComparer);
        }
    }

    public class SomeModel
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class CollectionComparer : IComparer, IComparer<SomeModel>
    {
        public int Compare(SomeModel x, SomeModel y)
        {
            if(x == null || y == null) return -1;
            return x.Age == y.Age && x.Name == y.Name ? 0 : -1;
        }

        public int Compare(object x, object y)
        {
            var modelX = x as SomeModel;
            var modelY = y as SomeModel;
            return Compare(modelX, modelY);
        }
    }
}