Es gibt Antworten auf die Frage, wie Testklassen eine Verbindung zu einer Datenbank herstellen, z. B. "Sollte Service-Testklassen eine Verbindung herstellen ..." und "Komponententest - Datenbankgekoppelte App" .
Nehmen wir also kurz an, Sie haben eine Klasse A, die eine Verbindung zu einer Datenbank herstellen muss. Anstatt A eine Verbindung herstellen zu lassen, stellen Sie A eine Schnittstelle zur Verfügung, über die A eine Verbindung herstellen kann. Zum Testen implementieren Sie diese Schnittstelle mit ein paar 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 bedeutet, dass Sie zum Testen von B die Verbindung in B einspeisen. B wird jedoch in Klasse C und so weiter instanziiert.
Ab wann muss ich also sagen, dass ich hier Daten aus einer Datenbank abrufe und keinen Komponententest für diesen Code schreibe?
Mit anderen Worten: Irgendwo im Code einer Klasse muss ich mich melden sqlDB.connect()
oder ähnliches. Wie teste ich diese Klasse?
Und ist es dasselbe mit Code, der mit einer GUI oder einem Dateisystem zu tun hat?
Ich möchte einen 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: "Use Dependency Injection!" Aber das verschiebt das Problem nur auf eine andere Klasse, nicht wahr? Also frage ich, wie teste ich die Klasse, die wirklich die Verbindung herstellt?
Bonusfrage: Einige Antworten beschränken sich auf "Use mock objects!" Was bedeutet das? Ich verspotte Klassen, von denen die getesteten Klassen abhängen. Soll ich den Prüfling jetzt verspotten und ihn tatsächlich testen (was der Idee der Verwendung von Vorlagenmethoden nahekommt, siehe unten)?
quelle
Antworten:
Der Zweck eines Komponententests besteht darin, eine Klasse zu testen (in der Regel sollte eine Methode getestet werden ).
Dies bedeutet, dass Sie beim Testen einer Klasse
A
eine Testdatenbank in die Testdatenbank einfügen - eine selbstgeschriebene oder eine blitzschnelle speicherinterne Datenbank, unabhängig davon, welche Aufgaben erledigt werden.Wenn Sie jedoch eine Klasse testen
B
, bei der es sich um einen Client handeltA
, verspotten Sie normalerweise das gesamteA
Objekt mit etwas anderem, vermutlich etwas, das seine Aufgabe primitiv und vorprogrammiert erledigt - ohne Verwendung eines tatsächlichenA
Objekts und mit Sicherheit ohne Verwendung von Daten base (es sei denn,A
die gesamte Datenbankverbindung wird an den Anrufer zurückgegeben - aber das ist so schrecklich, dass ich nicht darüber nachdenken möchte). Wenn Sie einen Komponententest für eine Klasse schreibenC
, die ein Kunde von istB
, verspotten Sie ebenfalls etwas, das die Rolle von übernimmtB
, und vergessen esA
insgesamt.Wenn Sie das nicht tun, handelt es sich nicht mehr um einen Komponententest, sondern um einen System- oder Integrationstest. Das ist auch sehr wichtig, aber ein ganz anderer Fischkessel. Zunächst sind sie in der Regel aufwändiger einzurichten und auszuführen. Es ist nicht praktikabel, die Weitergabe dieser Informationen als Voraussetzung für das Einchecken usw. zu verlangen.
quelle
Unit-Tests für eine Datenbankverbindung durchzuführen, 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 in Abhängigkeit injizierbar ist.Der Schlüssel besteht darin, eine temporäre oder eine reine Testdatenbank zu testen und einen möglichst einfachen Startprozess für die Erstellung dieser Testdatenbank zu haben.
Für Unit-Tests in CakePHP gibt es Dinge, die man nennt
fixtures
. Fixtures sind temporäre Datenbanktabellen, die im laufenden Betrieb für einen Unit-Test 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, nicht die Geschäftsdatenbank zu implementieren, sondern sich nur auf den Aspekt des Codes zu konzentrieren, den Sie testen. Wenn Sie über einen Komponententest verfügen, der bestätigt, dass ein Datenmodell nur veröffentlichte Dokumente liest, sollte das Tabellenschema für diesen Test nur die Felder enthalten, die für diesen Code erforderlich sind. Sie müssen nicht die 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
quelle
Irgendwo in Ihrer Codebasis befindet sich eine Codezeile, die die eigentliche Aktion zum Herstellen einer Verbindung mit der entfernten Datenbank ausführt. Diese Codezeile ist 9-mal in 10 ein Aufruf einer "eingebauten" 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-Testsuite noch testen können und sollten, sind Dinge wie die Sicherstellung, dass die Parameter, die für diesen Aufruf verwendet werden, 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 Komponententests ihre Laufzeit- "Sandbox" nicht verlassen dürfen und vom externen Status abhängig sind. Es ist eigentlich ganz praktisch; Mit einem Komponententest soll überprüft werden, ob sich der von Ihnen geschriebene Code (oder der Code , der gerade in TDD geschrieben wird) 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 Bestandteil eines Komponententests sein.
In Ihrer Integrationstestsuite werden diese Einschränkungen gelockert. Jetzt Sie könnenDesigntests, die die Datenbank berühren, um sicherzustellen, dass der Code, den Sie geschrieben haben, gut mit Code zusammenarbeitet, den Sie nicht geschrieben haben. Diese beiden Testsuiten sollten jedoch getrennt bleiben, da Ihre Komponententestsuite umso effektiver ist, je schneller sie ausgeführt wird (sodass Sie schnell überprüfen können, ob alle Aussagen der Entwickler zu ihrem Code noch gültig sind) ist aufgrund der zusätzlichen Abhängigkeit 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, mit denen externe Ressourcen gesperrt werden, damit die Entwickler sich nicht gegenseitig auf die Nerven gehen, 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 niemals einen Build verfehlt, als es wahrscheinlich sein sollte.
Wie genau Sie sich nun daran halten können, hängt von Ihrer genauen Strategie ab, wie Sie eine Verbindung zur Datenbank herstellen und diese abfragen. In vielen Fällen, in denen Sie das "Bare-Bones" -Datenzugriffsframework verwenden müssen, z. B. bei den ADO.NET-Objekten SqlConnection und SqlStatement, kann eine von Ihnen entwickelte Methode aus integrierten Methodenaufrufen und anderem Code bestehen, der von a abhängig ist Datenbankverbindung. Das Beste, was Sie in dieser Situation tun können, ist, die gesamte Funktion zu verspotten und Ihren Integrationstestsuiten zu vertrauen. Es hängt auch davon ab, wie weit Sie bereit sind, Ihre Klassen so zu entwerfen, dass bestimmte Codezeilen zu Testzwecken ersetzt werden können (z. B. Tobis Vorschlag für das Template-Methodenmuster, das gut ist, weil es "partielle Verspottungen" ermöglicht).
Wenn sich Ihr Datenpersistenzmodell auf Code in Ihrer Datenschicht stützt (z. B. Trigger, gespeicherte Prozesse usw.), gibt es einfach keine andere Möglichkeit, Code auszuüben, den Sie selbst schreiben, als Tests zu entwickeln, die entweder in der Datenschicht ablaufen oder die Datenschicht durchqueren 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 kommen würde. Selbst im Zeitalter sprachintegrierter Abfragen und anderer vom Compiler überprüfter, domänenabhängiger Persistenzvorgänge sehe ich den Wert darin, die Datenbank nur auf die Vorgänge zu beschränken, die über gespeicherte Prozeduren verfügbar gemacht werden, und natürlich müssen solche gespeicherten Prozeduren automatisiert überprüft werden Tests. Aber solche Tests sind nicht Einheit Tests. Sie sind Integration Tests.
Wenn Sie ein Problem mit dieser Unterscheidung haben, liegt dies in der Regel an der hohen Bedeutung einer vollständigen "Codeabdeckung", auch bekannt als "Einheitentestabdeckung". Sie möchten sicherstellen, dass jede Codezeile von einem Komponententest abgedeckt wird. Ein edles Ziel im Gesicht, aber ich sage Hogwash; Diese Mentalität eignet sich für Anti-Patterns, die weit über diesen speziellen Fall hinausreichen, wie das Schreiben von aussagefreien Tests, die ausgeführt werden, aber keine Übungen ausführendein Code. Diese Arten von End-Runs, die ausschließlich der Deckung 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 ganz einfach. Berücksichtigen Sie beim Berechnen von Kennzahlen zur Codeabdeckung die Integrationstests. Sie könnten sogar noch einen Schritt weiter gehen und diese umstrittenen "Itino" -Tests ("Integration nur in Namen") isolieren, und zwischen Ihrer Unit-Testsuite und dieser Unterkategorie von Integrationstests (die immer noch relativ schnell ablaufen sollten) sollten Sie verdammt sein Nahezu vollständige Abdeckung.
quelle
Unit-Tests sollten niemals eine Verbindung zu einer Datenbank herstellen. Per Definition sollten sie jeweils eine einzelne Codeeinheit (eine Methode) vollständig isoliert vom Rest Ihres Systems testen. Wenn dies nicht der Fall ist, handelt es sich nicht um einen Komponententest.
Abgesehen von der Semantik gibt es eine Vielzahl von Gründen, warum dies von Vorteil ist:
Unit-Tests sind eine Möglichkeit, Ihre Arbeit zu überprüfen. Sie sollten alle Szenarien für eine bestimmte Methode skizzieren, dh normalerweise alle unterschiedlichen Pfade durch eine Methode. Es ist Ihre Spezifikation, auf die Sie aufbauen, ähnlich wie bei der doppelten Buchführung.
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äß ineinander integriert ist.
Wie testen Sie Dinge mit Datenbankzugriff? Der gesamte Datenzugriffscode sollte sich auf einer bestimmten Ebene befinden, damit der Anwendungscode mit nachahmbaren Diensten anstelle der eigentlichen Datenbank interagieren kann. Es sollte egal sein, ob diese Dienste von einer SQL-Datenbank, In-Memory-Testdaten oder sogar von Remote-Webservice-Daten unterstützt werden. Das ist nicht ihre Sorge.
Im Idealfall (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 Stücke gebaut sind, müssen Sie sie zusammenfügen. 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 Fehlerquellen testen. Dies entspricht viel weniger Tests, nur um sicherzustellen, dass alles richtig verdrahtet ist. Beispiel - Test können Sie 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 einloggen können.
Stellen Sie sich diese drei Arten von Tests als Pyramide vor. Sie brauchen eine Menge Unit-Tests, um alles zu unterstützen, und dann arbeiten Sie sich von dort nach oben.
quelle
Das Template Method Pattern könnte helfen.
Sie verpacken die Aufrufe einer Datenbank in
protected
Methoden. Um diese Klasse zu testen, testen Sie ein falsches Objekt, das von der realen Datenbankverbindungsklasse erbt und die geschützten Methoden überschreibt.Auf diese Weise werden die tatsächlichen Aufrufe der Datenbank niemals einem Komponententest unterzogen, das ist richtig. Aber es sind nur diese wenigen Codezeilen. Und das ist akzeptabel.
quelle
Testen mit externen Daten ist ein Integrationstest. Einheitentest bedeutet, dass Sie nur die Einheit testen. Dies geschieht hauptsächlich mit Ihrer Geschäftslogik. Um Ihre Code-Einheit testbar zu machen, müssen Sie einige Richtlinien befolgen, z. B. müssen Sie Ihre Einheit von anderen Teilen Ihres Codes unabhängig machen. Wenn Sie während des Komponententests Daten benötigen, müssen Sie diese Daten mit Abhängigkeitsinjektion erzwingen. Es gibt einige spöttische und stumpfe Rahmenbedingungen da draußen.
quelle