2016-07-30 12 views
2

In einem Komponententest ist es im Allgemeinen eine gute Idee, zurückgegebene Werte anhand der Zeichenfolge zu testen, die ihre toString() - Rückgabewerte liefert.Verwendung von toString() für Komponententests in Java

Zum Beispiel Sie folgendermaßen vorgehen, um sicherzustellen, dass die erwartete Liste zurückgegeben wird:

assertEquals(someExpression.toString() ,"[a, b, c]"); 

Es scheint mir, dass die Überlegungen sind wie folgt:

Vorteile: Zeit sparen (die Konstruktion der Der tatsächliche erwartete Wert erfordert einen längeren Code.

Nachteile: Der Test hängt von toString() ab, das im Dokument nicht formell definiert ist und daher in jeder zukünftigen Version geändert werden kann.

+1

Es ist eine gute Idee, toString zu testen, wenn 'toString' Teil der API des CUT ist. Sonst ist es eine schlechte Idee. –

+0

@BoristheSpider Es ist nicht Teil der getesteten API. Ist es eine schlechte Idee toString() zu verwenden, weil es sich in der Zukunft ändern kann? – Ofer

+0

Wenn sich Ihr 'toString()' in Zukunft ändert, müssen Sie auch Ihren Test ändern. Und das ist ein weiterer Vorteil von Komponententests. Offensichtlich ist der Vergleich einer Liste mit ihrem toString fehlerhaft –

Antwort

4

Das einzige Mal, dass ich auf toString() eines Objekts prüfen würde, wenn ich einen nicht bearbeitbaren Klasse, die nicht hashcode oder equals, umgesetzt haben, sondern einen toString() zur Ausgabe umgesetzt der Inhalt seines Feldes. Selbst dann, ich werde nicht eine hartcodierte Zeichenfolge als Gleichheitstest verwenden, sondern etwas tun, wie

SomeObject b = new SomeObject(expected, values, here); 
assertEquals(a.toString(), b.toString()); 

Ihr Ansatz könnte Zeit zunächst, aber auf lange Sicht speichern wäre es viel mehr Zeit in Anspruch nehmen, nur um Pflegen Sie den Test, da Sie die Zeichenfolge des erwarteten Ergebnisses der toString() fest codieren.

Bearbeiten 1: Natürlich, wenn Sie eine Funktion/einen Prozess testen, der eine Zeichenfolge ausgibt, wäre das eine der Zeiten, die Sie eine hardcoded Zeichenfolge als das erwartete Ergebnis verwenden sollten.

String input = "abcde"; 
String result = removeVowels(input); 
assertEquals(result, "bcd"); 
+0

"hardcoding die erwarteten Ergebnisse" - was meinst du? Ich schreibe die erwarteten Ergebnisse in beide Richtungen.Meinst du, dass ich die erwartete Zeichenfolge von toString() hartcodiere, was eine schlechte Idee ist, weil (meistens) es nie eine formale Definition hat? – Ofer

+0

Nun, bei Tests geht es darum, das erwartete Ergebnis eines Prozesses trotzdem fest zu codieren. Aber ja, ich wollte die Saiten hart codieren. Das tut mir leid. Im Allgemeinen ist es schwierig, die erwartete Zeichenfolge von toString() zu codieren, da Sie sie vom erwarteten Ergebnis der aktuellen Implementierung ableiten, das sich nach einiger Zeit ändern könnte (und wird). Es könnte auch anfällig für Fehler sein, da Test-Ersteller auch die richtige erwartete Zeichenfolge eingeben können, nur weil es die erwartete Zeichenfolge ist. Es ist viel zuverlässiger, die Methode toString() eines Objekts über toString() eines anderen Objekts zu testen. – Kree

+0

Ok, ich kann meinen vorherigen Kommentar nicht bearbeiten. Mit "... könnte auch die richtige erwartete Zeichenfolge eingeben, nur weil es die erwartete Zeichenfolge ist." Ich meinte, dass es keine Basis gibt, von der die fest codierte Zeichenfolge stammt, außer dass es das erwartete Ergebnis ist. Wenn sich der toString plötzlich ändert, müssten Sie auch den Test ändern, während die Verwendung der toString() -Option des Objekts in der Assert kein Problem mit der Aufrechterhaltung der Integrität des Tests hätte. Jetzt, wo ich darüber nachdenke, ist es nicht wirklich eine Sache von toString(), dokumentiert zu werden oder nicht. – Kree

0

Es ist besser, equals und hashCode (auf eigene Faust oder zum Beispiel unter Verwendung von Lomboks @EqualsAndHashCode dafür) außer Kraft zu setzen und den Vergleich zu tun mit ihnen als toString verwenden. Auf diese Weise machen Sie den Test explizit über Gleichheit und später können Sie diese Methoden wiederverwenden.

Dann können Sie tun (nach Ihrem Beispiel mit Liste):

assertEquals(
    someExpression, 
    Arrays.asList(new Foo("a"), new Foo("b"), new Foo("c")) 
); 
+0

Aber angenommen, das Konstruieren des Objekts erfordert viel mehr Code, ist es dann legitim, toString() zu verwenden? (Auch auf einer Instanz einer Standard-Java-Klasse (wie LinkedList) - die ihre toString() ist nicht dokumentiert) – Ofer

1

Die Assert.assertThat() Methode hat Matcher spezifisch für Sammlungen und vieles mehr.

Zum Beispiel Werke für Sammlungen:

List<String> someExpression = asList("a", "b", "c"); 
assertThat(someExpression.toString(), contains("a", "b", "c")); 

Die Vorteile sind:

  • Sie brauchen nicht .toString() umzusetzen;
  • Wenn der Test fehlschlägt, ist die Fehlermeldung ziemlich deskriptiv, wird es sagen "Erwartete Sammlung containig 'b' und hat es nicht gefunden."

Versuchen:

import static org.junit.Assert.assertThat; 
import static org.hamcrest.Matchers.contains; 
import static java.util.Arrays.asList; 

List<String> someExpression = asList("a", "c"); 
assertThat(someExpression.toString(), contains("a", "b", "c")); 
+0

Danke, die Liste war nur ein Beispiel. Ist es im Allgemeinen vernünftig, toString() zu verwenden, anstatt ein Objekt zu erstellen, wenn es viel Code speichert? – Ofer

+1

Sie könnten es verwenden, wenn es Sie * das * viel Zeit spart. Aber es ist ein fragiler Ansatz (der '.toString()' selbst kann einen Fehler haben und falsche Positive erzeugen!). Im Allgemeinen würde ich diese Notwendigkeit/Schwierigkeit als ein mögliches Zeichen irgendeines anderen Designproblems konfrontieren. Warum reichen die aktuellen APIs nicht aus? Warum müssen Sie so viele Informationen überprüfen, ist das nicht zu viel Implementierungsdetails? Und so weiter. Nichts ist in Stein gemeißelt, also könntest du es verwenden, aber ich würde es nicht für jeden Fall oder Anfänger empfehlen - du musst wirklich verstehen, dass es ein suboptimaler Ansatz ist (mit Fallstricken), bevor du es benutzen kannst. – acdcjunior

1

Ich benutze toString in besonderen Fällen, vor allem, wenn der entsprechende Code viel komplexer wäre. Wenn Sie komplexere Datenstrukturen erhalten, möchten Sie die gesamte Struktur schnell testen. Wenn Sie Feld für Feld Code zum Testen schreiben, besteht die Gefahr, dass Sie vergessen, ein Feld hinzuzufügen.

Für mein Beispiel hier https://vanilla-java.github.io/2016/03/23/Microservices-in-the-Chronicle-world-Part-1.html

TopOfBookPrice tobp = new TopOfBookPrice("Symbol", 123456789000L, 1.2345, 1_000_000, 1.235, 2_000_000); 
assertEquals("!TopOfBookPrice {\n" + 
     " symbol: Symbol,\n" + 
     " timestamp: 123456789000,\n" + 
     " buyPrice: 1.2345,\n" + 
     " buyQuantity: 1000000.0,\n" + 
     " sellPrice: 1.235,\n" + 
     " sellQuantity: 2000000.0\n" + 
     "}\n", tobp.toString()); 

Dieser Test schlägt fehl, warum? Sie können in Ihrem IDE leicht sehen, wie es

Display Comparison

produziert Wie Sie komplexere Beispiele erhalten, jeden (verschachtelten) Wert überprüft und es später zu korrigieren, wenn es bricht, ist wirklich langweilig. Ein längeres Beispiel ist

assertEquals("--- !!meta-data #binary\n" + 
     "header: !SCQStore {\n" + 
     " wireType: !WireType BINARY,\n" + 
     " writePosition: 0,\n" + 
     " roll: !SCQSRoll {\n" + 
     " length: !int 86400000,\n" + 
     " format: yyyyMMdd,\n" + 
     " epoch: 0\n" + 
     " },\n" + 
     " indexing: !SCQSIndexing {\n" + 
     " indexCount: !short 16384,\n" + 
     " indexSpacing: 16,\n" + 
     " index2Index: 0,\n" + 
     " lastIndex: 0\n" + 
     " },\n" + 
     " lastAcknowledgedIndexReplicated: -1,\n" + 
     " recovery: !TimedStoreRecovery {\n" + 
     " timeStamp: 0\n" + 
     " }\n" + 
     "}\n" + 
     "# position: 344, header: 0\n" + 
     "--- !!data #binary\n" + 
     "msg: Hello world\n" + 
     "# position: 365, header: 1\n" + 
     "--- !!data #binary\n" + 
     "msg: Also hello world\n", Wires.fromSizePrefixedBlobs(mappedBytes.readPosition(0))); 

Ich habe viel längere Beispiele, wo ich das tue. Ich muss für jeden Wert, den ich überprüfe, keine Zeile schreiben, und wenn sich das Format ändert, weiß ich sogar etwas darüber. Ich möchte nicht, dass sich das Format unerwartet ändert.

Hinweis: Ich gebe immer zuerst den erwarteten Wert ein.

1

Mit toString() für Unit-Tests in Java

Wenn in Ihrem Gerät zu testen, Ihre Absicht, dass alle Felder des Objekts zu behaupten ist sind Gleichen, die toString() Methode des Objekts zu prüfenden Geräten sind Es wurde also eine Zeichenfolge zurückgegeben, die Schlüsselwerte für alle Felder anzeigt.
Aber wie Sie unterstrichen haben, in der Zeit können die Felder der Klasse ändern und so Ihre toString() Methode möglicherweise nicht tatsächliche Felder widerspiegeln und die toString() Methode war kein Design dafür.

Der Test hängt von toString() ab, das im Dokument nicht formell definiert ist und daher in jeder zukünftigen Version geändert werden kann.

Es gibt Alternativen zu toString(), die ohne
schönen Code zu schreiben erlaubt beschränke Sie eine toString() Methode mit allen Schlüssel-Wert-Feldern zurückgegeben oder Misch toString() ursprüngliche Absicht für das Debuggen Zweck mit Behauptung Zwecke zu halten.
Außerdem sollten Komponententests das Verhalten der getesteten Methode dokumentieren.
A-Code als die folgenden ist nicht selbsterklärend auf dem erwarteten Verhalten:

assertEquals(someExpression.toString() ,"[a, b, c]"); 

1) Reflection Bibliothek

