Vergleichen Sie die Gleichheit zwischen zwei Objekten in NUnit

126

Ich versuche zu behaupten, dass ein Objekt einem anderen Objekt "gleich" ist.

Die Objekte sind nur Instanzen einer Klasse mit einer Reihe öffentlicher Eigenschaften. Gibt es eine einfache Möglichkeit, NUnit die Gleichheit basierend auf den Eigenschaften geltend machen zu lassen?

Dies ist meine aktuelle Lösung, aber ich denke, es gibt vielleicht etwas Besseres:

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

Was ich anstrebe, wäre im gleichen Sinne wie die CollectionEquivalentConstraint, bei der NUnit überprüft, ob der Inhalt von zwei Sammlungen identisch ist.

Michael Haren
quelle

Antworten:

51

Überschreiben Sie .Gleichungen für Ihr Objekt und im Unit-Test können Sie dies einfach tun:

Assert.AreEqual(LeftObject, RightObject);

Dies könnte natürlich bedeuten, dass Sie nur alle Einzelvergleiche in die .Equals-Methode verschieben, aber es würde Ihnen ermöglichen, diese Implementierung für mehrere Tests wiederzuverwenden, und es ist wahrscheinlich sinnvoll zu haben, ob Objekte sich trotzdem mit Geschwistern vergleichen können sollten.

Lasse V. Karlsen
quelle
2
Danke, Massevk. Das hat bei mir funktioniert! Ich habe .Equals gemäß den Richtlinien hier implementiert
Michael Haren
12
Und GetHashCode () natürlich ;-p
Marc Gravell
Nummer 1 auf der Liste auf dieser Seite ist das Überschreiben von GetHashCode, und er hat gesagt, dass er diese Richtlinien befolgt hat :) Aber ja, häufiger Fehler, das zu ignorieren. Normalerweise kein Fehler, den Sie die meiste Zeit bemerken werden, aber wenn Sie dies tun, ist es wie in einer dieser Zeiten, wenn Sie sagen: "Oh, hey, warum schlängelt sich das in meine Hose und warum beißt er mir in den Arsch."
Lasse V. Karlsen
1
Eine wichtige Einschränkung: Wenn Ihr Objekt auch implementiert, wird IEnumerablees unabhängig von überschreibenden Implementierungen als Sammlung verglichen, Equalsda NUnit eine IEnumerablehöhere Priorität hat. NUnitEqualityComparer.AreEqualEinzelheiten finden Sie in den Methoden. Sie können den Vergleicher überschreiben, indem Sie eine der Using()Methoden der Gleichheitsbeschränkung verwenden. Selbst dann ist es IEqualityCompareraufgrund des von NUnit verwendeten Adapters nicht ausreichend, das nicht generische zu implementieren .
Kaleb Pederson
13
Weitere Vorsichtsmaßnahmen: Die Implementierung GetHashCode()auf veränderlichen Typen verhält sich schlecht, wenn Sie dieses Objekt jemals als Schlüssel verwenden. IMHO, überschrieben Equals(), GetHashCode()und machen das Objekt unveränderlich nur für die Prüfung keinen Sinn macht.
Bayaza
118

Wenn Sie Equals aus irgendeinem Grund nicht überschreiben können, können Sie eine Hilfsmethode erstellen, die öffentliche Eigenschaften durch Reflektion durchläuft und jede Eigenschaft bestätigt. Etwas wie das:

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
quelle
@wesley: das ist nicht wahr. Type.GetProperties-Methode: Gibt alle öffentlichen Eigenschaften des aktuellen Typs zurück. Siehe msdn.microsoft.com/en-us/library/aky14axb.aspx
Sergii Volchkov
4
Vielen Dank. Ich musste jedoch die Reihenfolge der tatsächlichen und erwarteten Parameter ändern, da die Konvertierung so ist, dass erwartet ein Parameter vor dem tatsächlichen ist.
Valamas
Dies ist ein besserer Ansatz. IMHO, Equal & HashCode-Überschreibungen sollten nicht auf dem Vergleich jedes Felds basieren müssen. Außerdem ist dies für jedes Objekt sehr mühsam. Gut gemacht!
Scott White
3
Dies funktioniert hervorragend, wenn Ihr Typ nur Basistypen als Eigenschaften hat. Wenn Ihr Typ jedoch Eigenschaften mit benutzerdefinierten Typen hat (die Equals nicht implementieren), schlägt dies fehl.
Bobby Cannon
Es wurde eine Rekursion für Objekteigenschaften hinzugefügt, aber ich musste indizierte Eigenschaften überspringen:
Cerhart
113

Überschreiben Sie Equals nicht nur zu Testzwecken. Es ist langwierig und wirkt sich auf die Domänenlogik aus. Stattdessen,

Verwenden Sie JSON, um die Daten des Objekts zu vergleichen

