it-swarm.com.de

Unit-Tests und Datenbanken: Ab wann verbinde ich mich tatsächlich mit der Datenbank?

Es gibt Antworten auf die Frage, wie Testklassen, die eine Verbindung zu einer Datenbank herstellen, z. "Sollten Service-Testklassen eine Verbindung herstellen ..." und "Unit-Test - Datenbankgekoppelte App" .

Nehmen wir also kurz an, Sie haben eine Klasse A, die eine Verbindung zu einer Datenbank herstellen muss. Anstatt A tatsächlich verbinden zu lassen, stellen Sie A eine Schnittstelle zur Verfügung, über die A eine Verbindung herstellen kann. Zum Testen implementieren Sie diese Schnittstelle mit einigen Dingen - natürlich ohne Verbindung. Wenn Klasse B A instanziiert, muss sie eine "echte" Datenbankverbindung an A übergeben. Dies bedeutet jedoch, dass B eine Datenbankverbindung öffnet. Das heißt, um B zu testen, injizieren Sie die Verbindung in B. B wird jedoch in Klasse C usw. instanziiert.

An welcher Stelle muss ich also sagen "Hier rufe ich Daten aus einer Datenbank ab und schreibe keinen Komponententest für diesen Code"?

Mit anderen Worten: Irgendwo im Code einer Klasse muss I sqlDB.connect() oder ähnliches aufrufen. Wie teste ich diese Klasse?

Und ist es dasselbe mit Code, der sich mit einer GUI oder einem Dateisystem befassen muss?


Ich möchte Unit-Test machen. Jede andere Art von Test hat nichts mit meiner Frage zu tun. Ich weiß, dass ich nur eine Klasse damit testen werde (da stimme ich dir zu, Kilian). Jetzt muss eine Klasse eine Verbindung zu einer Datenbank herstellen. Wenn ich diese Klasse testen und fragen möchte, wie ich das mache, sagen viele: "Verwenden Sie die Abhängigkeitsinjektion!" Aber das verschiebt das Problem nur in eine andere Klasse, nicht wahr? Also frage ich, wie teste ich die Klasse, die wirklich, wirklich die Verbindung herstellt?

Bonusfrage: Einige Antworten hier beschränken sich auf "Verwenden Sie Scheinobjekte!" Was bedeutet das? Ich verspotte Klassen, von denen die zu testende Klasse abhängt. Soll ich mich jetzt über die zu testende Klasse lustig machen und sie tatsächlich testen (was der Idee der Verwendung von Vorlagenmethoden nahe kommt, siehe unten)?

37
TobiMcNamobi

Das Template Method Pattern könnte helfen.

Sie verpacken die Aufrufe einer Datenbank in protected Methoden. Um diese Klasse zu testen, testen Sie tatsächlich ein gefälschtes Objekt, das von der realen Datenbankverbindungsklasse erbt und die geschützten Methoden überschreibt.

Auf diese Weise werden die tatsächlichen Aufrufe der Datenbank niemals Unit-Tests unterzogen, das ist richtig. Aber es sind nur diese wenigen Codezeilen. Und das ist akzeptabel.

1
TobiMcNamobi

Der Sinn eines Komponententests besteht darin, eine Klasse zu testen (tatsächlich sollte er normalerweise eine Methode testen).

Dies bedeutet, dass Sie beim Testen der Klasse A eine Testdatenbank einfügen - etwas Selbstgeschriebenes oder eine blitzschnelle In-Memory-Datenbank, unabhängig davon, was die Aufgabe erledigt.

