Unit Testing - Datenbank gekoppelte App

15

Was wäre der beste Ansatz beim Unit-Testen eines Modells, das in eine Anwendung integriert ist, die eng an die Datenbank gekoppelt ist?

Das spezielle Szenario hier ist ein Einkaufswagen - ich möchte das Hinzufügen, Entfernen und Abrufen von Artikeln aus dem Einkaufswagen sowie die Preislogik usw. testen können. Dies alles erfordert in meinem Kopf Datenbankzugriff, obwohl ich das mehrmals gelesen habe Datenbankzugriff sollte vermieden werden.

user1189880
quelle
1
Interessant, dass die Antworten, die effektiv "Schreibe deinen App-Code um" sagen,
positiv bewertet

Antworten:

10

Die Abhängigkeitsinjektion ist eine Möglichkeit, dies zu handhaben. Sie können eine Testdatenbank einrichten, um den Einkaufswagen nachzuahmen, oder Sie können sogar einen Code schreiben, der die Transaktion des Kunden "bestätigt". Anschließend wählt Ihre Software zur Laufzeit die Komponente aus, zu der eine Verbindung hergestellt werden soll.

Stellen Sie während des Testens einfach keine Verbindung zur Produktionsdatenbank her!

Chrisaycock
quelle
1
Mit DI und ordnungsgemäßem Anwendungsdesign sollten Sie in der Lage sein, ohne Datenbank zu testen - vorausgesetzt, der von Ihnen injizierte Mock bietet ausreichend detaillierte Informationen zum Mocking der Back-End-Datenbank.
Peter K.
4

Im Komponententest müssen Sie die Grenze dessen definieren, was Sie testen. Unit-Tests unterscheiden sich von Integrationstests. Wenn die Preislogik unabhängig vom Inhalt des Warenkorbs ist, testen Sie dies separat. Wenn dies nicht der Fall ist und alle Module eng miteinander verbunden sind, erstellen Sie eine Testumgebung, die die Produktion so gut wie möglich nachahmt, und arbeiten Sie damit. Ich glaube nicht, dass Abkürzungen und Simulationen langfristig helfen.

Keine Chance
quelle
2

Das Modell sollte nicht von einer (konkreten) DB abhängen. Wenn es nur einen abstrakten DB (read "interface") kennt, der an das Modell übergeben wird, können Sie den DB durch ein Scheinobjekt ersetzen .

In der objektorientierten Programmierung sind Scheinobjekte simulierte Objekte, die das Verhalten realer Objekte auf kontrollierte Weise nachahmen. Ein Programmierer erstellt normalerweise ein Scheinobjekt, um das Verhalten eines anderen Objekts zu testen, ähnlich wie ein Autodesigner einen Crashtest-Dummy verwendet , um das dynamische Verhalten eines Menschen bei Fahrzeugaufprallen zu simulieren.

EricSchaefer
quelle
1

Ich hatte ein ähnliches Problem - ich hatte keine Möglichkeit zu garantieren, dass meine Test-DB die Werte beibehält. So bekomme ich in Zukunft zB andere Preise.

Ich extrahierte die Daten , die ich in eine kleine benötigt SQLite - DB und DB verwendet , um diese für meine Tests. Die Test-DB ist nun Teil des Setups meines Unit-Tests.

knut
quelle
2
Bei Unit-Tests wird der Code isoliert getestet. Wenn Sie eine SQLLite-Datenbank verwenden, ist dies nicht isoliert. Auch Inkonsistenzen zwischen Datenbanken können Fehler verursachen
Tom Squires
0

"Best" ist subjektiv, aber Sie könnten einfach eine Test-DB-Verbindung verwenden.

Laden Sie mit Fixtures einige Testdaten (Beispielprodukte zum Kaufen) und schreiben Sie dann den Testfall für die Klasse / Funktion, die Sie testen möchten.

AD7six
quelle
Unit-Tests zu beschreiben, die eine Funktion testen, die als Integrationstests auf eine Datenbank einwirkt, ist @murph irreführend.
AD7six
1
Ok, jetzt bin ich zutiefst verwirrt - wenn es sich um eine Datenbank handelt, ist dies per Definition kein Komponententest, da sie nicht in sich geschlossen ist. Wenn Sie über eine Datenbank verfügen, führen Sie Tests auf einer höheren Ebene durch, bei denen es um Abhängigkeiten geht, bei denen es darum geht, Dinge zu "kombinieren". Ungeachtet dessen ist dies für mich keine klare Erklärung, wie das Problem zu lösen ist.
Murph
0

