Сравните равенство между двумя объектами в NUnit - PullRequest
114 голосов
/ 25 ноября 2008

Я пытаюсь утверждать, что один объект "равен" другому.

Объекты - это просто экземпляры класса с множеством открытых свойств. Есть ли простой способ, чтобы NUnit утверждал равенство на основе свойств?

Это мое текущее решение, но я думаю, что может быть что-то лучше:

Assert.AreEqual(LeftObject.Property1, RightObject.Property1)
Assert.AreEqual(LeftObject.Property2, RightObject.Property2)
Assert.AreEqual(LeftObject.Property3, RightObject.Property3)
...
Assert.AreEqual(LeftObject.PropertyN, RightObject.PropertyN)

То, что я собираюсь сделать, будет в том же духе, что и CollectionEquivalentConstraint, где NUnit проверяет, что содержимое двух коллекций идентично.

Ответы [ 19 ]

114 голосов
/ 25 ноября 2008

Если по какой-либо причине вы не можете переопределить Equals, вы можете создать вспомогательный метод, который перебирает общедоступные свойства путем отражения и утверждает каждое свойство. Примерно так:

public static class AssertEx
{
    public static void PropertyValuesAreEquals(object actual, object expected)
    {
        PropertyInfo[] properties = expected.GetType().GetProperties();
        foreach (PropertyInfo property in properties)
        {
            object expectedValue = property.GetValue(expected, null);
            object actualValue = property.GetValue(actual, null);

            if (actualValue is IList)
                AssertListsAreEquals(property, (IList)actualValue, (IList)expectedValue);
            else if (!Equals(expectedValue, actualValue))
                Assert.Fail("Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expectedValue, actualValue);
        }
    }

    private static void AssertListsAreEquals(PropertyInfo property, IList actualList, IList expectedList)
    {
        if (actualList.Count != expectedList.Count)
            Assert.Fail("Property {0}.{1} does not match. Expected IList containing {2} elements but was IList containing {3} elements", property.PropertyType.Name, property.Name, expectedList.Count, actualList.Count);

        for (int i = 0; i < actualList.Count; i++)
            if (!Equals(actualList[i], expectedList[i]))
                Assert.Fail("Property {0}.{1} does not match. Expected IList with element {1} equals to {2} but was IList with element {1} equals to {3}", property.PropertyType.Name, property.Name, expectedList[i], actualList[i]);
    }
}
99 голосов
/ 27 апреля 2012

Не переопределяйте Equals только для целей тестирования. Это утомительно и влияет на предметную логику. Вместо

Используйте JSON для сравнения данных объекта

Никакой дополнительной логики на ваших объектах. Никаких дополнительных заданий для тестирования.

Просто используйте этот простой метод:

public static void AreEqualByJson(object expected, object actual)
{
    var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
    var expectedJson = serializer.Serialize(expected);
    var actualJson = serializer.Serialize(actual);
    Assert.AreEqual(expectedJson, actualJson);
}

Кажется, это отлично работает. Информация о результатах выполнения теста покажет сравнение строк JSON (граф объектов), чтобы вы могли сразу увидеть, что не так.

Также обратите внимание! Если у вас есть более сложные сложные объекты и вы просто хотите сравнить их части, вы можете ( использовать LINQ для данных последовательности ) создавать анонимные объекты для использования с вышеуказанным методом.

public void SomeTest()
{
    var expect = new { PropA = 12, PropB = 14 };
    var sut = loc.Resolve<SomeSvc>();
    var bigObjectResult = sut.Execute(); // This will return a big object with loads of properties 
    AssExt.AreEqualByJson(expect, new { bigObjectResult.PropA, bigObjectResult.PropB });
}
81 голосов
/ 16 сентября 2011

Попробуйте библиотеку FluentAssertions:

dto.ShouldHave(). AllProperties().EqualTo(customer);

http://www.fluentassertions.com/

Его также можно установить с помощью NuGet.

47 голосов
/ 25 ноября 2008

Переопределить. Уравнения для вашего объекта и в модульном тесте вы можете просто сделать это:

Assert.AreEqual(LeftObject, RightObject);

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

34 голосов
/ 11 января 2009

Я предпочитаю не переопределять Equals только для того, чтобы включить тестирование. Не забывайте, что если вы переопределяете Equals, вам действительно следует переопределить GetHashCode, иначе вы можете получить неожиданные результаты, если, например, используете свои объекты в словаре.

Мне нравится подход, основанный на отражении выше, поскольку он учитывает добавление свойств в будущем.

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

// Sample class.  This would be in your main assembly.
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Unit tests
[TestFixture]
public class PersonTests
{
    private class PersonComparer : IEqualityComparer<Person>
    {
        public bool Equals(Person x, Person y)
        {
            if (x == null && y == null)
            {
                return true;
            }

            if (x == null || y == null)
            {
                return false;
            }

            return (x.Name == y.Name) && (x.Age == y.Age);
        }

