Java >> Java Tutorial >  >> Java

Schreiben von Tests für Datenzugriffscode - Datenangelegenheiten

Wenn wir Tests für unseren Datenzugriffscode schreiben, verwenden wir Datensätze für zwei verschiedene Zwecke:

  1. Wir initialisieren unsere Datenbank in einen bekannten Zustand, bevor unsere Datenzugriffstests durchgeführt werden.
  2. Wir überprüfen, ob die richtigen Änderungen in der Datenbank gefunden werden.

Das scheinen leichte Aufgaben zu sein. Es ist jedoch sehr leicht, Dinge auf eine Weise durcheinander zu bringen, die unser Leben schmerzhaft macht und uns viel Zeit kostet.

Deshalb habe ich mich entschlossen, diesen Blogbeitrag zu schreiben.

Dieser Blogpost beschreibt die drei häufigsten Fehler, die wir machen können, wenn wir DbUnit-Datensätze verwenden, und was noch wichtiger ist, dieser Blogpost beschreibt, wie wir sie vermeiden können.

Die drei Todsünden von DbUnit-Datensätzen

Der häufigste Grund, warum Bibliotheken wie DbUnit einen so schlechten Ruf haben, ist, dass Entwickler sie falsch verwenden und sich beschweren, nachdem sie sich selbst ins Knie geschossen haben.

Es ist wahr, dass wir bei der Verwendung von DbUnit-Datensätzen Fehler machen können, die viel Frustration verursachen und uns viel Zeit kosten. Deshalb müssen wir diese Fehler verstehen, um sie zu vermeiden.

Es gibt drei häufige (und kostspielige) Fehler, die wir machen können, wenn wir DbUnit-Datensätze verwenden:

1. Initialisieren der Datenbank durch Verwenden eines einzelnen Datensatzes

Der erste Fehler, den wir machen können, besteht darin, unsere Datenbank mit einem einzigen Datensatz zu initialisieren. Obwohl dies ziemlich praktisch ist, wenn unsere Anwendung nur eine Handvoll Funktionen und eine kleine Datenbank mit ein paar Datenbanktabellen hat, ist dies möglicherweise nicht der Fall, wenn wir in einem realen Softwareprojekt arbeiten.

Die Chancen stehen gut, dass unsere Anwendung viele Funktionen und eine große Datenbank mit Dutzenden (oder Hunderten) von Datenbanktabellen hat. Wenn wir diesen Ansatz in einem realen Softwareprojekt verwenden, wird unser Datensatz RIESIG sein weil:

  • Jede Datenbanktabelle erhöht die Größe unseres Datensatzes.
  • Die Anzahl der Tests erhöht die Größe unseres Datensatzes, da verschiedene Tests unterschiedliche Daten erfordern.

Die Größe unseres Datensatzes ist ein großes Problem, weil:

  • Je größer der Datensatz, desto langsamer ist es, die verwendete Datenbank in einen bekannten Zustand zu initialisieren, bevor unsere Tests ausgeführt werden. Erschwerend kommt hinzu, dass unsere Tests immer langsamer werden, wenn wir neue Datenbanktabellen hinzufügen oder neue Tests schreiben.
  • Es ist unmöglich herauszufinden, welche Daten für einen bestimmten Testfall relevant sind, ohne den getesteten Code zu lesen. Wenn ein Testfall fehlschlägt, ist es viel schwieriger, den Grund dafür herauszufinden, als es sein sollte.

Beispiel:

Nehmen wir an, wir müssen Tests für ein CRM schreiben, mit dem die Informationen unserer Kunden und Büros verwaltet werden. Außerdem befindet sich jeder Kunde und jedes Büro in einer Stadt. Die erste Version unseres Datensatzes könnte wie folgt aussehen:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<cities id="1" name="Helsinki"/>
	
	<customers id="1" city_id="1" name="Company A"/>
	
	<offices id="1" city_id="1" name="Office A"/>
</dataset>

Wir können sofort sehen, dass unsere Testsuite ein unnötiges INSERT aufrufen muss Anweisung pro Testfall. Das scheint keine große Sache zu sein, aber mal sehen, was passiert, wenn wir Tests für Funktionen schreiben müssen, die Kunden und Büros auflisten, die sich in einer bestimmten Stadt befinden. Nachdem wir diese Tests geschrieben haben, sieht unser Datensatz wie folgt aus:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<cities id="1" name="Helsinki"/>
	<cities id="2" name="Tampere"/>
	<cities id="3" name="Turku"/>
	
	<customers id="1" city_id="1" name="Company A"/>
	<customers id="2" city_id="2" name="Company B"/>
	
	<offices id="1" city_id="1" name="Office A"/>
	<offices id="2" city_id="3" name="Office B"/>