Keine zusätzliche Logik für Ihre Objekte. Keine zusätzlichen Aufgaben zum Testen.

Verwenden Sie einfach diese einfache Methode:

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

Es scheint großartig zu funktionieren. Die Informationen zu den Ergebnissen des Testläufers zeigen den enthaltenen JSON-Zeichenfolgenvergleich (das Objektdiagramm), sodass Sie direkt sehen, was falsch ist.

Beachten Sie auch! Wenn Sie größere komplexe Objekte haben und nur Teile davon vergleichen möchten, können Sie ( verwenden Sie LINQ für Sequenzdaten ) anonyme Objekte erstellen, die mit der obigen Methode verwendet werden können.

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 });
}
Max
quelle
1
Dies ist eine hervorragende Möglichkeit zum Testen, insbesondere wenn Sie sowieso mit JSON zu tun haben (z. B. wenn Sie einen typisierten Client verwenden, um auf einen Webdienst zuzugreifen). Diese Antwort sollte viel höher sein.
Roopesh Shenoy
1
Verwenden Sie Linq! @DmitryBLR (siehe letzter Absatz in Antwort) :)
Max
3
Das ist eine großartige Idee. Ich würde das neuere Json.NET verwenden: var expectedJson = Newtonsoft.Json.JsonConvert.SerializeObject (erwartet);
BrokeMyLegBiking
2
Dies funktioniert nicht mit Zirkelverweisen. Verwenden Sie stattdessen github.com/kbilsted/StatePrinter für eine verbesserte Erfahrung gegenüber dem JSON-Ansatz
Carlo V. Dango
2
Das ist wahr @KokaChernov und manchmal möchten Sie den Test nicht bestehen, wenn die Reihenfolge nicht identisch ist. Wenn Sie jedoch nicht fehlschlagen möchten, wenn die Reihenfolge nicht identisch ist, können Sie die Listen explizit sortieren (mit linq), bevor Sie sie an die AreEqualByJson-Methode übergeben. Eine einfache Variante zum "Neuanordnen" Ihrer Objekte vor dem Testen finden Sie im letzten Codebeispiel in der Antwort. Das ist also sehr "universell", denke ich! :)
Max
91

Probieren Sie die FluentAssertions-Bibliothek aus:

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

http://www.fluentassertions.com/

Es kann auch mit NuGet installiert werden.