        public int GetHashCode(Person obj)
        {
            throw new NotImplementedException();
        }
    }

    [Test]
    public void Test_PersonComparer()
    {
        Person p1 = new Person { Name = "Tom", Age = 20 }; // Control data

        Person p2 = new Person { Name = "Tom", Age = 20 }; // Same as control
        Person p3 = new Person { Name = "Tom", Age = 30 }; // Different age
        Person p4 = new Person { Name = "Bob", Age = 20 }; // Different name.

        Assert.IsTrue(new PersonComparer().Equals(p1, p2), "People have same values");
        Assert.IsFalse(new PersonComparer().Equals(p1, p3), "People have different ages.");
        Assert.IsFalse(new PersonComparer().Equals(p1, p4), "People have different names.");
    }
}
13 голосов
/ 16 февраля 2015

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

Expected string length 2326 but was 2342. Strings differ at index 1729.

Выяснить, где различия, - это боль, если не сказать больше.

С сравнениями графов объектов FluentAssertions (т.е. a.ShouldBeEquivalentTo(b)) вы получаете это обратно:

Expected property Name to be "Foo" but found "Bar"

Это намного приятнее. Получите FluentAssertions сейчас, вы будете рады позже (и если вы проголосуете за это, пожалуйста, также подтвердите ответ dkl , где впервые были предложены FluentAssertions).

9 голосов
/ 01 мая 2009

Я согласен с ChrisYoxall - реализация Equals в вашем основном коде исключительно для целей тестирования не годится.

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

Короче, держите код только для тестирования вне своего класса.

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

Sly

4 голосов
/ 15 ноября 2015

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

Assert.That(ActualObject, Has.Property("Prop1").EqualTo(ExpectedObject.Prop1)
                          & Has.Property("Prop2").EqualTo(ExpectedObject.Prop2)
                          & Has.Property("Prop3").EqualTo(ExpectedObject.Prop3)
                          // ...

Не так универсально, как реализация Equals, но это дает гораздо лучшее сообщение об ошибке, чем

Assert.AreEqual(ExpectedObject, ActualObject);
3 голосов
/ 29 мая 2013

Решение Max Wikstrom JSON (выше) имеет для меня самый смысл, оно короткое, чистое и самое главное, оно работает. Лично я предпочел бы реализовать преобразование JSON как отдельный метод и поместить утверждение обратно в модульный тест, как это ...

МЕТОД ПОМОЩИ:

public string GetObjectAsJson(object obj)
    {
        System.Web.Script.Serialization.JavaScriptSerializer oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
        return oSerializer.Serialize(obj);
    }

ПРОВЕРКА ЕДИНИЦЫ:

public void GetDimensionsFromImageTest()
        {
            Image Image = new Bitmap(10, 10);
            ImageHelpers_Accessor.ImageDimensions expected = new ImageHelpers_Accessor.ImageDimensions(10,10);

            ImageHelpers_Accessor.ImageDimensions actual;
            actual = ImageHelpers_Accessor.GetDimensionsFromImage(Image);

            /*USING IT HERE >>>*/
            Assert.AreEqual(GetObjectAsJson(expected), GetObjectAsJson(actual));
        }

К вашему сведению - вам может понадобиться добавить ссылку на System.Web.Extensions в вашем решении.

1 голос
/ 02 февраля 2015

https://github.com/kbilsted/StatePrinter был написан специально для вывода графов объектов в строковое представление с целью написания простых модульных тестов.

  • Он поставляется с методами Assert, которые выводят правильно экранированную строку, легко копируют и вставляют в тест для ее исправления.
  • Позволяет автоматически переписать unittest
  • Интегрируется со всеми платформами модульного тестирования
  • В отличие от сериализации JSON поддерживаются циклические ссылки
  • Вы можете легко фильтровать, так что сбрасываются только части типов

Учитывая

class A
{
  public DateTime X;
  public DateTime Y { get; set; }
  public string Name;
}

Вы можете в безопасном виде и с помощью автозаполнения Visual Studio включать или исключать поля.

  var printer = new Stateprinter();
  printer.Configuration.Projectionharvester().Exclude<A>(x => x.X, x => x.Y);

  var sut = new A { X = DateTime.Now, Name = "Charly" };

  var expected = @"new A(){ Name = ""Charly""}";
  printer.Assert.PrintIsSame(expected, sut);
...