</dataset>

Wie wir sehen können,

  • Unsere Testsuite muss drei unnötige INSERT aufrufen Anweisungen pro Testfall.
  • Es ist nicht klar, welche Daten für einen bestimmten Testfall relevant sind, da unser Datensatz die gesamte Datenbank initialisiert, bevor jeder Test ausgeführt wird.

Dies mag nicht wie ein katastrophaler Fehler erscheinen (und ist es auch nicht), aber dieses Beispiel zeigt dennoch, warum wir diesen Ansatz nicht verfolgen sollten, wenn wir Tests für reale Anwendungen schreiben.

2. Erstellen eines Datensatzes pro Testfall oder einer Gruppe von Testfällen

Wir können die durch einen einzelnen Datensatz verursachten Probleme lösen, indem wir diesen Datensatz in kleinere Datensätze aufteilen. Wenn wir uns dafür entscheiden, können wir einen Datensatz pro Testfall oder eine Gruppe von Testfällen erstellen.

Wenn wir diesen Ansatz verfolgen, sollte jeder unserer Datensätze nur die Daten enthalten, die für den Testfall (oder die Testfälle) relevant sind. Dies scheint eine gute Idee zu sein, da unsere Datensätze kleiner sind und jeder Datensatz nur die relevanten Daten enthält.

Wir müssen uns jedoch daran erinnern, dass der Weg zur Hölle mit guten Absichten gepflastert ist. Obwohl unsere Tests schneller sind als die Tests, die einen einzelnen Datensatz verwenden, und es einfach ist, die für einen bestimmten Testfall relevanten Daten zu finden, hat dieser Ansatz einen großen Nachteil:

Die Pflege unserer Datensätze wird zur Hölle.

Da viele Datasets Daten enthalten, die in dieselben Tabellen eingefügt werden, erfordert die Pflege dieser Datasets viel Arbeit, wenn die Struktur dieser Datenbanktabellen geändert wird (oder sollten wir sagen, wann?).

Beispiel:

Wenn wir diesen Ansatz verwenden, wenn wir Tests für das früher eingeführte CRM schreiben, könnten wir unseren einzelnen Datensatz in zwei kleinere Datensätze aufteilen.

Der erste Datensatz enthält die Informationen, die erforderlich sind, wenn wir Tests für die Funktionen schreiben, die zur Verwaltung der Informationen unserer Kunden verwendet werden. Es sieht wie folgt aus:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<cities id="1" name="Helsinki"/>
	<cities id="2" name="Tampere"/>
	
	<customers id="1" city_id="1" name="Company A"/>
	<customers id="2" city_id="2" name="Company B"/>
</dataset>

Der zweite Datensatz enthält die Informationen, die wir benötigen, wenn wir Tests für die Funktionen schreiben, die zur Verwaltung der Informationen unserer Büros verwendet werden. Der zweite Datensatz sieht wie folgt aus:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<cities id="1" name="Helsinki"/>
	<cities id="3" name="Turku"/>
	
	<offices id="1" city_id="1" name="Office A"/>
	<offices id="2" city_id="3" name="Office B"/>
</dataset>

Was passiert, wenn wir Änderungen an der Struktur der Städte vornehmen? Tisch?

Genau! Deshalb ist es keine gute Idee, diesem Ansatz zu folgen.

3. Alles behaupten

Wir können einen Datensatz erstellen, der verwendet wird, um zu überprüfen, ob die richtigen Daten in der Datenbank gefunden werden, indem Sie diesen Schritten folgen:

  1. Kopieren Sie die gefundenen Daten aus dem Datensatz, der zum Initialisieren der Datenbank verwendet wird, in einen bekannten Zustand, bevor unsere Tests ausgeführt werden.
  2. Fügen Sie seinen Inhalt in das Dataset ein, das verwendet wird, um zu überprüfen, ob die richtigen Daten in der Datenbank gefunden werden.
  3. Nehmen Sie die erforderlichen Änderungen daran vor.