Die Idee Java Reflexion Mechanismen mit JUnit Behauptung Mechanismen (oder jede Testeinheit mischt API, die Sie verwenden).
Mit der Reflexion können Sie vergleichen, ob jedes Feld von zwei Objekten des gleichen Typs gleich ist. Die Idee ist folgende: Sie erstellen eine Fehlermeldung, die Felder anzeigt, in denen die Gleichheit zwischen zwei Feldern nicht respektiert wird und aus welchen Gründen.
Wenn alle Felder durch Reflektion verglichen wurden, verwenden Sie den Unit Test-Mechanismus, wenn die Fehlermeldung nicht null ist, um eine Fehlerausnahme mit einer relevanten Fehlermeldung zu erzeugen, die Sie zuvor erstellt haben.
Ansonsten ist die Behauptung ein Erfolg. Ich benutze es regelmäßig bei meinem Projekt, wenn es relevant wird.
Sie können es selbst tun, oder Sie können API wie Untils verwenden, die die Arbeit für Sie erledigen können.

User user1 = new User(1, "John", "Doe"); 
User user2 = new User(1, "John", "Doe"); 
ReflectionAssert.assertReflectionEquals(user1, user2); 

Persönlich habe ich meine eigene Bibliothek den Job mit der Möglichkeit zu tun, mehrere Anpassungen in der Behauptung hinzuzufügen. Es ist nicht sehr schwer zu tun und Sie können von Untils inspirieren.

2) Unit-Test-Matcher Bibliothek

Ich denke, dass es eine bessere Alternative ist.
Sie könnten Harmcrest oder AssertJ verwenden.
Es ist ausführlicher, dass reine Reflexion, sondern es hat auch große Vorteile:

  • es durch die bewährte Methode erwartet speziell das Verhalten dokumentiert.
  • es fließend Methoden bietet behaupten
  • es mehrere Behauptung Funktionen bietet Kesselblech Code zu reduzieren in Einheit

Mit AssertJ testet, können Sie diesen Code ersetzen könnte:

assertEquals(foo.toString() ,"[value1=a, value1=b, value1=c]"); 

wo foo eine Foo-Instanz einer Klasse definiert als:

public class Foo{ 

    private String value1; 
    private String value2; 
    private String value3; 

    // getters 
} 

von etwas wie :

Assertions.assertThat(someExpression) 
      .extracting(Foo::getValue1, Foo::getValue2, Foo::getValue3) 
      .containsExactly(a, b, c); 
Verwandte Themen