Assert.AreEqual не использует переопределения .Equals в реализации IEnumerable

У меня есть класс PagedModel, который реализует IEnumerable, чтобы просто вернуть ModelData, игнорируя данные подкачки. Я также переопределил Equals и GetHashCode, чтобы сравнить два объекта PagedModel по их параметрам ModelData, PageNumber и TotalPages и PageSize.

Здесь проблема

Dim p1 As New PagedModel() With {
    .PageNumber = 1,
    .PageSize = 10,
    .TotalPages = 10,
    .ModelData = GetModelData()
}

Dim p2 As New PagedModel() With {
    .PageNumber = 1,
    .PageSize = 10,
    .TotalPages = 10,
    .ModelData = GetModelData()
}

p1.Equals(p2) =====> True
Assert.AreEqual(p1, p2) ======> False!

Похоже, что NUnit называет его внутренним методом EnumerableEqual для сравнения моего PagedModel вместо использования методов Equals, которые я предоставил! Есть ли способ переопределить это поведение или мне нужно написать настраиваемое утверждение.

Ответы

Ответ 1

Выполнение того, что вы просите. Я бы посоветовал это, но если вам действительно не нравится поведение NUnit и вы хотите настроить утверждение, вы можете предоставить свой собственный EqualityComparer.

Assert.That(p1, Is.EqualTo(p2).Using(myCustomEqualityComparer));

Что вы должны делать (короткий ответ): вам нужен GetHashCode и равен ModelData вместо PagedModel, поскольку вы используете PagedModel в качестве коллекции и ModelData как элементы.

Что вы должны делать (длинный ответ): Вместо переопределения Equals (object) на PagedModel вам нужно реализовать IEquatable <T> в ModelData, где T является параметром типа для IEnumerable, а также переопределяет GetHashCode(). Эти два метода - это то, что все методы IEnumerable в .Net используют для определения равенства (для операций, таких как Union, Distinct и т.д.) При использовании Default Equality Comparer (вы не указываете свой собственный IEqualityComparer).

[Default Equality Comparer] проверяет, реализует ли тип T интерфейс System.IEquatable, и, если это так, возвращает EqualityComparer, который использует эту реализацию. В противном случае он возвращает EqualityComparer, который использует переопределения Object.Equals и Object.GetHashCode, предоставленные T.


Чтобы правильно функционировать, GetHashCode должен возвращать те же результаты для всех объектов, которые возвращают true для .Equals(T). Обратное не обязательно верно - GetHashCode может возвращать столкновения для объектов, которые не равны. Дополнительная информация здесь - см. ответ Марка Гравеля. Я также нашел реализацию GetHashCode в этом ответе, используя простые значения.

Ответ 2

Если вы посмотрите на реализацию сопоставления равенства NUnit в GIT repo, вы увидите, что есть выделенный блок сравнения для двух перечислений, который имеет более высокий приоритет (просто потому, что он помещен выше), чем сравнения с использованием интерфейса IEquatable<T> или Object.Equals(Object), который вы внедрили или перегрузили в своем классе PagedModel.

Я не знаю, является ли это ошибкой или функцией, но вы, вероятно, должны сначала спросить себя, если реализация интерфейса IEnumerable<ModelData> непосредственно вашим классом PagedModel на самом деле является лучшим вариантом, особенно потому, что ваш PagedModel это нечто большее, чем просто перечисление экземпляров ModelData.

Вероятно, было бы достаточно (или даже лучше) предоставить перечисление ModelData через простое свойство IEnumerable<ModelData> только для чтения класса PagedModel. NUnit перестанет смотреть на ваш объект PagedModel, как при простой перечислении объектов ModelData, и ваши модульные тесты будут вести себя так, как ожидалось.

Единственный другой вариант - это предложение, предлагаемое csauve; для реализации простой пользовательской IComparer для вашего PagedModel и для поставки экземпляра для всех утверждений, где вы сравниваете два экземпляра PagedModel:

internal class PagedModelComparer : System.Collections.IComparer
{
    public static readonly IComparer Instance = new PagedModelComparer();

    private PagedModelComparer()
    {
    }

    public int Compare( object x, object y )
    {
        return x is PagedModel && ((PagedModel)x).Equals( y );
    }
}

    ...
    [Test]
    ...
        Assert.That( actual, Is.EqualTo( expected ).Using( PagedModelComparer.Instance ) );
    ...

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