Das Befolgen dieser Schritte ist gefährlich, weil es sinnvoll ist. Wenn wir unsere Datenbank mit dem Datensatz X initialisiert haben, erscheint es schließlich logisch, dass wir diesen Datensatz verwenden, wenn wir den Datensatz erstellen, der verwendet wird, um sicherzustellen, dass die richtigen Informationen aus der Datenbank gefunden werden.

Dieser Ansatz hat jedoch drei Nachteile:

  • Es ist schwierig, das erwartete Ergebnis zu bestimmen, da diese Datensätze häufig Informationen enthalten, die durch den getesteten Code nicht geändert werden. Dies ist insbesondere dann ein Problem, wenn wir einen oder zwei Fehler gemacht haben.
  • Da diese Datasets Informationen enthalten, die nicht durch getesteten Code geändert werden (z. B. allgemeine Datenbanktabellen), wird die Pflege dieser Datasets viel unnötige Arbeit erfordern. Wenn wir die Struktur dieser Datenbanktabellen ändern, müssen wir die gleiche Änderung auch an unseren Datensätzen vornehmen. Das wollen wir nicht.
  • Da diese Datensätze oft unnötige Informationen enthalten (Informationen, die durch den getesteten Code nicht geändert werden), ist die Überprüfung, ob die erwarteten Informationen aus der Datenbank gefunden werden, langsamer als es sein könnte.

Beispiel:

Nehmen wir an, wir müssen Tests für eine Funktion schreiben, die die Informationen eines Kunden aktualisiert (die ID des aktualisierten Kunden ist 2).

Der Datensatz, der die verwendete Datenbank in einen bekannten Zustand initialisiert, bevor dieser Test ausgeführt wird, sieht wie folgt aus:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<cities id="1" name="Helsinki"/>
	<cities id="2" name="Tampere"/>
	
	<customers id="1" city_id="1" name="Company A"/>
	<customers id="2" city_id="2" name="Company B"/>
</dataset>

Der Datensatz, der sicherstellt, dass die richtigen Informationen in der Datenbank gespeichert werden, sieht wie folgt aus:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<cities id="1" name="Helsinki"/>
	<cities id="2" name="Tampere"/>
	
	<customers id="1" city_id="1" name="Company A"/>
	<customers id="2" city_id="1" name="Company B"/>
</dataset>

Gehen wir die Nachteile dieser Lösung nacheinander durch:

  • Es ist ziemlich einfach herauszufinden, welche Informationen aktualisiert werden sollten, weil die Größe unseres Datensatzes so klein ist, aber es ist nicht so einfach, wie es sein könnte. Wenn unser Datensatz größer wäre, wäre dies natürlich viel schwieriger.
  • Dieser Datensatz enthält die gefundenen Informationen aus den Städten Tisch. Da diese Informationen von der getesteten Funktion nicht modifiziert werden, müssen unsere Tests irrelevante Behauptungen aufstellen und das bedeutet, dass unsere Tests langsamer sind, als sie sein könnten.
  • Wenn wir die Struktur der Städte ändern Datenbanktabelle müssen wir den Datensatz ändern, der überprüft, ob die richtigen Informationen in der Datenbank gespeichert werden. Das bedeutet, dass die Pflege dieser Datensätze viel Zeit in Anspruch nimmt und uns zu unnötiger Arbeit zwingt.

Datensätze richtig gemacht

Wir haben jetzt die drei häufigsten Fehler identifiziert, die Entwickler machen, wenn sie DbUnit-Datensätze verwenden. Jetzt ist es an der Zeit herauszufinden, wie wir diese Fehler vermeiden und Datensätze in unseren Tests effektiv nutzen können.

Schauen wir uns zunächst die Anforderungen an eine gute Testsuite genauer an. Die Anforderungen an eine gute Testsuite sind:

  • Es muss leicht zu lesen sein . Wenn unsere Testsuite einfach zu lesen ist, fungiert sie als Dokumentation, die immer auf dem neuesten Stand ist, und es ist schneller herauszufinden, was falsch ist, wenn ein Testfall fehlschlägt.
  • Es muss einfach zu warten sein . Eine einfach zu pflegende Testsuite spart uns viel Zeit, die wir produktiver nutzen können. Außerdem wird es uns wahrscheinlich viel Frust ersparen.
  • Es muss so schnell wie möglich gehen denn eine schnelle Testsuite sorgt für schnelles Feedback, und schnelles Feedback bedeutet, dass wir unsere Zeit produktiver nutzen können. Außerdem müssen wir verstehen, dass eine Integrationstestsuite zwar normalerweise viel langsamer als eine Einheitentestsuite ist, es jedoch keinen Sinn macht, diese Anforderung aufzugeben. Tatsächlich behaupte ich, dass wir dem mehr Aufmerksamkeit schenken müssen, denn wenn wir das tun, können wir die Ausführungszeit unserer Testsuite erheblich verkürzen.

