Porównaj równość między dwoma obiektami w NUnit

126

Próbuję zapewnić, że jeden obiekt jest „równy” innemu obiektowi.

Obiekty są po prostu instancjami klasy z wieloma właściwościami publicznymi. Czy istnieje łatwy sposób, aby NUnit potwierdzał równość na podstawie właściwości?

To jest moje obecne rozwiązanie, ale myślę, że może być coś lepszego:

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

To, do czego zmierzam, będzie w tym samym duchu, co CollectionEquivalentConstraint, w którym NUnit sprawdza, czy zawartość dwóch kolekcji jest identyczna.

Michael Haren
źródło

Odpowiedzi:

51

Zastąp .Equals dla swojego obiektu i w teście jednostkowym możesz po prostu zrobić to:

Assert.AreEqual(LeftObject, RightObject);

Oczywiście może to oznaczać po prostu przeniesienie wszystkich indywidualnych porównań do metody .Equals, ale pozwoliłoby to na ponowne użycie tej implementacji w wielu testach i prawdopodobnie ma sens, gdyby obiekty i tak mogły porównywać się z rodzeństwem.

Lasse V. Karlsen
źródło
2
Dzięki, lassevk. To zadziałało dla mnie! Zaimplementowałem .Equals zgodnie z wytycznymi tutaj: msdn.microsoft.com/en-us/library/336aedhh(VS.80).aspx
Michael Haren
12
I oczywiście GetHashCode () ;-p
Marc Gravell
Numer 1 na liście na tej stronie to zastąpienie GetHashCode i powiedział, że postępował zgodnie z tymi wytycznymi :) Ale tak, powszechny błąd ignorowania tego. Zazwyczaj nie jest to błąd, który zauważysz przez większość czasu, ale kiedy to zrobisz, to tak, jakbyś powiedział: „Och, hej, dlaczego ten wąż wciąga mi spodnie i dlaczego gryzie mnie w tyłek”.
Lasse V. Karlsen
1
Jedno ważne zastrzeżenie: jeśli Twój obiekt również implementuje IEnumerable, zostanie porównany jako kolekcja, niezależnie od zastępowania implementacji, Equalsponieważ NUnit daje IEnumerablewyższy priorytet. Zobacz NUnitEqualityComparer.AreEqualmetody, aby uzyskać szczegółowe informacje. Można przesłonić funkcję porównującą przy użyciu jednej z Using()metod ograniczenia równości . Nawet wtedy nie wystarczy zaimplementować nieogólnego, IEqualityComparerponieważ adapter używa NUnit.
Kaleb Pederson
13
Więcej zastrzeżeń: implementacja GetHashCode()na zmiennych typach będzie źle działać, jeśli kiedykolwiek użyjesz tego obiektu jako klucza. IMHO, nadrzędne Equals(), GetHashCode()a co niezmiennych obiektów tylko do testowania nie ma sensu.
bavaza
118

Jeśli z jakiegokolwiek powodu nie możesz zastąpić Equals, możesz utworzyć metodę pomocniczą, która iteruje przez właściwości publiczne przez odbicie i potwierdzić każdą właściwość. Coś takiego:

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]);
    }
}
Juanma
źródło
@wesley: to nieprawda. Metoda Type.GetProperties: zwraca wszystkie właściwości publiczne bieżącego typu. Zobacz msdn.microsoft.com/en-us/library/aky14axb.aspx
Sergii Volchkov,
4
dzięki. musiałem jednak zmienić kolejność parametrów rzeczywistych i oczekiwanych, ponieważ zbieżność polega na tym, że oczekiwana wartość jest paramem przed rzeczywistą.
Valamas,
jest to lepsze podejście. IMHO, nadpisania Equal i HashCode nie powinny opierać się na porównywaniu każdego pola, a ponadto jest to bardzo żmudne w przypadku każdego obiektu. Dobra robota!
Scott White,
3
Działa to świetnie, jeśli typ ma tylko podstawowe typy jako właściwości. Jeśli jednak typ ma właściwości z typami niestandardowymi (które nie implementują równości), zakończy się niepowodzeniem.
Bobby Cannon
Dodano niektóre rekursji do właściwości obiektu, ale musiałem przeskoczyć indeksowane właściwości:
cerhart
113

Nie zastępuj Równa się tylko w celach testowych. Jest to żmudne i wpływa na logikę domeny. Zamiast,

Użyj formatu JSON, aby porównać dane obiektu