Wenn Sie jedoch die Klasse B testen, die ein Client von A ist, verspotten Sie normalerweise das gesamte A -Objekt mit etwas anderem, vermutlich etwas, das seine Aufgabe in a erfüllt primitiver, vorprogrammierter Weg - ohne Verwendung eines tatsächlichen A Objekts und sicherlich ohne Verwendung einer Datenbank (es sei denn, A übergibt die gesamte Datenbankverbindung an seinen Aufrufer zurück - aber das ist so schrecklich, dass ich es nicht tue Ich möchte nicht darüber nachdenken. Wenn Sie einen Komponententest für die Klasse C schreiben, die ein Client von B ist, verspotten Sie etwas, das die Rolle von B übernimmt, und vergessen A insgesamt.

Wenn Sie dies nicht tun, handelt es sich nicht mehr um einen Komponententest, sondern um einen System- oder Integrationstest. Das sind auch sehr wichtig, aber ein ganz anderer Fischkessel. Zunächst sind sie in der Regel aufwendiger einzurichten und auszuführen. Es ist nicht praktikabel, die Übergabe als Voraussetzung für das Einchecken usw. zu verlangen.

21
Kilian Foth

Das Durchführen von Komponententests für eine Datenbankverbindung ist völlig normal und eine gängige Praxis. Es ist einfach nicht möglich, einen purist -Ansatz zu erstellen, bei dem alles in Ihrem System abhängig injizierbar ist.

Der Schlüssel hier ist, gegen eine temporäre oder nur eine Testdatenbank zu testen und einen möglichst leichten Startprozess zum Erstellen dieser Testdatenbank zu haben.

Für Unit-Tests in CakePHP gibt es Dinge, die fixtures genannt werden. Fixtures sind temporäre Datenbanktabellen, die im laufenden Betrieb für einen Komponententest erstellt werden. Das Gerät verfügt über praktische Methoden zum Erstellen. Sie können ein Schema aus einer Produktionsdatenbank in der Testdatenbank neu erstellen oder das Schema mit einer einfachen Notation definieren.

Der Schlüssel zum Erfolg besteht darin, die Geschäftsdatenbank nicht zu implementieren, sondern sich nur auf den Aspekt des Codes zu konzentrieren, den Sie testen. Wenn Sie einen Komponententest haben, der überprüft, dass ein Datenmodell nur veröffentlichte Dokumente liest, sollte das Tabellenschema für diesen Test nur die für diesen Code erforderlichen Felder enthalten. Sie müssen nicht eine gesamte Content-Management-Datenbank erneut implementieren, um diesen Code zu testen.

Einige zusätzliche Referenzen.

http://en.wikipedia.org/wiki/Test_fixture

http://phpunit.de/manual/3.7/en/database.html

http://book.cakephp.org/2.0/en/development/testing.html#fixtures

12
Reactgular

Irgendwo in Ihrer Codebasis befindet sich eine Codezeile, die die eigentliche Aktion zum Herstellen einer Verbindung mit der Remote-Datenbank ausführt. Diese Codezeile ist neunmal in zehn Fällen ein Aufruf einer "integrierten" Methode, die von den für Ihre Sprache und Umgebung spezifischen Laufzeitbibliotheken bereitgestellt wird. Als solches ist es nicht "Ihr" Code und Sie müssen ihn nicht testen. Für die Zwecke eines Komponententests können Sie darauf vertrauen, dass dieser Methodenaufruf ordnungsgemäß ausgeführt wird. Was Sie in Ihrer Unit-Test-Suite noch testen können und sollten, sind beispielsweise die Sicherstellung, dass die für diesen Aufruf verwendeten Parameter Ihren Erwartungen entsprechen, z. B. die Richtigkeit der Verbindungszeichenfolge oder die SQL-Anweisung oder Name der gespeicherten Prozedur.

Dies ist einer der Gründe für die Einschränkung, dass Unit-Tests ihre Laufzeit-Sandbox nicht verlassen und vom externen Status abhängig sein dürfen. Es ist eigentlich ziemlich praktisch; Der Zweck eines Komponententests besteht darin, zu überprüfen, ob sich der von Ihnen geschriebene Code (oder in TDD zu schreiben) so verhält, wie Sie es sich vorgestellt haben. Code, den Sie nicht geschrieben haben, wie z. B. die Bibliothek, mit der Sie Ihre Datenbankoperationen ausführen, sollte aus dem einfachen Grund, dass Sie ihn nicht geschrieben haben, nicht Teil eines Komponententests sein.

In Ihrer Integrationstestsuite werden diese Einschränkungen gelockert. Jetzt können Sie Tests entwerfen, die die Datenbank berühren, um sicherzustellen, dass der von Ihnen geschriebene Code gut mit dem Code übereinstimmt, den Sie nicht geschrieben haben. Diese beiden Testsuiten sollten jedoch getrennt bleiben, da Ihre Unit-Test-Suite umso effektiver ist, je schneller sie ausgeführt wird (sodass Sie schnell überprüfen können, ob alle Aussagen von Entwicklern zu ihrem Code noch gültig sind), und fast per Definition ein Integrationstest ist aufgrund der zusätzlichen Abhängigkeiten von externen Ressourcen um Größenordnungen langsamer. Lassen Sie den Build-Bot alle paar Stunden Ihre vollständige Integrationssuite ausführen und die Tests ausführen, die externe Ressourcen sperren, damit sich die Entwickler nicht gegenseitig auf die Zehen treten, indem Sie dieselben Tests lokal ausführen. Und wenn der Build kaputt geht, was dann? Es wird viel mehr Wert darauf gelegt, dass der Build-Bot einen Build niemals fehlschlägt, als es wahrscheinlich sein sollte.


Wie genau Sie sich daran halten können, hängt von Ihrer genauen Strategie für die Verbindung zur Datenbank und die Abfrage der Datenbank ab. In vielen Fällen, in denen Sie das "Bare-Bones" -Datenzugriffsframework verwenden müssen, z. B. die SqlConnection- und SqlStatement-Objekte von ADO.NET, kann eine von Ihnen entwickelte gesamte Methode aus integrierten Methodenaufrufen und anderem Code bestehen, der von einem abhängig ist Datenbankverbindung, und das Beste, was Sie in dieser Situation tun können, ist, die gesamte Funktion zu verspotten und Ihren Integrationstestsuiten zu vertrauen. Dies hängt auch davon ab, wie bereit Sie sind, Ihre Klassen so zu gestalten, dass bestimmte Codezeilen zu Testzwecken ersetzt werden können (z. B. Tobis Vorschlag für das Muster der Vorlagenmethode, das gut ist, da es "partielle Verspottungen" ermöglicht, die einige davon ausüben Methoden einer realen Klasse, während andere überschrieben werden, die Nebenwirkungen haben).

Wenn Ihr Datenpersistenzmodell auf Code in Ihrer Datenschicht basiert (z. B. Trigger, gespeicherte Prozesse usw.), gibt es einfach keine andere Möglichkeit, den von Ihnen selbst geschriebenen Code auszuüben, als Tests zu entwickeln, die entweder innerhalb der Datenschicht leben oder die Datenschicht überschreiten Grenze zwischen Ihrer Anwendungslaufzeit und dem DBMS. Ein Purist würde sagen, dass dieses Muster aus diesem Grund zugunsten eines ORM vermieden werden sollte. Ich glaube nicht, dass ich so weit gehen würde; Selbst im Zeitalter sprachintegrierter Abfragen und anderer vom Compiler überprüfter, domänenabhängiger Persistenzoperationen sehe ich den Wert darin, die Datenbank nur für die Operationen zu sperren, die über gespeicherte Prozeduren verfügbar gemacht werden, und natürlich müssen solche gespeicherten Prozeduren mithilfe von Automatisierung überprüft werden Tests. Solche Tests sind jedoch keine Einheitentests . Sie sind Integrationstests .

Wenn Sie ein Problem mit dieser Unterscheidung haben, basiert dies normalerweise auf einer hohen Bedeutung, die einer vollständigen "Codeabdeckung" oder "Unit Test Coverage" beigemessen wird. Sie möchten sicherstellen, dass jede Zeile Ihres Codes von einem Komponententest abgedeckt wird. Ein edles Ziel im Gesicht, aber ich sage Hogwash; Diese Mentalität eignet sich für Anti-Muster, die weit über diesen speziellen Fall hinausgehen, z. B. das Schreiben von Tests ohne Behauptung, die Ihren Code ausführen , aber nicht (== --- ==) ausüben . Diese Arten von Endläufen, die ausschließlich der Deckungszahl dienen, sind schädlicher als die Lockerung Ihrer Mindestdeckung. Wenn Sie sicherstellen möchten, dass jede Zeile Ihrer Codebasis durch einen automatisierten Test ausgeführt wird, ist dies einfach. Berücksichtigen Sie bei der Berechnung der Kennzahlen für die Codeabdeckung die Integrationstests. Sie könnten sogar noch einen Schritt weiter gehen und diese umstrittenen "Itino" -Tests isolieren ("Integration nur im Namen"), und zwischen Ihrer Unit-Test-Suite und dieser Unterkategorie von Integrationstests (die immer noch relativ schnell laufen sollten) sollten Sie verdammt werden Nahezu nahezu vollständige Abdeckung.

4
KeithS

Unit-Tests sollten niemals eine Verbindung zu einer Datenbank herstellen. Per Definition sollten sie jeweils eine einzelne Codeeinheit (eine Methode) völlig isoliert vom Rest Ihres Systems testen. Wenn nicht, sind sie kein Komponententest.

Abgesehen von der Semantik gibt es eine Vielzahl von Gründen, warum dies von Vorteil ist:

  • Tests laufen um Größenordnungen schneller
  • Die Rückkopplungsschleife wird sofort (<1s Rückkopplung für TDD als Beispiel)
  • Tests können für Build/Deploy-Systeme parallel ausgeführt werden
  • Für Tests muss keine Datenbank ausgeführt werden (macht das Erstellen viel einfacher oder zumindest schneller).

Unit-Tests sind eine Möglichkeit, Ihre Arbeit zu überprüfen. Sie sollten alle Szenarien für eine bestimmte Methode skizzieren, was normalerweise alle unterschiedlichen Pfade durch eine Methode bedeutet. Es ist Ihre Spezifikation, nach der Sie bauen, ähnlich wie bei der doppelten Buchhaltung.

Was Sie beschreiben, ist eine andere Art von automatisiertem Test: ein Integrationstest. Während sie auch sehr wichtig sind, haben Sie im Idealfall viel weniger davon. Sie sollten überprüfen, ob eine Gruppe von Einheiten ordnungsgemäß miteinander integriert ist.

Wie testen Sie Dinge mit Datenbankzugriff? Ihr gesamter Datenzugriffscode sollte sich in einer bestimmten Schicht befinden, damit Ihr Anwendungscode mit verspottbaren Diensten anstelle der eigentlichen Datenbank interagieren kann. Es sollte keine Rolle spielen, ob diese Dienste von einer SQL-Datenbank, In-Memory-Testdaten oder sogar Remote-Webservice-Daten unterstützt werden. Das ist nicht ihre Sorge.

Idealerweise (und das ist sehr subjektiv) möchten Sie, dass der Großteil Ihres Codes durch Unit-Tests abgedeckt wird. Dies gibt Ihnen die Gewissheit, dass jedes Stück unabhängig arbeitet. Sobald die Teile gebaut sind, müssen Sie sie zusammensetzen. Beispiel - Wenn ich das Passwort des Benutzers hashe, sollte ich genau diese Ausgabe erhalten.

Angenommen, jede Komponente besteht aus ungefähr 5 Klassen. Sie möchten alle darin enthaltenen Fehlerstellen testen. Dies entspricht viel weniger Tests, nur um sicherzustellen, dass alles richtig verdrahtet ist. Beispiel - Test Sie können den Benutzer aus der Datenbank mit einem Benutzernamen/Passwort finden.

Schließlich möchten Sie einige Abnahmetests durchführen, um sicherzustellen, dass Sie die Geschäftsziele erreichen. Es gibt noch weniger davon; Sie stellen möglicherweise sicher, dass die Anwendung ausgeführt wird und das tut, wofür sie erstellt wurde. Beispiel - Angesichts dieser Testdaten sollte ich mich anmelden können.

Stellen Sie sich diese drei Arten von Tests als Pyramide vor. Sie benötigen viele Komponententests, um alles zu unterstützen, und arbeiten sich dann von dort aus nach oben.

2