web-dev-qa-db-ger.com

Wie kann man behaupten, dass zwei Listen Elemente mit denselben öffentlichen Eigenschaften in NUnit enthalten?

Ich möchte behaupten, dass die Elemente von zwei Listen Werte enthalten, die ich erwartet habe, so etwas wie:

var foundCollection = fooManager.LoadFoo();
var expectedCollection = new List<Foo>() 
{
    new Foo() { Bar = "a", Bar2 = "b" },
    new Foo() { Bar = "c", Bar2 = "d" }
};

//assert: I use AreEquivalent since the order does not matter
CollectionAssert.AreEquivalent(expectedCollection, foundCollection);

Der obige Code wird jedoch nicht funktionieren (ich vermute, weil .Equals () für verschiedene Objekte mit demselben Wert nicht true zurückgibt). In meinem Test sind mir nur die öffentlichen Eigenschaftswerte wichtig, nicht, ob die Objekte gleich sind. Was kann ich tun, um meine Behauptung aufzustellen?

19
Louis Rhys

ÜBERARBEITETE ANTWORT

Es gibt eine CollectionAssert.AreEqual(IEnumerable, IEnumerable, IComparer)-Überladung, um zu behaupten, dass zwei Objektgruppen die gleichen Objekte in derselben Reihenfolge enthalten. Verwenden Sie eine IComparer-Implementierung, um die Objektäquivalenz zu überprüfen.

In dem oben beschriebenen Szenario ist die Reihenfolge nicht wichtig. Um jedoch ausreichend mit der Situation umzugehen, in der es mehrere äquivalente Objekte in den beiden Auflistungen gibt, ist es erforderlich, die Objekte in jeder Auflistung zuerst zu ordnen und einen Einzelvergleich zu verwenden, um sicherzustellen, dass auch die Anzahl der äquivalenten Objekte gleich ist in den beiden Sammlungen.

Enumerable.OrderBy liefert eine Überladung, die ein IComparer<T>-Argument benötigt. Um sicherzustellen, dass die beiden Sammlungen in derselben Reihenfolge sortiert werden, ist es mehr oder weniger erforderlich, dass die Typen der identifizierenden Eigenschaften IComparable implementieren. Hier ist ein Beispiel für eine Vergleichsklasse, die sowohl die IComparer- als auch die IComparer<Foo>-Schnittstelle implementiert und bei der angenommen wird, dass Bar bei der Bestellung Vorrang hat:

public class FooComparer : IComparer, IComparer<Foo>
{
    public int Compare(object x, object y)
    {
        var lhs = x as Foo;
        var rhs = y as Foo;
        if (lhs == null || rhs == null) throw new InvalidOperationException();
        return Compare(lhs, rhs);
    }

    public int Compare(Foo x, Foo y)
    {
        int temp;
        return (temp = x.Bar.CompareTo(y.Bar)) != 0 ? temp : x.Bar2.CompareTo(y.Bar2);
    }
}

Um zu behaupten, dass die Objekte in den beiden Sammlungen gleich sind und in der gleichen Anzahl stehen (jedoch nicht notwendigerweise in der gleichen Reihenfolge), sollten die folgenden Zeilen den Trick ausführen:

var comparer = new FooComparer();
CollectionAssert.AreEqual(
    expectedCollection.OrderBy(foo => foo, comparer), 
    foundCollection.OrderBy(foo => foo, comparer), comparer);    
20

Nein, NUnit hat keinen solchen Mechanismus wie er aktuell ist. Sie müssen Ihre eigene Assertionslogik rollen. Entweder als separate Methode oder mit Has.All.Matches:

Assert.That(found, Has.All.Matches<Foo>(f => IsInExpected(f, expected)));

private bool IsInExpected(Foo item, IEnumerable<Foo> expected)
{
    var matchedItem = expected.FirstOrDefault(f => 
        f.Bar1 == item.Bar1 &&
        f.Bar2 == item.Bar2 &&
        f.Bar3 == item.Bar3
    );

    return matchedItem != null;
}

Dies setzt natürlich voraus, dass Sie alle relevanten Eigenschaften im Voraus kennen (andernfalls muss IsInExpected auf Reflexion zurückgreifen), und diese Elementreihenfolge ist nicht relevant.

(Und Ihre Annahme war richtig, die Sammlung von NUnit verwendet Standardvergleicher für Typen, die in den meisten Fällen benutzerdefiniert die ReferenceEquals des Objekts sind.)

6
k.m

Die Verwendung von Has.All.Matches () eignet sich sehr gut für den Vergleich einer found collection mit der expected collection. Es ist jedoch nicht erforderlich, das von Has.All.Matches () verwendete Prädikat als separate Funktion zu definieren. Für relativ einfache Vergleiche kann das Prädikat so als Teil des Lambda-Ausdrucks eingeschlossen werden.

Assert.That(found, Has.All.Matches<Foo>(f => 
    expected.Any(e =>
        f.Bar1 == e.Bar1 &&
        f.Bar2 == e.Bar2 &&
        f.Bar3= = e.Bar3)));

Diese Behauptung stellt zwar sicher, dass jeder Eintrag in der found collection auch in der expected collection vorhanden ist, beweist jedoch nicht das Gegenteil, nämlich dass jeder Eintrag in der erwartet wird collection ist in der found collection enthalten. Wenn es also wichtig ist zu wissen, dass found und expected contain semantisch äquivalent sind (d. H. Sie enthalten die gleichen semantisch äquivalenten Einträge), müssen wir eine zusätzliche Assertion hinzufügen. 