Brak dodatkowej logiki dla twoich obiektów. Brak dodatkowych zadań do testowania.

Po prostu użyj tej prostej metody:

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

Wydaje się, że działa świetnie. Informacje o wynikach programu uruchamiającego testy będą zawierać porównanie ciągów JSON (wykres obiektów), dzięki czemu od razu zobaczysz, co jest nie tak.

Zwróć też uwagę! Jeśli masz większe złożone obiekty i chcesz po prostu porównać ich części, możesz ( użyj LINQ do danych sekwencyjnych ) utworzyć anonimowe obiekty do użycia z powyższą metodą.

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 });
}
Maks
źródło
1
Jest to doskonały sposób na testowanie, zwłaszcza jeśli mimo wszystko masz do czynienia z JSON (np. Używając wpisanego klienta w celu uzyskania dostępu do usługi sieciowej). Ta odpowiedź powinna być znacznie wyższa.
Roopesh Shenoy
1
Użyj Linq! @DmitryBLR (patrz ostatni akapit odpowiedzi) :)
Maks.
3
To świetny pomysł. Użyłbym nowszego Json.NET: var awareJson = Newtonsoft.Json.JsonConvert.SerializeObject (oczekiwany);
BrokeMyLegBiking
2
To nie zadziała w przypadku odwołań cyklicznych. Zamiast tego użyj github.com/kbilsted/StatePrinter, aby uzyskać lepsze wrażenia w porównaniu z podejściem JSON
Carlo V. Dango
2
To prawda @KokaChernov i czasami chcesz oblać test, jeśli nie, kolejność jest taka sama, ale jeśli nie chcesz zawieść, jeśli kolejność nie jest taka sama, możesz dokonać jawnego sortowania (za pomocą linq) na listach przed przekazaniem ich do metody AreEqualByJson. Prosty wariant „ponownego ułożenia” obiektów przed testowaniem znajduje się w ostatnim przykładzie kodu w odpowiedzi. Myślę, że to jest bardzo „uniwersalne”! :)
Max
91

Wypróbuj bibliotekę FluentAssertions:

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

http://www.fluentassertions.com/

Można go również zainstalować za pomocą NuGet.

dkl
źródło
18
PowinienHave został uznany za przestarzały, więc powinien być dto.ShouldBeEquivalentTo (customer); zamiast tego
WhiteKnight
2
To najlepsza odpowiedź z tego powodu .
Todd Menier
ShouldBeEquivalent jest buggy :(
Konstantin Chernov
3
po prostu miałem ten sam problem i użyłem następującego, który wydaje się działać dobrze:actual.ShouldBeEquivalentTo(expected, x => x.ExcludingMissingMembers())
stt106
1
To świetna biblioteka! Nie wymaga przesłonięcia Equals, a także (jeśli equals i tak jest nadpisane, np. Dla obiektów wartości) nie polega na poprawnej implementacji. Różnica jest również ładnie wydrukowana, tak jak robi to Hamcrest dla Javy.
kap
35

Wolę nie zastępować Equals tylko po to, aby włączyć testowanie. Nie zapominaj, że jeśli nadpisujesz Equals, to naprawdę powinieneś również przesłonić GetHashCode, w przeciwnym razie możesz otrzymać nieoczekiwane wyniki, jeśli na przykład używasz swoich obiektów w słowniku.

Podobało mi się powyższe podejście do refleksji, ponieważ umożliwia dodawanie właściwości w przyszłości.

Jednak w celu szybkiego i prostego rozwiązania często najłatwiej jest utworzyć metodę pomocniczą, która sprawdza, czy obiekty są równe, lub zaimplementować IEqualityComparer w klasie, która jest prywatna dla testów. Korzystając z rozwiązania IEqualityComparer nie musisz przejmować się implementacją GetHashCode. Na przykład:

// 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.");
    }
}
Chris Yoxall
źródło
Equals nie obsługuje wartości null. Dodałbym następujące wyrażenie przed instrukcją return w metodzie equals. if (x == null && y == null) {return true; } if (x == null || y == null) {return false; } Edytowałem pytanie, aby dodać obsługę zerową.
Bobby Cannon
Nie działa dla mnie z rzutem new NotImplementedException (); w GetHashCode. Po co mi ta funkcja w IEqualityComparer w obu przypadkach?
love2code,
15

Wypróbowałem kilka podejść wymienionych tutaj. Większość obejmuje serializację obiektów i porównywanie ciągów. Chociaż jest to bardzo łatwe i ogólnie bardzo skuteczne, stwierdziłem, że jest trochę za krótki, gdy masz awarię i pojawia się coś takiego:

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

