Frage

Ich habe gehört, dass Unit-Tests „absolut großartig“, „wirklich cool“ und „alle möglichen guten Dinge“ sind, aber 70 % oder mehr meiner Dateien beinhalten Datenbankzugriff (einige lesen und andere schreiben), und ich bin mir nicht sicher, wie um einen Unit-Test für diese Dateien zu schreiben.

Ich verwende PHP und Python, aber ich denke, diese Frage gilt für die meisten/alle Sprachen, die Datenbankzugriff verwenden.

War es hilfreich?

Lösung

Ich würde vorschlagen, Ihre Aufrufe an die Datenbank zu verspotten.Mocks sind grundsätzlich 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.für den Anrufer verfügbar.Aber anstatt die Aktion auszuführen, für die sie programmiert sind, wenn eine bestimmte Methode aufgerufen wird, wird diese vollständig übersprungen und nur ein Ergebnis zurückgegeben.Dieses Ergebnis wird in der Regel von Ihnen im Voraus festgelegt.

Um Ihre Objekte für das Verspotten einzurichten, müssen Sie wahrscheinlich eine Art Umkehrung der Kontrolle/Abhängigkeitsinjektionsmuster 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, das es Ihnen ermöglicht, die Methode GetAllFoos aufzurufen, ohne tatsächlich auf die Datenbank zugreifen 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 Spottszenario.Natürlich möchten Sie wahrscheinlich trotzdem auch Ihre tatsächlichen Datenbankaufrufe einem Unit-Test unterziehen, wofür Sie auf die Datenbank zugreifen müssen.

Andere Tipps

Im Idealfall sollten Ihre Objekte dauerhaft unwissend sein.Sie sollten beispielsweise über eine „Datenzugriffsschicht“ verfügen, an die Sie Anfragen stellen und die Objekte zurückgeben würde.Auf diese Weise können Sie diesen Teil aus Ihren Unit-Tests herauslassen oder sie isoliert testen.

Wenn Ihre Objekte eng mit Ihrer Datenschicht verbunden sind, ist es schwierig, ordnungsgemäße Unit-Tests durchzuführen.Der erste Teil des Unit-Tests ist „Unit“.Alle Einheiten 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 der Zugriff erfolgt über meine Anwendungsschicht.Die Anwendungsschicht kommuniziert sowohl mit der Datenschicht als auch mit der Domänenmodellschicht.

Die Anwendungsschicht wird manchmal auch als „Business Layer“ bezeichnet.

Wenn Sie PHP verwenden, erstellen Sie einen bestimmten Satz von Klassen dafür NUR Datenzugriff.Stellen Sie sicher, dass Ihre Objekte keine Ahnung haben, wie sie persistiert werden, und verbinden Sie die beiden in Ihren Anwendungsklassen.

Eine andere Möglichkeit wäre die Verwendung von Mocking/Stubs.

Der einfachste Weg, ein Objekt mit Datenbankzugriff einem Komponententest zu unterziehen, ist die Verwendung von Transaktionsbereichen.

Zum Beispiel:

    [Test]
    [ExpectedException(typeof(NotFoundException))]
    public void DeleteAttendee() {

        using(TransactionScope scope = new TransactionScope()) {
            Attendee anAttendee = Attendee.Get(3);
            anAttendee.Delete();
            anAttendee.Save();

            //Try reloading. Instance should have been deleted.
            Attendee deletedAttendee = Attendee.Get(3);
        }
    }

Dadurch wird der Status der Datenbank wiederhergestellt, im Grunde wie bei einem Transaktions-Rollback, sodass Sie den Test so oft ausführen können, wie Sie möchten, ohne dass es zu Nebenwirkungen kommt.Wir haben diesen Ansatz erfolgreich in großen Projekten eingesetzt.Die Ausführung unseres Builds dauert zwar etwas länger (15 Minuten), ist aber bei 1800 Unit-Tests nicht schlecht.Wenn die Build-Zeit ein Problem darstellt, können Sie den Build-Prozess auch so ändern, dass er über mehrere Builds verfügt, einen zum Erstellen von src, einen anderen, der anschließend gestartet wird und Unit-Tests, Code-Analyse, Paketierung usw. übernimmt.

Sie sollten den Datenbankzugriff verspotten, wenn Sie Ihre Klassen einem Unit-Test unterziehen möchten.Schließlich möchten Sie die Datenbank nicht in einem Unit-Test testen.Das wäre ein Integrationstest.

Abstrahieren Sie die Aufrufe und fügen Sie dann einen Schein ein, der nur die erwarteten Daten zurückgibt.Wenn Ihre Klassen jedoch nicht mehr tun, als Abfragen auszuführen, lohnt es sich möglicherweise nicht einmal, sie zu testen ...