Die einfachste Wahl besteht darin, Folgendes hinzuzufügen.

Assert.AreEqual(found.Count() == expected.Count());

Für diejenigen, die einen größeren Hammer bevorzugen, kann stattdessen die folgende Behauptung verwendet werden.

Assert.That(expected, Has.All.Matches<Foo>(e => 
    found.Any(f =>
        e.Bar1 == f.Bar1 &&
        e.Bar2 == f.Bar2 &&
        e.Bar3= = f.Bar3)));

Durch die Verwendung der ersten Behauptung oben in Verbindung mit der zweiten (bevorzugten) oder dritten Behauptung haben wir nun bewiesen, dass die beiden Sammlungen semantisch gleich sind.

4
RickC

Hast du so etwas probiert?

Assert.That(foundCollection, Is.EquivalentTo(expectedCollection))
3
Erwin

Um gleichberechtigte Operationen an komplexen Typen durchzuführen, müssen Sie IComaprable implementieren.

http://support.Microsoft.com/kb/320727

Alternativ können Sie die rekursive Reflexion verwenden, was weniger wünschenswert ist.

1
Slappy

Eine Option ist das Erstellen von benutzerdefinierten Einschränkungen, um die Elemente zu vergleichen. Hier ist ein Nizza Artikel zum Thema: http://www.davidarno.org/2012/07/25/improving-nunit-custom-constraints-with-syntax-helpers/

1
Scroog1

Ich hatte ein ähnliches Problem. Listing Mitwirkende, die "Kommentatoren" und andere Personen enthält ... Ich möchte alle Kommentare abrufen und davon die Ersteller ableiten, aber ich bin natürlich nur an einzigartigen Erstellern interessiert. Wenn jemand 50 Kommentare erstellt hat, möchte ich, dass ihr Name nur einmal erscheint. Ich schreibe also einen Test, um zu sehen, dass die Kommentatoren das Ergebnis von GetContributors () haben. 

Ich kann mich irren, aber was ich denke, ist Ihr nach (was war, als ich diesen Beitrag gefunden habe), zu behaupten, dass sich in jeder Sammlung genau ein Element befindet, das sich in einer anderen Sammlung befindet. 

Ich habe das so gelöst: 

Assert.IsTrue(commenters.All(c => actual.Count(p => p.Id == c.Id) == 1));

Wenn Sie möchten, dass die Ergebnisliste auch andere Elemente als erwartet enthält, können Sie auch die Länge der Listen vergleichen. 

Assert.IsTrue(commenters.length == actual.Count());

Ich hoffe, das ist hilfreich. Wenn ja, wäre ich sehr dankbar, wenn Sie meine Antwort bewerten würden. 

1
Dizzypointed

Einfacher Code, der die Verwendung des IComparer erläutert

using System.Collections;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace CollectionAssert
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            IComparer collectionComparer = new CollectionComparer();
            var expected = new List<SomeModel>{ new SomeModel { Name = "SomeOne", Age = 40}, new SomeModel{Name="SomeOther", Age = 50}};
            var actual = new List<SomeModel> { new SomeModel { Name = "SomeOne", Age = 40 }, new SomeModel { Name = "SomeOther", Age = 50 } };
            NUnit.Framework.CollectionAssert.AreEqual(expected, actual, collectionComparer);
        }
    }

    public class SomeModel
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class CollectionComparer : IComparer, IComparer<SomeModel>
    {
        public int Compare(SomeModel x, SomeModel y)
        {
            if(x == null || y == null) return -1;
            return x.Age == y.Age && x.Name == y.Name ? 0 : -1;
        }

        public int Compare(object x, object y)
        {
            var modelX = x as SomeModel;
            var modelY = y as SomeModel;
            return Compare(modelX, modelY);
        }
    }
}
0
Shishira M J

Ich empfehle, keine Reflexion oder etwas Komplexes zu verwenden, da dies einfach mehr Arbeit/Wartung hinzufügt.

Serialisieren Sie das Objekt (ich empfehle Json) und vergleichen Sie es mit einem String. Ich bin mir nicht sicher, warum Sie Widerspruch einlegen, aber ich würde es trotzdem empfehlen, da es für jeden Typ einen eigenen Vergleich speichern wird.

Und es funktioniert automatisch bei Änderung von Domänenobjekten.

Beispiel (SharpTestsEx für fließend)

using Newtonsoft.Json;
using SharpTestsEx;

JsonConvert.SerializeObject(actual).Should().Be.EqualTo(JsonConvert.SerializeObject(expected));

Sie können es als einfache Erweiterung schreiben und lesbarer machen.

   public static class CollectionAssertExtensions
    {
        public static void CollectionAreEqual<T>(this IEnumerable<T> actual, IEnumerable<T> expected)
        {
            JsonConvert.SerializeObject(actual).Should().Be.EqualTo(JsonConvert.SerializeObject(expected));
        }
    }

und dann mit Ihrem Beispiel es so nennen:

var foundCollection = fooManager.LoadFoo();
var expectedCollection = new List<Foo>() 
{
    new Foo() { Bar = "a", Bar2 = "b" },
    new Foo() { Bar = "c", Bar2 = "d" }
};


foundCollection.CollectionAreEqual(foundCollection);

Sie erhalten eine Bestätigungsnachricht wie folgt:

...: "a", "Bar2": "b"}, {"Bar": "d", "Bar2": "d"}]

...: "a", "Bar2": "b"}, {"Bar": "c", "Bar2": "d"}]

... _ __ _ __ _ __ _ __ _ __ _ __^ _ __ _ _

0
Choco Smith