Ustalenie, gdzie są różnice, jest co najmniej uciążliwe.

Dzięki porównaniom grafów obiektów FluentAssertions (tj. a.ShouldBeEquivalentTo(b)) Otrzymujesz to z powrotem:

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

To dużo przyjemniejsze. Pobierz FluentAssertions już teraz, będziesz zadowolony później (a jeśli zagłosujesz za tym, prosimy również o głosowanie za odpowiedzią dkl, gdzie FluentAssertions zostało po raz pierwszy zasugerowane).

Todd Menier
źródło
9

Zgadzam się z ChrisYoxallem - implementowanie Equals w głównym kodzie wyłącznie do celów testowych nie jest dobre.

Jeśli implementujesz Equals, ponieważ wymaga tego pewna logika aplikacji, to jest w porządku, ale zachowaj czysty kod przeznaczony tylko do testów z dala od bałaganu (również semantyka sprawdzania tego samego do testowania może być inna niż to, czego wymaga Twoja aplikacja).

Krótko mówiąc, trzymaj kod przeznaczony tylko do testowania poza swoją klasą.

Proste, płytkie porównanie właściwości za pomocą odbicia powinno wystarczyć dla większości klas, chociaż może być konieczne powtórzenie, jeśli obiekty mają złożone właściwości. Jeśli stosujesz się do odnośników, uważaj na odnośniki cykliczne lub podobne.

Chytry

Sly Gryphon
źródło
Niezły chwyt okrągłych odniesień. Łatwe do pokonania, jeśli masz słownik obiektów już w drzewie porównania.
Lucas B
6

Ograniczenia właściwości , dodane w NUnit 2.4.2, pozwalają na rozwiązanie, które jest bardziej czytelne niż oryginalne rozwiązanie OP i generuje znacznie lepsze komunikaty o błędach. Nie jest to w żaden sposób ogólne, ale jeśli nie musisz tego robić dla zbyt wielu zajęć, jest to bardzo odpowiednie rozwiązanie.

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

Nie jest to tak ogólny cel, jak wdrażanie, Equalsale daje znacznie lepszy komunikat o błędzie niż

Assert.AreEqual(ExpectedObject, ActualObject);
Paul Hicks
źródło
4

Rozwiązanie JSON Maxa Wikstroma (powyżej) ma dla mnie największy sens, jest krótkie, przejrzyste i co najważniejsze działa. Osobiście jednak wolałbym zaimplementować konwersję JSON jako oddzielną metodę i umieścić potwierdzenie z powrotem w teście jednostkowym w ten sposób ...

METODA POMOCNICZA:

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

TEST JEDNOSTKOWY:

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

Do Twojej wiadomości - może być konieczne dodanie odwołania do System.Web.Extensions w swoim rozwiązaniu.

samaspin
źródło
4

To dość stary wątek, ale zastanawiałem się, czy istnieje powód, dla którego nie zaproponowano odpowiedzi NUnit.Framework.Is.EqualToiNUnit.Framework.Is.NotEqualTo ?

Jak na przykład:

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

i

Assert.That(LeftObject, Is.Not.EqualTo(RightObject)); 
user2315856
źródło
4
Ponieważ nie drukuje szczegółów, co jest inne
Shrage Smilowitz
1

Inną opcją jest napisanie niestandardowego ograniczenia poprzez zaimplementowanie Constraintklasy abstrakcyjnej NUnit . Dzięki klasie pomocniczej, która dostarcza trochę cukru składniowego, wynikowy kod testu jest przyjemnie zwięzły i czytelny, np.

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

Jako skrajny przykład rozważmy klasę, która ma członków „tylko do odczytu”, a nie jest IEquatable, i nie możesz zmienić testowanej klasy, nawet jeśli chcesz:

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}}" 
        };
    }
}

Kontrakt dla Constraintklasy wymaga nadpisania Matchesi WriteDescriptionTo(w przypadku niedopasowania narracja dla oczekiwanej wartości), ale także nadpisania WriteActualValueTo(narracja dla rzeczywistej wartości) ma sens:

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

Plus klasa pomocnicza:

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

Przykładowe użycie:

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

        Assert.That( LeftObject, PortfolioState.Matches( RightObject ) );
    }
}
onedaywhen
źródło
1

Oparłbym się na odpowiedzi @Juanma. Uważam jednak, że nie powinno się tego implementować z asercjami testów jednostkowych. Jest to narzędzie, które może być bardzo dobrze wykorzystane w pewnych okolicznościach przez kod niebędący testem.

