Сравните равенство между двумя объектами в 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 ]

1 голос
/ 15 марта 2013

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

Assert.That( LeftObject, PortfolioState.Matches( RightObject ) ); 

В качестве крайнего примера рассмотрим класс, который имеет члены «только для чтения», но не IEquatable, и вы не можете изменить тестируемый класс, даже если хотите:

public class Portfolio // Somewhat daft class for pedagogic purposes...
{
    // Cannot be instanitated externally, instead has two 'factory' methods
    private Portfolio(){ }

    // Immutable properties
    public string Property1 { get; private set; }
    public string Property2 { get; private set; }  // Cannot be accessed externally
    public string Property3 { get; private set; }  // Cannot be accessed externally

    // 'Factory' method 1
    public static Portfolio GetPortfolio(string p1, string p2, string p3)
    {
        return new Portfolio() 
        { 
            Property1 = p1, 
            Property2 = p2, 
            Property3 = p3 
        };
    }

    // 'Factory' method 2
    public static Portfolio GetDefault()
    {
        return new Portfolio() 
        { 
            Property1 = "{{NONE}}", 
            Property2 = "{{NONE}}", 
            Property3 = "{{NONE}}" 
        };
    }
}

Контракт для класса Constraint требует, чтобы он переопределял Matches и WriteDescriptionTo (в случае несоответствия - описательную часть для ожидаемого значения), но также переопределял WriteActualValueTo (описательную часть для фактического значения): смысл:

public class PortfolioEqualityConstraint : Constraint
{
    Portfolio expected;
    string expectedMessage = "";
    string actualMessage = "";

    public PortfolioEqualityConstraint(Portfolio expected)
    {
        this.expected = expected;
    }

    public override bool Matches(object actual)
    {
        if ( actual == null && expected == null ) return true;
        if ( !(actual is Portfolio) )
        { 
            expectedMessage = "<Portfolio>";
            actualMessage = "null";
            return false;
        }
        return Matches((Portfolio)actual);
    }

    private bool Matches(Portfolio actual)
    {
        if ( expected == null && actual != null )
        {
            expectedMessage = "null";
            expectedMessage = "non-null";
            return false;
        }
        if ( ReferenceEquals(expected, actual) ) return true;

        if ( !( expected.Property1.Equals(actual.Property1)
                 && expected.Property2.Equals(actual.Property2) 
                 && expected.Property3.Equals(actual.Property3) ) )
        {
            expectedMessage = expected.ToStringForTest();
            actualMessage = actual.ToStringForTest();
            return false;
        }
        return true;
    }

    public override void WriteDescriptionTo(MessageWriter writer)
    {
        writer.WriteExpectedValue(expectedMessage);
    }
    public override void WriteActualValueTo(MessageWriter writer)
    {
        writer.WriteExpectedValue(actualMessage);
    }
}

Плюс класс помощника:

public static class PortfolioState
{
    public static PortfolioEqualityConstraint Matches(Portfolio expected)
    {
        return new PortfolioEqualityConstraint(expected);
    }

    public static string ToStringForTest(this Portfolio source)
    {
        return String.Format("Property1 = {0}, Property2 = {1}, Property3 = {2}.", 
            source.Property1, source.Property2, source.Property3 );
    }
}

Пример использования:

[TestFixture]
class PortfolioTests
{
    [Test]
    public void TestPortfolioEquality()
    {
        Portfolio LeftObject 
            = Portfolio.GetDefault();
        Portfolio RightObject 
            = Portfolio.GetPortfolio("{{GNOME}}", "{{NONE}}", "{{NONE}}");

        Assert.That( LeftObject, PortfolioState.Matches( RightObject ) );
    }
}
1 голос
/ 24 мая 2015

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

У меня есть несколько примеров на github: https://github.com/hatelove/CompareObjectEquals

