Kontext
Ich schreibe einen einfachen JUnit- Test für die MyObject
Klasse.
A MyObject
kann aus einer statischen Factory - Methode erstellt werden , die ein varargs Takes String .
MyObject.ofComponents("Uno", "Dos", "Tres");
Während des Bestehens von MyObject
können Clients jederzeit die Parameter überprüfen, mit denen sie in Form einer Liste <E> über die .getComponents()
Methode erstellt wurden.
myObject.ofComponents(); // -> List<String>: { "Uno", "Dos", "Tres" }
Mit anderen Worten, a MyObject
merkt sich die Liste der Parameter, die sie ins Leben gerufen haben, und legt sie offen. Weitere Details zu diesem Vertrag:
- Die Reihenfolge von
getComponents
wird dieselbe sein wie die für die Objekterstellung ausgewählte - Doppelte nachfolgende String- Komponenten sind zulässig und werden der Reihe nach beibehalten
- Das Verhalten bei
null
ist undefiniert (anderer Code garantiert, dass keinnull
Werk erreicht wird) - Es gibt keine Möglichkeit, die Liste der Komponenten nach der Objektinstanziierung zu ändern
Ich schreibe einen einfachen Test, der MyObject
aus einer Liste von Zeichenfolgen einen erstellt und prüft, ob er dieselbe Liste über zurückgeben kann .getComponents()
. Ich mache das sofort, aber das soll in einiger Entfernung in einem realistischen Codepfad passieren .
Code
Hier mein Versuch:
List<String> argumentComponents = Lists.newArrayList("One", "Two", "Three");
List<String> returnedComponents =
MyObject.ofComponents(
argumentComponents.toArray(new String[argumentComponents.size()]))
.getComponents();
assertTrue(Iterables.elementsEqual(argumentComponents, returnedComponents));
Frage
- Ist Google Guava
Iterables.elementsEqual()
der beste Weg, um diese beiden Listen zu vergleichen, vorausgesetzt, ich habe die Bibliothek in meinem Erstellungspfad? das ist etwas, worüber ich mich gequält habe; sollte ich diese Hilfsmethode verwenden, die über eine Iterable <E> .. Größe überprüft und dann die Ausführung wiederholt.equals()
.. oder eine andere Methode, die eine Internetsuche vorschlägt? Wie kann man Listen für Unit-Tests kanonisch vergleichen?
IsIterableContainingInOrder
das genau zum Testen im Gegensatz zuIterables
. Wenn Sie Hamcrest verwenden, erhalten Sie im Fehlerfall gute Nachrichten.IsIterableContainingInOrder
ist es nicht streng genug, oder?M={A,B,C}
enthältN={A,B}
in der Reihenfolge aberM!=N
.Creates a matcher for Iterables that matches when a single pass over the examined Iterable yields a series of items, each logically equal to the corresponding item in the specified items. For a positive match, the examined iterable must be of the same length as the number of specified items
Antworten:
Ich bevorzuge die Verwendung von Hamcrest, da es im Fehlerfall eine viel bessere Ausgabe liefert
Anstatt zu berichten
expected true, got false
es wird berichten
expected List containing "1, 2, 3, ..." got list containing "4, 6, 2, ..."
IsIterableContainingInOrder.contain
Hamcrest
Nach Angaben des Javadoc:
Das
listUnderTest
Element muss also die gleiche Anzahl von Elementen haben und jedes Element muss in der angegebenen Reihenfolge mit den erwarteten Werten übereinstimmen.quelle
Expected: iterable containing [<1>, <3>, <5>, <7>] but: item 3: was <6>
. In einer langen Liste von Elementen, derentoString
Ausgabe sehr lang sein kann, kann es sehr hilfreich sein, zu wissen, welches Element falsch ist.IsIterableContainingInOrder.contain()
ist irreführender Name. Es spiegelt nicht die Tatsache wider, dass zusätzlichactual
überprüft wird, ob die gleiche Anzahl von Elementen vorhanden ist (Überprüfung der Größengleichheit).hasSameElementsAs()
wäre passender NameWarum nicht einfach benutzen
List#equals
?Vertrag von
List#equals
:quelle
IsIterableContainingInOrder.contain()
(dessen Name sein Verhalten nicht vollständig widerspiegelt). Wenn Sie also andere Sammlungen als List <> haben, ist es besser, diese in die Liste zu konvertieren und diesen Code zu verwenden, als ein Array, das von Hamcrest benötigt wird.Die equals () -Methode in Ihrer List-Implementierung sollte also einen elementweisen Vergleich durchführen
ist viel einfacher.
quelle
org.junit.Assert.assertEquals()
undorg.junit.Assert.assertArrayEquals()
mach den Job.So vermeiden Sie die nächsten Fragen: Wenn Sie die Reihenfolge ignorieren möchten, setzen Sie alle Elemente und vergleichen Sie sie:
Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two))
Wenn Sie jedoch nur Duplikate ignorieren möchten, aber den von Ihnen aufgelisteten Auftragsumbruch beibehalten möchten
LinkedHashSet
.Noch ein Tipp. Der Trick
Assert.assertEquals(new HashSet<String>(one), new HashSet<String>(two))
funktioniert einwandfrei, bis der Vergleich fehlschlägt. In diesem Fall wird eine Fehlermeldung mit Zeichenfolgendarstellungen Ihrer Mengen angezeigt, die verwirrend sein kann, da die Reihenfolge in der Menge (zumindest für komplexe Objekte) fast nicht vorhersehbar ist. Der Trick, den ich gefunden habe, besteht darin, die Sammlung mit einem sortierten Satz anstatt zu umschließenHashSet
. Sie könnenTreeSet
mit benutzerdefinierten Komparator verwenden.quelle
Für eine hervorragende Lesbarkeit des Codes bietet Fest Assertions eine gute Unterstützung für das Durchsetzen von Listen
Also in diesem Fall so etwas wie:
Assertions.assertThat(returnedComponents).containsExactly("One", "Two", "Three");
Oder machen Sie die erwartete Liste zu einem Array, aber ich bevorzuge den obigen Ansatz, weil es klarer ist.
quelle
assertTrue () / assertFalse (): Nur zum Bestätigen des zurückgegebenen booleschen Ergebnisses
Sie möchten
Assert.assertTrue()
oderAssert.assertFalse()
als zu testende Methode einenboolean
Wert zurückgeben.Da die Methode eine bestimmte Sache zurückgibt, z. B. eine
List
, die einige erwartete Elemente enthalten sollte, wird Folgendes behauptetassertTrue()
:Assert.assertTrue(myActualList.containsAll(myExpectedList)
ist ein Anti-Muster.Es macht das Schreiben der Behauptung einfach, aber wenn der Test fehlschlägt, macht es auch das Debuggen schwierig, da der Testläufer Ihnen nur Folgendes sagt:
Assert.assertEquals(Object, Object)
in JUnit4 oderAssertions.assertIterableEquals(Iterable, Iterable)
in JUnit 5: Nur als beides verwendenequals()
undtoString()
für die Klassen (und tief) der verglichenen Objekte überschrieben werdenDies ist wichtig, da sich der Gleichheitstest in der Zusicherung
equals()
auftoString()
die verglichenen Objekte und die Testfehlermeldung auf diese stützt .Da
String
überschreibt beideequals()
undtoString()
, ist es vollkommen gültig, dasList<String>
mit zu behauptenassertEquals(Object,Object)
. Und zu diesem Thema: Sie müssenequals()
in einer Klasse überschreiben, weil dies im Hinblick auf die Objektgleichheit sinnvoll ist, nicht nur, um Aussagen in einem Test mit JUnit zu vereinfachen.Um die Behauptungen zu vereinfachen, haben Sie andere Möglichkeiten (die Sie in den nächsten Punkten der Antwort sehen können).
Ist Guave eine Möglichkeit, Unit-Test-Assertions durchzuführen / zu erstellen?
Nein ist es nicht. Guava ist keine Bibliothek zum Schreiben von Unit-Test-Assertions.
Sie brauchen es nicht, um die meisten (alles, was ich denke) Unit-Tests zu schreiben.
Was ist der kanonische Weg, um Listen für Unit-Tests zu vergleichen?
Als gute Praxis bevorzuge ich Assertion / Matcher-Bibliotheken.
Ich kann JUnit nicht dazu ermutigen, bestimmte Behauptungen aufzustellen, da dies wirklich zu wenige und eingeschränkte Funktionen bietet: Es führt nur eine Behauptung mit einer tiefen Gleichheit aus.
Manchmal möchten Sie eine beliebige Reihenfolge in den Elementen zulassen, manchmal möchten Sie zulassen, dass alle Elemente der erwarteten mit der tatsächlichen übereinstimmen, und so weiter für ...
Die Verwendung einer Unit-Test-Assertion / Matcher-Bibliothek wie Hamcrest oder AssertJ ist daher der richtige Weg.
Die eigentliche Antwort bietet eine Hamcrest-Lösung. Hier ist eine AssertJ- Lösung.
org.assertj.core.api.ListAssert.containsExactly()
ist das, was Sie brauchen: Es überprüft, ob die tatsächliche Gruppe genau die angegebenen Werte und nichts anderes enthält, in der angegebenen Reihenfolge:Ihr Test könnte folgendermaßen aussehen:
import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @Test void ofComponent_AssertJ() throws Exception { MyObject myObject = MyObject.ofComponents("One", "Two", "Three"); Assertions.assertThat(myObject.getComponents()) .containsExactly("One", "Two", "Three"); }
Ein guter Punkt von AssertJ ist, dass es
List
unnötig ist, a wie erwartet zu deklarieren : Dadurch wird die Assertion gerader und der Code besser lesbar:Assertions.assertThat(myObject.getComponents()) .containsExactly("One", "Two", "Three");
Und wenn der Test fehlschlägt:
// Fail : Three was not expected Assertions.assertThat(myObject.getComponents()) .containsExactly("One", "Two");
Sie erhalten eine sehr klare Nachricht wie:
Assertion / Matcher-Bibliotheken sind ein Muss, da diese wirklich weiter gehen
Angenommen,
MyObject
es werden keine s-Instanzen gespeichert,String
sondernFoo
s-Instanzen wie:public class MyFooObject { private List<Foo> values; @SafeVarargs public static MyFooObject ofComponents(Foo... values) { // ... } public List<Foo> getComponents(){ return new ArrayList<>(values); } }
Das ist ein sehr häufiges Bedürfnis. Mit AssertJ ist die Behauptung immer noch einfach zu schreiben. Besser können Sie behaupten, dass der Listeninhalt gleich ist, auch wenn die Klasse der Elemente nicht überschrieben wird,
equals()/hashCode()
während JUnit-Methoden Folgendes erfordern:import org.assertj.core.api.Assertions; import static org.assertj.core.groups.Tuple.tuple; import org.junit.jupiter.api.Test; @Test void ofComponent() throws Exception { MyFooObject myObject = MyFooObject.ofComponents(new Foo(1, "One"), new Foo(2, "Two"), new Foo(3, "Three")); Assertions.assertThat(myObject.getComponents()) .extracting(Foo::getId, Foo::getName) .containsExactly(tuple(1, "One"), tuple(2, "Two"), tuple(3, "Three")); }
quelle
Iterables.elementsEqual
die beste Wahl ist:Iterables.elementsEqual
reicht aus, um 2List
s zu vergleichen .Iterables.elementsEqual
wird in allgemeineren Szenarien verwendet. Es werden allgemeinere Typen akzeptiert :Iterable
. Das heißt, Sie könnten sogar aList
mit a vergleichenSet
. (In iterativer Reihenfolge ist es wichtig)Sicher
ArrayList
undLinkedList
gleich definieren ziemlich gut, man könnte gleich gleich nennen. Wenn Sie eine nicht genau definierte Liste verwenden,Iterables.elementsEqual
ist dies die beste Wahl. Eines sollte beachtet werden:Iterables.elementsEqual
akzeptiert nichtnull
So konvertieren Sie Liste in Array:
Iterables.toArray
ist einfacher.Für Unit-Tests empfehle ich , Ihrem Testfall eine leere Liste hinzuzufügen .
quelle