Napisałem artykuł w tej sprawie http://timoch.com/blog/2013/06/unit-test-equality-is-not-domain-equality/

Moja propozycja jest następująca:

/// <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();
} 

Używanie tego z NUnit

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

zwraca następujący komunikat w przypadku niezgodności.

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
TiMoch
źródło
1

https://github.com/kbilsted/StatePrinter został napisany specjalnie w celu zrzucania grafów obiektów do reprezentacji ciągów w celu napisania łatwych testów jednostkowych.

  • Jest dostarczany z metodami Assert, które wyświetlają poprawnie zmieniony kod znaków, łatwe kopiowanie i wklejanie do testu, aby go poprawić.
  • Pozwala na automatyczne przepisanie unittest
  • Integruje się ze wszystkimi frameworkami do testów jednostkowych
  • W przeciwieństwie do serializacji JSON obsługiwane są odwołania cykliczne
  • Możesz łatwo filtrować, więc zrzucane są tylko części typów

Dany

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

Możesz w bezpieczny sposób i przy użyciu funkcji automatycznego uzupełniania programu Visual Studio uwzględnić lub wykluczyć pola.

  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);
Carlo V. Dango
źródło
1

Po prostu zainstaluj ExpectedObjects z Nuget, możesz łatwo porównać dwie wartości właściwości obiektów, każdą wartość obiektu kolekcji, dwie skomponowane wartości obiektu i częściowe porównanie wartości właściwości według typu anonimowego.

Mam kilka przykładów na github: https://github.com/hatelove/CompareObjectEquals

Oto kilka przykładów, które zawierają scenariusze porównywania obiektów:

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

Odniesienie:

  1. ExpectedObjects github
  2. Wprowadzenie ExpectedObjects
W91
źródło
1

Skończyłem na napisaniu prostej fabryki wyrażeń:

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

i po prostu go użyj:

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

Jest to bardzo przydatne, ponieważ muszę porównać kolekcję takich obiektów. I możesz użyć tego porównania gdzie indziej :)

Oto sedno z przykładem: https://gist.github.com/Pzixel/b63fea074864892f9aba8ffde312094f

Alex Zhukovskiy
źródło
0

Zdeserializuj obie klasy i wykonaj porównanie ciągów.

EDYCJA: Działa doskonale, to jest wyjście, które otrzymuję z 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)

EDYTUJ DWA: Te dwa obiekty mogą być identyczne, ale kolejność serializacji właściwości nie jest taka sama. Dlatego XML jest inny. DOH!

EDYCJA TRZECIA: To działa. Używam go w moich testach. Musisz jednak dodać elementy do właściwości kolekcji w kolejności, w jakiej testowany kod je dodaje.

Casey Burns
źródło
1
serializować ? Ciekawy pomysł. Nie jestem jednak pewien, jak by to wytrzymało pod względem wydajności
Michael Haren
nie pozwoli na porównanie liczb podwójnych ani dziesiętnych z określoną dokładnością.
Noctis
0

Wiem, że to naprawdę stare pytanie, ale NUnit nadal nie ma do tego natywnej obsługi. Jeśli jednak lubisz testy w stylu BDD (ala Jasmine), byłbyś mile zaskoczony NExpect ( https://github.com/fluffynuts/NExpect , pobierz to z NuGet), który ma wbudowane głębokie testy równości .

(zastrzeżenie: jestem autorem NExpect)

daf
źródło
-1

Określ i porównaj dwa ciągi

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

jmtt89
źródło
-1
//Below works precisely well, Use it.
private void CompareJson()
{
object expected = new object();
object actual = new object();
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var expectedResponse = serializer.Serialize(expected);
var actualResponse = serializer.Serialize(actual);
Assert.AreEqual(expectedResponse, actualResponse);
}
Satish Babu
źródło
Dziękujemy za ten fragment kodu, który może zapewnić ograniczoną krótkoterminową pomoc. Właściwe wyjaśnienie znacznie poprawiłoby jego długoterminową wartość, pokazując, dlaczego jest to dobre rozwiązanie problemu i uczyniłoby go bardziej użytecznym dla przyszłych czytelników z innymi, podobnymi pytaniami. Proszę edytować swoją odpowiedź dodać kilka wyjaśnień, w tym założeń już wykonanych.
Toby Speight
I co to dodaje do odpowiedzi Maxa ?
Toby Speight