Ich würde vorschlagen, Ihre Anrufe in die Datenbank zu verspotten. Mocks sind im Grunde genommen Objekte, die wie das Objekt aussehen, für das Sie eine Methode aufrufen möchten, in dem Sinne, dass sie dieselben Eigenschaften, Methoden usw. haben, die dem Aufrufer zur Verfügung stehen. Aber anstatt die Aktion auszuführen, für die sie programmiert sind, wenn eine bestimmte Methode aufgerufen wird, überspringt sie diese insgesamt und gibt nur ein Ergebnis zurück. Dieses Ergebnis wird normalerweise von Ihnen im Voraus definiert.
Um Ihre Objekte für das Verspotten einzurichten, müssen Sie wahrscheinlich eine Art Inversion des Kontroll- / Abhängigkeitsinjektionsmusters verwenden, wie im folgenden Pseudocode:
class Bar
{
private FooDataProvider _dataProvider;
public instantiate(FooDataProvider dataProvider) {
_dataProvider = dataProvider;
}
public getAllFoos() {
// instead of calling Foo.GetAll() here, we are introducing an extra layer of abstraction
return _dataProvider.GetAllFoos();
}
}
class FooDataProvider
{
public Foo[] GetAllFoos() {
return Foo.GetAll();
}
}
Jetzt erstellen Sie in Ihrem Komponententest ein Modell von FooDataProvider, mit dem Sie die Methode GetAllFoos aufrufen können, ohne die Datenbank tatsächlich aufrufen zu müssen.
class BarTests
{
public TestGetAllFoos() {
// here we set up our mock FooDataProvider
mockRepository = MockingFramework.new()
mockFooDataProvider = mockRepository.CreateMockOfType(FooDataProvider);
// create a new array of Foo objects
testFooArray = new Foo[] {Foo.new(), Foo.new(), Foo.new()}
// the next statement will cause testFooArray to be returned every time we call FooDAtaProvider.GetAllFoos,
// instead of calling to the database and returning whatever is in there
// ExpectCallTo and Returns are methods provided by our imaginary mocking framework
ExpectCallTo(mockFooDataProvider.GetAllFoos).Returns(testFooArray)
// now begins our actual unit test
testBar = new Bar(mockFooDataProvider)
baz = testBar.GetAllFoos()
// baz should now equal the testFooArray object we created earlier
Assert.AreEqual(3, baz.length)
}
}
Kurz gesagt, ein häufiges Spott-Szenario. Natürlich möchten Sie wahrscheinlich auch weiterhin Ihre tatsächlichen Datenbankaufrufe testen, für die Sie die Datenbank aufrufen müssen.
Im Idealfall sollten Ihre Objekte dauerhaft unwissend sein. Zum Beispiel sollten Sie eine "Datenzugriffsschicht" haben, an die Sie Anforderungen stellen würden, die Objekte zurückgeben würde. Auf diese Weise können Sie diesen Teil aus Ihren Komponententests herauslassen oder isoliert testen.
Wenn Ihre Objekte eng mit Ihrer Datenschicht verbunden sind, ist es schwierig, ordnungsgemäße Unit-Tests durchzuführen. Der erste Teil des Komponententests ist "Einheit". Alle Geräte sollten isoliert getestet werden können.
In meinen c # -Projekten verwende ich NHibernate mit einer vollständig separaten Datenschicht. Meine Objekte befinden sich im Kerndomänenmodell und werden von meiner Anwendungsschicht aus aufgerufen. Die Anwendungsschicht spricht sowohl mit der Datenschicht als auch mit der Domänenmodellschicht.
Die Anwendungsschicht wird manchmal auch als "Geschäftsschicht" bezeichnet.
Wenn Sie PHP verwenden, erstellen Sie einen bestimmten Satz von Klassen für NUR den Datenzugriff. Stellen Sie sicher, dass Ihre Objekte keine Ahnung haben, wie sie beibehalten werden, und verbinden Sie die beiden in Ihren Anwendungsklassen.
Eine andere Möglichkeit wäre die Verwendung von Mocking / Stubs.
quelle
Der einfachste Weg, ein Objekt mit Datenbankzugriff einem Komponententest zu unterziehen, ist die Verwendung von Transaktionsbereichen.
Beispielsweise:
Dadurch wird der Status der Datenbank zurückgesetzt, im Grunde wie bei einem Transaktions-Rollback, sodass Sie den Test so oft ausführen können, wie Sie möchten, ohne dass Nebenwirkungen auftreten. Wir haben diesen Ansatz erfolgreich in großen Projekten eingesetzt. Unser Build dauert zwar etwas lange (15 Minuten), aber es ist nicht schrecklich, 1800 Unit-Tests durchzuführen. Wenn die Build-Zeit ein Problem darstellt, können Sie den Build-Prozess so ändern, dass mehrere Builds vorhanden sind, einer zum Erstellen von src und einer, der anschließend gestartet wird und Unit-Tests, Code-Analyse, Verpackung usw. übernimmt.
quelle
Ich kann Ihnen vielleicht einen Vorgeschmack auf unsere Erfahrungen geben, als wir uns mit Unit-Tests für unseren Middle-Tier-Prozess befassten, der eine Menge SQL-Operationen mit "Geschäftslogik" umfasste.
Wir haben zuerst eine Abstraktionsschicht erstellt, mit der wir jede vernünftige Datenbankverbindung "einstecken" konnten (in unserem Fall haben wir einfach eine einzelne ODBC-Verbindung unterstützt).
Sobald dies geschehen war, konnten wir so etwas in unserem Code tun (wir arbeiten in C ++, aber ich bin sicher, Sie haben die Idee):
GetDatabase (). ExecuteSQL ("INSERT INTO foo (bla, bla)")
Zur normalen Laufzeit würde GetDatabase () ein Objekt, das alle unsere SQL-Daten (einschließlich Abfragen) über ODBC eingespeist hat, direkt an die Datenbank zurückgeben.
Wir haben uns dann mit In-Memory-Datenbanken befasst - das mit Abstand beste scheint SQLite zu sein. ( http://www.sqlite.org/index.html ). Es ist bemerkenswert einfach einzurichten und zu verwenden und ermöglicht es uns, GetDatabase () in Unterklassen zu überschreiben und zu überschreiben, um SQL an eine speicherinterne Datenbank weiterzuleiten, die für jeden durchgeführten Test erstellt und zerstört wurde.
Wir befinden uns noch im Anfangsstadium, aber es sieht bisher gut aus. Wir müssen jedoch sicherstellen, dass wir alle erforderlichen Tabellen erstellen und sie mit Testdaten füllen. Wir haben jedoch die Arbeitslast hier durch Erstellen etwas reduziert Ein allgemeiner Satz von Hilfsfunktionen, die all dies für uns erledigen können.
Insgesamt hat dies bei unserem TDD-Prozess immens geholfen, da das Vornehmen von scheinbar harmlosen Änderungen zur Behebung bestimmter Fehler seltsame Auswirkungen auf andere (schwer zu erkennende) Bereiche Ihres Systems haben kann - aufgrund der Natur von SQL / Datenbanken.
Natürlich haben sich unsere Erfahrungen auf eine C ++ - Entwicklungsumgebung konzentriert, aber ich bin sicher, dass Sie unter PHP / Python vielleicht etwas Ähnliches zum Laufen bringen könnten.
Hoffe das hilft.
quelle
Sie sollten den Datenbankzugriff verspotten, wenn Sie Ihre Klassen einem Komponententest unterziehen möchten. Schließlich möchten Sie die Datenbank nicht in einem Komponententest testen. Das wäre ein Integrationstest.
Abstrakt die Anrufe weg und füge dann einen Mock ein, der nur die erwarteten Daten zurückgibt. Wenn Ihre Klassen nicht mehr als nur Abfragen ausführen, lohnt es sich möglicherweise nicht einmal, sie zu testen ...
quelle
Das Buch xUnit Test Patterns beschreibt einige Möglichkeiten, um mit Unit-Testing-Code umzugehen, der auf eine Datenbank trifft. Ich stimme den anderen Leuten zu, die sagen, dass Sie dies nicht tun wollen, weil es langsam ist, aber Sie müssen es irgendwann tun, IMO. Es ist eine gute Idee, die Datenbankverbindung zu verspotten, um übergeordnete Inhalte zu testen. In diesem Buch finden Sie jedoch Vorschläge zu Maßnahmen, die Sie zur Interaktion mit der eigentlichen Datenbank ergreifen können.
quelle
Optionen, die Sie haben:
Injizieren Sie die Datenbank. (Beispiel in Pseudo-Java, gilt aber für alle OO-Sprachen)
Jetzt verwenden Sie in der Produktion die normale Datenbank und fügen für alle Tests nur die Scheindatenbank ein, die Sie ad hoc erstellen können.User
anstelle eines Tupels zurückgibt{name: "marcin", password: "blah"}
). Schreiben Sie alle Ihre Tests mit ad hoc erstellten realen Objekten und schreiben Sie einen großen Test, der von einer Datenbank abhängt, die diese Konvertierung sicherstellt funktioniert OK.Natürlich schließen sich diese Ansätze nicht gegenseitig aus, und Sie können sie nach Bedarf mischen und anpassen.
quelle
Das Unit-Testen Ihres Datenbankzugriffs ist einfach genug, wenn Ihr Projekt durchgehend einen hohen Zusammenhalt und eine lose Kopplung aufweist. Auf diese Weise können Sie nur die Dinge testen, die jede bestimmte Klasse tut, ohne alles auf einmal testen zu müssen.
Wenn Sie beispielsweise Ihre Benutzeroberflächenklasse einem Komponententest unterziehen, sollten die von Ihnen geschriebenen Tests nur versuchen, zu überprüfen, ob die Logik in der Benutzeroberfläche wie erwartet funktioniert hat, nicht die Geschäftslogik oder Datenbankaktion, die hinter dieser Funktion steht.
Wenn Sie den tatsächlichen Datenbankzugriff einem Komponententest unterziehen möchten, erhalten Sie tatsächlich eher einen Integrationstest, da Sie vom Netzwerkstapel und Ihrem Datenbankserver abhängig sind. Sie können jedoch überprüfen, ob Ihr SQL-Code die Anforderungen erfüllt machen.
Die verborgene Kraft von Unit-Tests für mich persönlich war, dass ich gezwungen bin, meine Anwendungen viel besser zu gestalten, als ich es ohne sie tun könnte. Dies liegt daran, dass es mir wirklich geholfen hat, mich von der Mentalität "Diese Funktion sollte alles tun" zu lösen.
Leider habe ich keine spezifischen Codebeispiele für PHP / Python, aber wenn Sie ein .NET-Beispiel sehen möchten, habe ich einen Beitrag , der eine Technik beschreibt, mit der ich genau diese Tests durchgeführt habe.
quelle
Normalerweise versuche ich, meine Tests zwischen dem Testen der Objekte (und gegebenenfalls von ORM) und dem Testen der Datenbank aufzuteilen. Ich teste die Objektseite der Dinge, indem ich die Datenzugriffsaufrufe verspotte, während ich die Datenbankseite der Dinge teste, indem ich die Objektinteraktionen mit der Datenbank teste, die meiner Erfahrung nach normalerweise ziemlich begrenzt sind.
Früher war ich frustriert, Unit-Tests zu schreiben, bis ich anfing, den Datenzugriffsteil zu verspotten, damit ich keine Testdatenbank erstellen oder Testdaten im laufenden Betrieb generieren musste. Durch Verspotten der Daten können Sie alles zur Laufzeit generieren und sicherstellen, dass Ihre Objekte mit bekannten Eingaben ordnungsgemäß funktionieren.
quelle
Ich habe dies noch nie in PHP getan und Python noch nie verwendet, aber Sie möchten die Aufrufe der Datenbank verspotten. Zu diesem Zweck können Sie ein IoC implementieren, unabhängig davon, ob es sich um ein Tool eines Drittanbieters handelt, oder Sie verwalten es selbst. Anschließend können Sie eine Scheinversion des Datenbankaufrufers implementieren, mit der Sie das Ergebnis dieses gefälschten Aufrufs steuern können.
Eine einfache Form der IoC kann nur durch Codierung in Schnittstellen ausgeführt werden. Dies erfordert eine Art Objektorientierung in Ihrem Code, sodass sie möglicherweise nicht für Ihre Arbeit gilt (ich sage, da ich nur PHP und Python erwähnen muss).
Ich hoffe, das ist hilfreich, wenn Sie sonst nichts zu suchen haben.
quelle
Ich bin damit einverstanden, dass der erste Zugriff nach der Datenbank auf eine DAO-Schicht reduziert werden sollte, die eine Schnittstelle implementiert. Anschließend können Sie Ihre Logik anhand einer Stub-Implementierung der DAO-Schicht testen.
quelle
Sie können Mocking-Frameworks verwenden , um das Datenbankmodul zu abstrahieren. Ich weiß nicht, ob PHP / Python welche hat, aber für getippte Sprachen (C #, Java usw.) gibt es viele Möglichkeiten
Dies hängt auch davon ab, wie Sie diesen Datenbankzugriffscode entworfen haben, da einige Designs einfacher zu testen sind als andere, wie in den früheren Beiträgen erwähnt.
quelle
Das Einrichten von Testdaten für Unit-Tests kann eine Herausforderung sein.
Wenn Sie in Java Spring-APIs zum Testen von Einheiten verwenden, können Sie die Transaktionen auf Einheitenebene steuern. Mit anderen Worten, Sie können Komponententests ausführen, bei denen die Datenbank aktualisiert / eingefügt / gelöscht und die Änderungen rückgängig gemacht werden. Am Ende der Ausführung belassen Sie alles in der Datenbank, wie es war, bevor Sie mit der Ausführung begonnen haben. Für mich ist es so gut wie es nur geht.
quelle