Вот несколько примеров, которые содержат сценарии сравнения объектов:

    [TestMethod]
    public void Test_Person_Equals_with_ExpectedObjects()
    {
        //use extension method ToExpectedObject() from using ExpectedObjects namespace to project Person to ExpectedObject
        var expected = new Person
        {
            Id = 1,
            Name = "A",
            Age = 10,
        }.ToExpectedObject();

        var actual = new Person
        {
            Id = 1,
            Name = "A",
            Age = 10,
        };

        //use ShouldEqual to compare expected and actual instance, if they are not equal, it will throw a System.Exception and its message includes what properties were not match our expectation.
        expected.ShouldEqual(actual);
    }

    [TestMethod]
    public void Test_PersonCollection_Equals_with_ExpectedObjects()
    {
        //collection just invoke extension method: ToExpectedObject() to project Collection<Person> to ExpectedObject too
        var expected = new List<Person>
        {
            new Person { Id=1, Name="A",Age=10},
            new Person { Id=2, Name="B",Age=20},
            new Person { Id=3, Name="C",Age=30},
        }.ToExpectedObject();

        var actual = new List<Person>
        {
            new Person { Id=1, Name="A",Age=10},
            new Person { Id=2, Name="B",Age=20},
            new Person { Id=3, Name="C",Age=30},
        };

        expected.ShouldEqual(actual);
    }

    [TestMethod]
    public void Test_ComposedPerson_Equals_with_ExpectedObjects()
    {
        //ExpectedObject will compare each value of property recursively, so composed type also simply compare equals.
        var expected = new Person
        {
            Id = 1,
            Name = "A",
            Age = 10,
            Order = new Order { Id = 91, Price = 910 },
        }.ToExpectedObject();

        var actual = new Person
        {
            Id = 1,
            Name = "A",
            Age = 10,
            Order = new Order { Id = 91, Price = 910 },
        };

        expected.ShouldEqual(actual);
    }

    [TestMethod]
    public void Test_PartialCompare_Person_Equals_with_ExpectedObjects()
    {
        //when partial comparing, you need to use anonymous type too. Because only anonymous type can dynamic define only a few properties should be assign.
        var expected = new
        {
            Id = 1,
            Age = 10,
            Order = new { Id = 91 }, // composed type should be used anonymous type too, only compare properties. If you trace ExpectedObjects's source code, you will find it invoke config.IgnoreType() first.
        }.ToExpectedObject();

        var actual = new Person
        {
            Id = 1,
            Name = "B",
            Age = 10,
            Order = new Order { Id = 91, Price = 910 },
        };

        // partial comparing use ShouldMatch(), rather than ShouldEqual()
        expected.ShouldMatch(actual);
    }

Справка:

  1. Ожидаемые объекты github
  2. Введение ожидаемых объектов
1 голос
/ 11 июня 2013

Я бы основывался на ответе @Juanma. Тем не менее, я считаю, что это не должно быть реализовано с утверждениями модульного теста. Это утилита, которая в некоторых случаях вполне может быть использована не тестовым кодом.

Я написал статью по этому вопросу http://timoch.com/blog/2013/06/unit-test-equality-is-not-domain-equality/

Мое предложение выглядит следующим образом:

/// <summary>
/// Returns the names of the properties that are not equal on a and b.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns>An array of names of properties with distinct 
///          values or null if a and b are null or not of the same type
/// </returns>
public static string[] GetDistinctProperties(object a, object b) {
    if (object.ReferenceEquals(a, b))
        return null;
    if (a == null)
        return null;
    if (b == null)
        return null;

    var aType = a.GetType();
    var bType = b.GetType();

    if (aType != bType)
        return null;

    var props = aType.GetProperties();

    if (props.Any(prop => prop.GetIndexParameters().Length != 0))
        throw new ArgumentException("Types with index properties not supported");

    return props
        .Where(prop => !Equals(prop.GetValue(a, null), prop.GetValue(b, null)))
        .Select(prop => prop.Name).ToArray();
} 

Использование этого с NUnit

Expect(ReflectionUtils.GetDistinctProperties(tile, got), Empty);

выдает следующее сообщение о несоответствии.

Expected: <empty>
But was:  < "MagmaLevel" >
at NUnit.Framework.Assert.That(Object actual, IResolveConstraint expression, String message, Object[] args)
at Undermine.Engine.Tests.TileMaps.BasicTileMapTests.BasicOperations() in BasicTileMapTests.cs: line 29
1 голос
/ 13 мая 2016