dkl
quelle
18
ShouldHave wurde veraltet, sollte also dto.ShouldBeEquivalentTo (Kunde) sein; stattdessen
WhiteKnight
2
Dies ist die beste Antwort aus diesem Grund .
Todd Menier
ShouldBeEquivalent ist fehlerhaft :(
Konstantin Chernov
3
hatte gerade das gleiche Problem und benutzte das Folgende, was gut zu funktionieren scheint:actual.ShouldBeEquivalentTo(expected, x => x.ExcludingMissingMembers())
stt106
1
Das ist eine großartige Bibliothek! Erfordert nicht das Überschreiben von Equals und ist auch (wenn equals sowieso überschrieben wird, z. B. für Wertobjekte) nicht auf die korrekte Implementierung angewiesen. Auch der Unterschied wird gut gedruckt, wie es Hamcrest für Java tut.
Kap
35

Ich ziehe es vor, Equals nicht zu überschreiben, nur um das Testen zu ermöglichen. Vergessen Sie nicht, dass Sie GetHashCode auch überschreiben sollten, wenn Sie Equals überschreiben. Andernfalls erhalten Sie möglicherweise unerwartete Ergebnisse, wenn Sie Ihre Objekte beispielsweise in einem Wörterbuch verwenden.

Ich mag den obigen Reflexionsansatz, da er das Hinzufügen von Eigenschaften in der Zukunft ermöglicht.

Für eine schnelle und einfache Lösung ist es jedoch oft am einfachsten, entweder eine Hilfsmethode zu erstellen, die prüft, ob die Objekte gleich sind, oder IEqualityComparer für eine Klasse zu implementieren, die Sie für Ihre Tests privat halten. Wenn Sie die IEqualityComparer-Lösung verwenden, müssen Sie sich nicht um die Implementierung von GetHashCode kümmern. Beispielsweise:

// 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
quelle
Das Gleiche behandelt keine Nullwerte. Ich würde Folgendes vor Ihrer return-Anweisung in der equals-Methode hinzufügen. if (x == null && y == null) {return true; } if (x == null || y == null) {return false; } Ich habe die Frage bearbeitet, um keine Unterstützung hinzuzufügen.
Bobby Cannon
Ich arbeite nicht mit der neuen NotImplementedException (). im GetHashCode. Warum brauche ich diese Funktion im IEqualityComparer so oder so?
love2code
15

Ich habe verschiedene hier erwähnte Ansätze ausprobiert. In den meisten Fällen müssen Sie Ihre Objekte serialisieren und einen Zeichenfolgenvergleich durchführen. Obwohl es super einfach und im Allgemeinen sehr effektiv ist, habe ich festgestellt, dass es etwas zu kurz kommt, wenn Sie einen Fehler haben und so etwas gemeldet wird:

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

Herauszufinden, wo die Unterschiede liegen, ist gelinde gesagt ein Schmerz.

Mit FluentAssertions' Objektgraph - Vergleichen (dh a.ShouldBeEquivalentTo(b)), erhalten Sie diese zurück:

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

Das ist viel schöner. Holen Sie sich jetzt FluentAssertions , Sie werden sich später freuen (und wenn Sie dies positiv bewerten, stimmen Sie bitte auch der Antwort von dkl zu, wo FluentAssertions zuerst vorgeschlagen wurde).

Todd Menier
quelle
9

Ich stimme ChrisYoxall zu - die Implementierung von Equals in Ihrem Hauptcode nur zu Testzwecken ist nicht gut.

Wenn Sie Equals implementieren, weil es für eine Anwendungslogik erforderlich ist, ist das in Ordnung. Halten Sie jedoch reinen Nur-Test-Code von Unordnung fern (auch die Semantik der Überprüfung des gleichen Tests kann sich von der für Ihre App erforderlichen unterscheiden).

Kurz gesagt, halten Sie Testcode nur aus Ihrer Klasse heraus.

Ein einfacher flacher Vergleich von Eigenschaften mithilfe von Reflexion sollte für die meisten Klassen ausreichen, obwohl Sie möglicherweise eine Rekursion durchführen müssen, wenn Ihre Objekte komplexe Eigenschaften haben. Achten Sie bei folgenden Referenzen auf Zirkelverweise oder ähnliches.

Schlau

Schlauer Greif
quelle
Netter Fang bei Zirkelverweisen. Leicht zu überwinden, wenn Sie ein Wörterbuch mit Objekten bereits im Vergleichsbaum behalten.
Lucas B
6

In NUnit 2.4.2 hinzugefügte Eigenschaftsbeschränkungen ermöglichen eine Lösung, die besser lesbar ist als die ursprüngliche des OP und viel bessere Fehlermeldungen erzeugt. Es ist in keiner Weise generisch, aber wenn Sie es nicht für zu viele Klassen tun müssen, ist es eine sehr adäquate Lösung.

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

Nicht so universell wie die Implementierung, Equalsaber es gibt eine viel bessere Fehlermeldung als

Assert.AreEqual(ExpectedObject, ActualObject);
Paul Hicks
quelle
4

Die JSON-Lösung von Max Wikstrom (oben) ist für mich am sinnvollsten. Sie ist kurz, sauber und funktioniert vor allem. Persönlich würde ich es jedoch vorziehen, die JSON-Konvertierung als separate Methode zu implementieren und die Bestätigung wie folgt wieder in den Komponententest einzufügen ...

HILFE-METHODE:

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

GERÄTETEST :

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

Zu Ihrer Information - Möglicherweise müssen Sie in Ihrer Lösung einen Verweis auf System.Web.Extensions hinzufügen.

Samaspin
quelle
4

Dies ist ein ziemlich alter Thread, aber ich habe mich gefragt, ob es einen Grund gibt, warum keine Antwort vorgeschlagen wurde NUnit.Framework.Is.EqualTound NUnit.Framework.Is.NotEqualTo?

Sowie:

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

und

Assert.That(LeftObject, Is.Not.EqualTo(RightObject)); 
user2315856
quelle
4
Weil es die Details nicht ausdruckt, was anders ist
Shrage Smilowitz
1

Eine weitere Option besteht darin, eine benutzerdefinierte Einschränkung durch Implementieren der abstrakten NUnit- ConstraintKlasse zu schreiben . Mit einer Hilfsklasse, die ein wenig syntaktischen Zucker liefert, ist der resultierende Testcode angenehm knapp und lesbar, z

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

Betrachten Sie als extremes Beispiel eine Klasse mit schreibgeschützten Mitgliedern, IEquatableund Sie können die zu testende Klasse nicht ändern, selbst wenn Sie Folgendes möchten:

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

Der Vertrag für die ConstraintKlasse erfordert eine Überschreibung Matchesund WriteDescriptionTo(im Falle einer Nichtübereinstimmung eine Erzählung für den erwarteten Wert), aber auch eine Überschreibung WriteActualValueTo(Erzählung für den tatsächlichen Wert) ist sinnvoll:

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 die Helferklasse:

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

Anwendungsbeispiel:

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

        Assert.That( LeftObject, PortfolioState.Matches( RightObject ) );
    }
}
eines Tages, wenn
quelle
1