Ich kann Ihnen vielleicht einen Vorgeschmack auf unsere Erfahrungen geben, als wir begannen, uns mit Unit-Tests unseres Mittelschichtprozesses zu befassen, der eine Menge „Geschäftslogik“-SQL-Operationen umfasste.

Wir haben zunächst eine Abstraktionsschicht erstellt, die es uns ermöglichte, jede sinnvolle Datenbankverbindung „einzufügen“ (in unserem Fall unterstützten wir einfach eine einzelne ODBC-Verbindung).

Sobald dies eingerichtet war, konnten wir in unserem Code so etwas tun (wir arbeiten in C++, aber ich bin sicher, Sie verstehen, worauf es ankommt):

GetDatabase().ExecuteSQL( "INSERT INTO foo (bla, blah)")

Zur normalen Laufzeit würde GetDatabase() ein Objekt zurückgeben, das unser gesamtes SQL (einschließlich Abfragen) über ODBC direkt an die Datenbank weiterleitet.

Dann haben wir uns mit In-Memory-Datenbanken befasst – die mit Abstand beste scheint SQLite zu sein.(http://www.sqlite.org/index.html).Es ist bemerkenswert einfach einzurichten und zu verwenden und ermöglichte es uns, GetDatabase() in Unterklassen zu unterteilen und zu überschreiben, um SQL an eine In-Memory-Datenbank weiterzuleiten, die für jeden durchgeführten Test erstellt und zerstört wurde.

Wir befinden uns noch im Anfangsstadium, aber bisher sieht es gut aus. Allerdings müssen wir sicherstellen, dass wir alle erforderlichen Tabellen erstellen und diese mit Testdaten füllen. Allerdings haben wir den Arbeitsaufwand hier durch die Erstellung etwas reduziert ein allgemeiner Satz von Hilfsfunktionen, die einen Großteil davon für uns erledigen können.

Insgesamt hat es unserem TDD-Prozess enorm geholfen, da scheinbar recht harmlose Änderungen zur Behebung bestimmter Fehler aufgrund der Natur von SQL/Datenbanken ziemlich seltsame Auswirkungen auf andere (schwer zu erkennende) Bereiche Ihres Systems haben können.

Offensichtlich konzentrierten sich unsere Erfahrungen auf eine C++-Entwicklungsumgebung, ich bin mir jedoch sicher, dass Sie vielleicht etwas Ähnliches unter PHP/Python zum Laufen bringen könnten.

Hoffe das hilft.

Das Buch xUnit-Testmuster beschreibt einige Möglichkeiten zum Umgang mit Unit-Test-Code, der auf eine Datenbank trifft.Ich stimme mit den anderen Leuten überein, die sagen, dass man das nicht tun möchte, weil es langsam ist, aber man muss es meiner Meinung nach irgendwann tun.Es ist eine gute Idee, die Datenbankverbindung auszuprobieren, um Dinge auf höherer Ebene zu testen, aber schauen Sie sich dieses Buch an, um Vorschläge zu Dingen zu erhalten, die Sie tun können, um mit der eigentlichen Datenbank zu interagieren.

Optionen, die Sie haben:

  • Schreiben Sie ein Skript, das die Datenbank löscht, bevor Sie mit den Komponententests beginnen, füllen Sie dann die Datenbank mit vordefinierten Datensätzen und führen Sie die Tests aus.Sie können dies auch vor jedem Test tun – es ist zwar langsamer, aber weniger fehleranfällig.
  • Injizieren Sie die Datenbank.(Beispiel in Pseudo-Java, gilt aber für alle OO-Sprachen)

    class Database {
     public Result query(String query) {... real db here ...}
    }

    Klasse MockDatabase erweitert die Datenbank {öffentliche Ergebnisabfrage (String -Abfrage) {return "Mock -Ergebnis";}}

    class ObjectThatUseSdb {public ObjectThatusesDB (Datenbank db) {this.database = db;}}

    Jetzt verwenden Sie in der Produktion eine normale Datenbank und für alle Tests fügen Sie einfach die Scheindatenbank ein, die Sie ad hoc erstellen können.

  • Verwenden Sie DB im größten Teil des Codes überhaupt nicht (das ist ohnehin eine schlechte Praxis).Erstellen Sie ein „Datenbank“-Objekt, das statt mit Ergebnissen normale Objekte zurückgibt (d. h.wird zurückkehren User anstelle eines Tupels {name: "marcin", password: "blah"}) Schreiben Sie alle Ihre Tests mit Ad-hoc-Konstruktion real Objekte und schreiben Sie einen großen Test, der auf einer Datenbank basiert, die sicherstellt, dass diese Konvertierung ordnungsgemäß funktioniert.

Natürlich schließen sich diese Ansätze nicht gegenseitig aus und Sie können sie je nach Bedarf kombinieren.

Normalerweise versuche ich, meine Tests zwischen dem Testen der Objekte (und ORM, falls vorhanden) und dem Testen der Datenbank aufzuteilen.Ich teste die Objektseite der Dinge, indem ich die Datenzugriffsaufrufe nachahme, während ich die Datenbankseite der Dinge teste, indem ich die Objektinteraktionen mit der Datenbank teste, was meiner Erfahrung nach normalerweise ziemlich begrenzt ist.

Früher war ich beim Schreiben von Unit-Tests frustriert, bis ich anfing, den Datenzugriffsteil zu verspotten, damit ich keine Testdatenbank erstellen oder Testdaten im Handumdrehen generieren musste.Indem Sie die Daten simulieren, können Sie alles zur Laufzeit generieren und sicherstellen, dass Ihre Objekte mit bekannten Eingaben ordnungsgemäß funktionieren.

Ich habe das noch nie in PHP gemacht und ich habe nie Python verwendet, aber Sie möchten die Aufrufe an die Datenbank nachahmen.Dazu können Sie einige implementieren IoC Unabhängig davon, ob Sie ein Tool eines Drittanbieters verwenden oder es selbst verwalten, können Sie eine Scheinversion des Datenbankaufrufers implementieren, mit der Sie das Ergebnis dieses gefälschten Aufrufs steuern.

Eine einfache Form von IoC kann einfach durch Codierung in Schnittstellen durchgeführt werden.Dies erfordert eine Art Objektorientierung in Ihrem Code, sodass dies möglicherweise nicht auf das zutrifft, was Sie tun (ich sage das, da ich nur darauf eingehen muss, dass Sie PHP und Python erwähnen).

Ich hoffe, das ist hilfreich. Wenn nichts anderes passiert, haben Sie jetzt einige Begriffe, nach denen Sie suchen können.

Ich stimme dem ersten Beitrag zu – der Datenbankzugriff sollte auf eine DAO-Schicht reduziert werden, die eine Schnittstelle implementiert.Anschließend können Sie Ihre Logik anhand einer Stub-Implementierung der DAO-Schicht testen.

Du könntest benutzen Spottrahmen um die Datenbank-Engine zu abstrahieren.Ich weiß nicht, ob PHP/Python welche hat, aber für typisierte Sprachen (C#, Java usw.) gibt es viele Möglichkeiten

Es 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.

Das Unit-Testen Ihres Datenbankzugriffs ist einfach genug, wenn Ihr Projekt durchgehend eine hohe Kohäsion und lose Kopplung aufweist.Auf diese Weise können Sie nur die Dinge testen, die jede einzelne 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, die Logik innerhalb der Benutzeroberfläche wie erwartet zu überprüfen, nicht die Geschäftslogik oder Datenbankaktion hinter dieser Funktion.

Wenn Sie den tatsächlichen Datenbankzugriff einem Komponententest unterziehen möchten, erhalten Sie am Ende eher einen Integrationstest, da Sie vom Netzwerkstapel und Ihrem Datenbankserver abhängig sind. Sie können jedoch überprüfen, ob Ihr SQL-Code das tut, was Sie von ihm verlangt haben Tun.

Die verborgene Stärke von Unit-Tests besteht für mich persönlich darin, dass sie mich dazu zwingen, meine Anwendungen viel besser zu entwerfen, als ich es ohne sie tun könnte.Das liegt daran, dass es mir wirklich geholfen hat, mich von der „Diese Funktion sollte alles machen“-Mentalität zu lösen.

Tut mir leid, ich habe keine spezifischen Codebeispiele für PHP/Python, aber wenn Sie ein .NET-Beispiel sehen möchten, habe ich eines Post Das beschreibt eine Technik, mit der ich genau diesen Test durchgeführt habe.

Das Einrichten von Testdaten für Unit-Tests kann eine Herausforderung sein.

Wenn Sie in Bezug auf Java Spring-APIs für Unit-Tests verwenden, können Sie die Transaktionen auf Unit-Ebene steuern.Mit anderen Worten: Sie können Komponententests ausführen, die Datenbankaktualisierungen/-einfügungen/-löschungen und ein Rollback der Änderungen umfassen.Am Ende der Ausführung lassen Sie alles in der Datenbank so, wie es war, bevor Sie mit der Ausführung begonnen haben.Für mich ist es so gut wie es nur geht.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top