Ich habe ein Plugin für Symfony 1.4 (PHP) erstellt , um dieses Problem zu lösen (unter anderem). Es orientiert sich an der Funktionsweise von Djangos Test-Framework (Python) : Das Framework erstellt und füllt eine separate Testdatenbank vor jedem Teststart und zerstört die Testdatenbank nach Abschluss jedes Tests.

Ich hatte ein paar Bedenken hinsichtlich dieser Strategie, sowohl hinsichtlich der Leistung (wenn sich das Schema nicht ändert, warum nicht einfach die Daten löschen, anstatt die gesamte Struktur neu zu erstellen?) Als auch hinsichtlich der Benutzerfreundlichkeit (manchmal möchte ich die Datenbank nach einer Überprüfung überprüfen) Test fehlgeschlagen, also nicht wahllos zerstören!), also habe ich einen etwas anderen Ansatz gewählt.

Vor den ersten Testläufen wird die Datenbank zerstört und neu erstellt, falls seit dem letzten Test Modelländerungen aufgetreten sind. Vor jedem weiteren Testlauf werden die Daten in der Datenbank gelöscht, aber die Struktur wird nicht neu erstellt (obwohl bei Bedarf eine manuelle Neuerstellung durch einen Test ausgelöst werden kann).

Durch selektives Laden von Daten-Fixtures in jeden Test kann die richtige Umgebung für diesen Test erstellt werden, ohne nachfolgende Tests zu beeinträchtigen. Fixture-Dateien können auch wiederverwendet werden, was diese Aufgabe viel weniger lästig macht (obwohl es immer noch mein am wenigsten favorisierter Teil beim Schreiben von Tests ist!).

In beiden Test-Frameworks ist der Datenbankadapter so konfiguriert, dass die Testverbindung anstelle der "Produktions" -Verbindung verwendet wird, um zu verhindern, dass die Testausführung vorhandene Daten beschädigt.


quelle
0

Ich würde sagen, gehen Sie einfach voran und verwenden Sie Fixtures, um die Daten vorab zu laden. So scheinen Unit-Test-Frameworks im Allgemeinen zu funktionieren, wenn Sie die Manipulation von Daten testen.

Aber wenn Sie wirklich vermeiden möchten, eine Verbindung zu einer Datenbank jeglicher Art herstellen zu müssen, und die allzu strenge Definition befolgen möchten, dass Komponententests nichts außerhalb des Codes berühren, sollten Sie sich die Objektverspottung ansehen - sie kann Ihnen Ideen geben.

Anstatt den SQL-Code direkt an der Stelle abzulegen, an der Sie ihn benötigen, können Sie beispielsweise eine Methode aufrufen, die nur die Aufgaben dieses SQL-Codes ausführt. Verwenden Sie Person.getPhoneNumber()zum Beispiel anstelle von SELECT phone_number FROM person WHERE id = <foo>. Es ist nicht nur auf einen Blick übersichtlicher und verständlicher, sondern Sie können während des Testens das Person-Objekt verspotten, sodass getPhoneNumber()es immer zurückkehrt 555-555-5555oder so, anstatt die Datenbank zu berühren.

Izkata
quelle
0

Dies ist ziemlich einfach mit junit zu tun, wenn ein wenig langatmig.

Das "Setup" sollte einen Satz temporärer Tabellen definieren und füllen.

Anschließend können Sie die Komponententests für alle Funktionen zum Aktualisieren, Einfügen und Löschen durchführen.

Rufen Sie für jeden Test, den Sie aufrufen, Ihre Aktualisierungsmethode auf, und führen Sie dann SQL aus, um das erwartete Ergebnis zu überprüfen.

In der "Teardown" -Phase lassen Sie alle Tabellen fallen.

Auf diese Weise führen Sie immer dieselben Tests mit denselben Anfangsdaten durch. Wenn Sie die Tabellen zwischen den Tests beibehalten, werden sie durch fehlgeschlagene Tests "verunreinigt". Außerdem ist ein konsistenter "Einfügetest" fast unmöglich, da Sie bei jedem Test immer wieder neue Schlüssel erfinden müssen.

James Anderson
quelle