Ich würde auf der Antwort von @Juanma aufbauen. Ich bin jedoch der Meinung, dass dies nicht mit Unit-Test-Behauptungen implementiert werden sollte. Dies ist ein Dienstprogramm, das unter bestimmten Umständen von Nicht-Testcode verwendet werden kann.

Ich habe einen Artikel zu diesem Thema geschrieben: http://timoch.com/blog/2013/06/unit-test-equality-is-not-domain-equality/

Mein Vorschlag lautet wie folgt:

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

Verwenden Sie dies mit NUnit

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

gibt die folgende Meldung bei Nichtübereinstimmung aus.

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
quelle
1

https://github.com/kbilsted/StatePrinter wurde speziell geschrieben, um Objektdiagramme in die Zeichenfolgendarstellung zu verschieben, um einfache Komponententests zu schreiben.

  • Es kommt mit Assert-Methoden, die eine ordnungsgemäß maskierte Zeichenfolge ausgeben, einfach kopieren und in den Test einfügen, um sie zu korrigieren.
  • Damit kann unittest automatisch neu geschrieben werden
  • Es lässt sich in alle Unit-Testing-Frameworks integrieren
  • Im Gegensatz zur JSON-Serialisierung werden Zirkelverweise unterstützt
  • Sie können leicht filtern, sodass nur Teile von Typen ausgegeben werden

Gegeben

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

Sie können auf typsichere Weise und mithilfe der automatischen Vervollständigung von Visual Studio Felder ein- oder ausschließen.

  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
quelle
1

Installieren Sie einfach ExpectedObjects von Nuget. Sie können auf einfache Weise den Eigenschaftswert von zwei Objekten, jeden Objektwert der Sammlung, den Wert von zwei zusammengesetzten Objekten und den Teilvergleich des Eigenschaftswerts nach anonymem Typ vergleichen.

Ich habe einige Beispiele für Github: https://github.com/hatelove/CompareObjectEquals

Hier sind einige Beispiele, die Szenarien zum Vergleichen von Objekten enthalten:

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

Referenz:

  1. ExpectedObjects Github
  2. Einführung von ExpectedObjects
In91
quelle
1

Ich habe mit dem Schreiben einer einfachen Ausdrucksfabrik geendet:

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

und benutze es einfach:

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

Es ist sehr nützlich, da ich die Sammlung solcher Objekte vergleichen muss. Und Sie können diesen Vergleich woanders verwenden :)

Hier ist das Wesentliche mit einem Beispiel: https://gist.github.com/Pzixel/b63fea074864892f9aba8ffde312094f

Alex Zhukovskiy
quelle
0

Deserialisieren Sie beide Klassen und führen Sie einen Zeichenfolgenvergleich durch.

EDIT: Funktioniert perfekt, dies ist die Ausgabe, die ich von NUnit bekomme;

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)

BEARBEITEN SIE ZWEI: Die beiden Objekte können identisch sein, aber die Reihenfolge, in der die Eigenschaften serialisiert werden, ist nicht dieselbe. Daher ist das XML anders. DOH!

EDIT THREE: Das funktioniert. Ich benutze es in meinen Tests. Sie müssen den Sammlungseigenschaften jedoch Elemente in der Reihenfolge hinzufügen, in der der zu testende Code sie hinzufügt.

Casey Burns
quelle
1
serialisieren ? Interessante Idee. Ich bin mir nicht sicher, wie es sich in Bezug auf die Leistung halten würde
Michael Haren
Sie können Doppel- oder Dezimalzahlen nicht mit einer bestimmten Genauigkeit vergleichen.
Noctis
0

Ich weiß, dass dies eine sehr alte Frage ist, aber NUnit hat immer noch keine native Unterstützung dafür. Wenn Sie jedoch Tests im BDD-Stil (ala Jasmine) mögen, werden Sie von NExpect ( https://github.com/fluffynuts/NExpect ) angenehm überrascht sein , erhalten Sie es von NuGet), in dem tiefgreifende Gleichheitstests sind .

(Haftungsausschluss: Ich bin der Autor von NExpect)

daf
quelle
-1

Stringifizieren und vergleichen Sie zwei Strings

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

jmtt89
quelle
-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
quelle
Vielen Dank für dieses Code-Snippet, das möglicherweise nur begrenzte kurzfristige Hilfe bietet. Eine richtige Erklärung würde ihren langfristigen Wert erheblich verbessern, indem sie zeigt, warum dies eine gute Lösung für das Problem ist, und es für zukünftige Leser mit anderen, ähnlichen Fragen nützlicher machen. Bitte bearbeiten Sie Ihre Antwort, um eine Erklärung hinzuzufügen, einschließlich der von Ihnen getroffenen Annahmen.
Toby Speight
Und was trägt dies zu Max 'Antwort bei ?
Toby Speight