Jetzt, da wir wissen, was die Anforderungen unserer Testsuite sind, ist es viel einfacher herauszufinden, wie wir sie erfüllen können, indem wir DbUnit-Datensätze verwenden.

Wenn wir diese Anforderungen erfüllen wollen, müssen wir uns an diese Regeln halten:

1. Verwenden Sie kleine Datensätze

Wir müssen kleine Datensätze verwenden, weil sie einfacher zu lesen sind und dafür sorgen, dass unsere Tests so schnell wie möglich sind. Mit anderen Worten, wir müssen die Mindestdatenmenge ermitteln, die zum Schreiben unserer Tests erforderlich ist, und nur diese Daten verwenden.

Beispiel:

Der Datensatz, der verwendet wird, um unsere Datenbank zu initialisieren, wenn wir kundenbezogene Funktionen testen, sieht wie folgt aus:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<cities id="1" name="Helsinki"/>
	<cities id="2" name="Tampere"/>
	
	<customers id="1" city_id="1" name="Company A"/>
	<customers id="2" city_id="2" name="Company B"/>
</dataset>

Auf der anderen Seite sieht der Datensatz, der unsere Datenbank initialisiert, wenn wir die Tests ausführen, die Office-bezogene Funktionen testen, wie folgt aus:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<cities id="1" name="Helsinki"/>
	<cities id="3" name="Turku"/>
	
	<offices id="1" city_id="1" name="Office A"/>
	<offices id="2" city_id="3" name="Office B"/>
</dataset>

Wenn wir uns die hervorgehobenen Zeilen ansehen, stellen wir fest, dass unsere Datensätze unterschiedliche Städte verwenden. Wir können dies beheben, indem wir den zweiten Datensatz so ändern, dass dieselben Städte wie im ersten Datensatz verwendet werden. Danach sieht der zweite Datensatz wie folgt aus:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<cities id="1" name="Helsinki"/>
	<cities id="2" name="Tampere"/>
	
	<offices id="1" city_id="1" name="Office A"/>
	<offices id="2" city_id="2" name="Office B"/>
</dataset>

Also, was ist der Punkt? Es mag den Anschein haben, dass wir nicht viel erreicht haben, aber wir konnten die Anzahl der verwendeten Städte von drei auf zwei reduzieren. Der Grund, warum diese kleine Verbesserung wertvoll ist, wird offensichtlich, wenn wir einen Blick auf die nächste Regel werfen.

2. Teilen Sie große Datensätze in kleinere Datensätze auf

Wir haben bereits zwei Datensätze erstellt, die die minimale Datenmenge enthalten, die zum Initialisieren unserer Datenbank erforderlich ist, bevor unsere Tests ausgeführt werden. Das Problem besteht darin, dass beide Datensätze "gemeinsame" Daten enthalten, was die Pflege unserer Datensätze erschwert.

Wir können dieses Problem beseitigen, indem wir diesen Schritten folgen:

  1. Identifizieren Sie die Daten, die in mehr als einem Datensatz verwendet werden.
  2. Verschieben Sie diese Daten in ein separates Dataset (oder in mehrere Datasets).

Beispiel:

Wir haben zwei Datensätze, die wie folgt aussehen (gemeinsame Daten sind hervorgehoben):

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<cities id="1" name="Helsinki"/>
	<cities id="2" name="Tampere"/>
	
	<customers id="1" city_id="1" name="Company A"/>
	<customers id="2" city_id="2" name="Company B"/>
</dataset>
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<cities id="1" name="Helsinki"/>
	<cities id="2" name="Tampere"/>
	
	<offices id="1" city_id="1" name="Office A"/>
	<offices id="2" city_id="2" name="Office B"/>
</dataset>

Wir können unser Wartungsproblem beseitigen, indem wir einen einzigen Datensatz erstellen, der die in die Städte eingefügten Informationen enthält Tisch. Nachdem wir dies getan haben, haben wir drei Datensätze, die wie folgt aussehen:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<cities id="1" name="Helsinki"/>
	<cities id="2" name="Tampere"/>