Это довольно старая ветка, но мне было интересно, есть ли причина, по которой не предлагается ответ NUnit.Framework.Is.EqualTo и NUnit.Framework.Is.NotEqualTo?

Например:

Assert.That(LeftObject, Is.EqualTo(RightObject)); 

и

Assert.That(LeftObject, Is.Not.EqualTo(RightObject)); 
1 голос
/ 21 января 2016

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

http://www.codeproject.com/Articles/22709/Testing-Equality-of-Two-Objects?msg=5189539#xx5189539xx

0 голосов
/ 20 сентября 2018

Я знаю, что это действительно старый вопрос, но NUnit все еще не имеет встроенной поддержки для этого. Тем не менее, если вам нравится тестирование в стиле BDD (аля Jasmine), вы будете приятно удивлены NExpect (https://github.com/fluffynuts/NExpect, получить его от NuGet), в котором прямо сейчас готовится тестирование глубокого равенства.

(отказ от ответственности: я автор NExpect)

0 голосов
/ 23 октября 2017

Я закончил с написанием простой фабрики выражений:

public static class AllFieldsEqualityComprision<T>
{
    public static Comparison<T> Instance { get; } = GetInstance();

    private static Comparison<T> GetInstance()
    {
        var type = typeof(T);
        ParameterExpression[] parameters =
        {
            Expression.Parameter(type, "x"),
            Expression.Parameter(type, "y")
        };
        var result = type.GetProperties().Aggregate<PropertyInfo, Expression>(
            Expression.Constant(true),
            (acc, prop) =>
                Expression.And(acc,
                    Expression.Equal(
                        Expression.Property(parameters[0], prop.Name),
                        Expression.Property(parameters[1], prop.Name))));
        var areEqualExpression = Expression.Condition(result, Expression.Constant(0), Expression.Constant(1));
        return Expression.Lambda<Comparison<T>>(areEqualExpression, parameters).Compile();
    }
}

и просто используйте его:

Assert.That(
    expectedCollection, 
    Is.EqualTo(actualCollection)
      .Using(AllFieldsEqualityComprision<BusinessCategoryResponse>.Instance));

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

Вот пример с примером: https://gist.github.com/Pzixel/b63fea074864892f9aba8ffde312094f

0 голосов
/ 09 августа 2017

Stringify и сравнение двух строк

Assert.AreEqual (JSON.stringify (LeftObject), JSON.stringify (RightObject))

0 голосов
/ 10 ноября 2010

Десериализация обоих классов и сравнение строк.

EDIT: Работает отлично, это вывод, который я получаю от NUnit;

Test 'Telecom.SDP.SBO.App.Customer.Translator.UnitTests.TranslateEaiCustomerToDomain_Tests.TranslateNew_GivenEaiCustomer_ShouldTranslateToDomainCustomer_Test("ApprovedRatingInDb")' failed:
  Expected string length 2841 but was 5034. Strings differ at index 443.
  Expected: "...taClasses" />\r\n  <ContactMedia />\r\n  <Party i:nil="true" /..."
  But was:  "...taClasses" />\r\n  <ContactMedia>\r\n    <ContactMedium z:Id="..."
  ----------------------------------------------^
 TranslateEaiCustomerToDomain_Tests.cs(201,0): at Telecom.SDP.SBO.App.Customer.Translator.UnitTests.TranslateEaiCustomerToDomain_Tests.Assert_CustomersAreEqual(Customer expectedCustomer, Customer actualCustomer)
 TranslateEaiCustomerToDomain_Tests.cs(114,0): at Telecom.SDP.SBO.App.Customer.Translator.UnitTests.TranslateEaiCustomerToDomain_Tests.TranslateNew_GivenEaiCustomer_ShouldTranslateToDomainCustomer_Test(String custRatingScenario)

РЕДАКТИРОВАТЬ ВТОРОЕ: Два объекта могут быть идентичны, но порядок, в котором сериализуются свойства, не одинаков. Поэтому XML отличается. DOH!

РЕДАКТИРОВАТЬ ТРИ: Это работает. Я использую это в своих тестах. Но вы должны добавлять элементы в свойства коллекции в том порядке, в котором их тестирует код.

...