</dataset>
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<customers id="1" city_id="1" name="Company A"/>
	<customers id="2" city_id="2" name="Company B"/>
</dataset>
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<offices id="1" city_id="1" name="Office A"/>
	<offices id="2" city_id="2" name="Office B"/>
</dataset>

Was haben wir gerade gemacht?

Nun, die bedeutendste Verbesserung ist, wenn wir Änderungen an den Städten vornehmen Tabelle müssen wir diese Änderungen nur an einem Datensatz vornehmen. Mit anderen Worten, die Pflege dieser Datensätze ist viel einfacher als zuvor.

3. Bestätigen Sie nur die Informationen, die durch den getesteten Code geändert werden können

Zuvor haben wir uns einen Datensatz angesehen, der sicherstellt, dass die richtigen Informationen aus der verwendeten Datenbank gefunden werden, wenn wir die Informationen eines Kunden aktualisieren. Das Problem ist, dass der Datensatz Daten enthält, die durch den getesteten Code nicht geändert werden können. Das bedeutet:

  • Es ist schwierig, das erwartete Ergebnis herauszufinden, da unser Datensatz irrelevante Daten enthält.
  • Unsere Tests sind langsamer als sie sein könnten, weil sie irrelevante Behauptungen aufstellen müssen.
  • Unsere Tests sind schwer zu warten, denn wenn wir Änderungen an der Datenbank vornehmen, müssen wir die gleichen Änderungen auch an unseren Datensätzen vornehmen.

Wir können alle diese Probleme lösen, indem wir diese einfache Regel befolgen:

Wir müssen nur die Informationen bestätigen, die durch den getesteten Code geändert werden können.

Lassen Sie uns herausfinden, was diese Regel bedeutet.

Beispiel:

Zuvor haben wir einen (problematischen) Datensatz erstellt, der sicherstellt, dass die korrekten Informationen in der Datenbank gespeichert werden, wenn wir die Informationen eines Kunden aktualisieren (die ID des aktualisierten Kunden ist 2). Dieser Datensatz sieht wie folgt aus:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
	<cities id="1" name="Helsinki"/>
	<cities id="2" name="Tampere"/>
	
	<customers id="1" city_id="1" name="Company A"/>
	<customers id="2" city_id="1" name="Company B"/>
</dataset>

Wir können seine Probleme beheben, indem wir die wesentlichen Daten behalten und andere Daten entfernen. Wenn wir einen Test schreiben, der sicherstellt, dass die Informationen des richtigen Kunden in der Datenbank aktualisiert werden, ist es ziemlich offensichtlich, dass wir uns nicht um die Informationen kümmern, die aus den Städten gefunden werden Tisch. Das einzige, was uns interessiert, sind die Daten, die von den Kunden gefunden werden Tabelle.

Nachdem wir die irrelevanten Informationen aus unserem Datensatz entfernt haben, sieht es wie folgt aus:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>	
	<customers id="1" city_id="1" name="Company A"/>
	<customers id="2" city_id="1" name="Company B"/>
</dataset>

Wir haben jetzt die Leistungs- und Wartungsprobleme behoben, aber es gibt immer noch ein Problem:

Unser Datensatz hat zwei Zeilen und es ist nicht klar, welche Zeile die aktualisierten Informationen enthält. Dies ist kein großes Problem, da unser Datensatz eher klein ist, aber es kann zu einem Problem werden, wenn wir größere Datensätze verwenden. Wir können dieses Problem beheben, indem wir unserem Datensatz einen Kommentar hinzufügen.

Danach sieht unser Datensatz wie folgt aus:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>	
	<customers id="1" city_id="1" name="Company A"/>
	
	<!-- The information of the updated customer -->
	<customers id="2" city_id="1" name="Company B"/>
</dataset>

Viel besser. Richtig?

Zusammenfassung

Dieser Blogpost hat uns Folgendes gelehrt:

  • Der Weg zur Hölle ist mit guten Absichten gepflastert. Die drei häufigsten Fehler, die wir machen können, wenn wir DbUnit-Datensätze verwenden, scheinen eine gute Idee zu sein, aber wenn wir diese Fehler in einem realen Softwareprojekt machen, schießen wir uns selbst ins Knie.
  • Wir können die durch DbUnit-Datensätze verursachten Probleme vermeiden, indem wir kleine Datensätze verwenden, große Datensätze in kleinere Datensätze unterteilen und nur die Informationen behaupten, die durch getesteten Code geändert werden können.

Java-Tag