Frage

Im vergangenen Sommer habe ich eine einfache ASP.NET/SQL Server CRUD-App entwickelt, und Unit-Tests waren eine der Anforderungen.Beim Versuch, mit der Datenbank zu testen, bin ich auf einige Probleme gestoßen.Nach meinem Verständnis sollten Unit-Tests wie folgt aussehen:

  • staatenlos
  • unabhängig voneinander
  • wiederholbar mit den gleichen Ergebnissen, d.h.Keine bleibenden Änderungen

Diese Anforderungen scheinen bei der Entwicklung einer Datenbank im Widerspruch zueinander zu stehen.Ich kann beispielsweise Insert() nicht testen, ohne sicherzustellen, dass die einzufügenden Zeilen noch nicht vorhanden sind. Daher muss ich zuerst Delete() aufrufen.Aber was ist, wenn sie noch nicht da sind?Dann müsste ich zuerst die Funktion Exists() aufrufen.

Meine letztendliche Lösung umfasste sehr umfangreiche Setup-Funktionen (igitt!) und einen leeren Testfall, der zuerst ausgeführt wurde und anzeigte, dass das Setup ohne Probleme lief.Dadurch wird die Unabhängigkeit der Tests geopfert, während gleichzeitig ihre Staatenlosigkeit gewahrt bleibt.

Eine andere Lösung, die ich gefunden habe, besteht darin, die Funktionsaufrufe in eine Transaktion einzubinden, die leicht zurückgesetzt werden kann, z Roy Osheroves XtUnit.Das funktioniert, aber es beinhaltet eine andere Bibliothek, eine andere Abhängigkeit, und es scheint eine etwas zu schwere Lösung für das vorliegende Problem zu sein.

Was hat die SO-Community also getan, als sie mit dieser Situation konfrontiert wurde?


tgmdbm sagte:

Normalerweise verwenden Sie Ihr bevorzugtes automatisiertes Unit -Test -Framework, um Integrationstests durchzuführen, weshalb einige Leute verwirrt sind, aber sie folgen nicht den gleichen Regeln.Sie dürfen die konkrete Implementierung vieler Ihrer Klassen einbeziehen (weil sie Unit getestet wurden).Sie testen Wie Ihre konkreten Klassen miteinander und mit der Datenbank interagieren.

Wenn ich das also richtig lese, gibt es wirklich keine Möglichkeit dazu effektiv Unit-Test einer Datenzugriffsschicht.Oder würde ein „Komponententest“ einer Datenzugriffsschicht beispielsweise das Testen der von den Klassen generierten SQL-Befehle/Befehle umfassen, unabhängig von der tatsächlichen Interaktion mit der Datenbank?

War es hilfreich?

Lösung

Es gibt keine wirkliche Möglichkeit, eine Datenbank einem Komponententest zu unterziehen, außer sicherzustellen, dass die Tabellen vorhanden sind, die erwarteten Spalten enthalten und über die entsprechenden Einschränkungen verfügen.Aber das lohnt sich meist nicht wirklich.

Das ist normalerweise nicht der Fall Einheit Testen Sie die Datenbank.Normalerweise beziehen Sie die Datenbank mit ein Integration Tests.

Normalerweise verwenden Sie Ihr bevorzugtes automatisiertes Unit-Testing-Framework, um Integrationstests durchzuführen, weshalb manche Leute verwirrt sind, aber sie befolgen nicht die gleichen Regeln.Sie dürfen die konkrete Implementierung vieler Ihrer Klassen einbeziehen (da sie Unit-Tests unterzogen wurden).Sie testen, wie Ihre konkreten Klassen miteinander und mit der Datenbank interagieren.

Andere Tipps

DBunit

Mit diesem Tool können Sie den Status einer Datenbank zu einem bestimmten Zeitpunkt exportieren und sie dann bei Unit-Tests zu Beginn der Tests automatisch auf den vorherigen Status zurücksetzen.Wir verwenden es ziemlich oft dort, wo ich arbeite.

Die übliche Lösung für externe Abhängigkeiten in Unit-Tests ist die Verwendung von Scheinobjekten – also Bibliotheken, die das Verhalten der realen Objekte nachahmen, gegen die Sie testen.Dies ist nicht immer einfach und erfordert manchmal etwas Einfallsreichtum, aber es gibt mehrere gute (Freeware-)Mock-Bibliotheken für .Net, wenn Sie nicht „Ihre eigenen“ erstellen möchten.Zwei fallen mir sofort ein:

Nashorn-Mocks hat einen ziemlich guten Ruf.

NMock ist ein anderer.

Es stehen auch zahlreiche kommerzielle Modellbibliotheken zur Verfügung.Ein Teil des Schreibens guter Unit-Tests besteht tatsächlich darin, Ihren Code dafür zu entwerfen – zum Beispiel durch die Verwendung von Schnittstellen, wo es sinnvoll ist, sodass Sie ein abhängiges Objekt „verspotten“ können, indem Sie eine „falsche“ Version seiner Schnittstelle implementieren, die sich dennoch in einem verhält vorhersehbare Weise, zu Testzwecken.

Bei Datenbank-Mocks bedeutet dies, dass Sie Ihre eigene DB-Zugriffsschicht mit Objekten „verspotten“, die erfundene Tabellen-, Zeilen- oder Datensatzobjekte zurückgeben, damit Ihre Komponententests sie verarbeiten können.

Wo ich arbeite, erstellen wir normalerweise unsere eigenen Scheinbibliotheken von Grund auf, aber das bedeutet nicht, dass Sie das müssen.

Ja, Sie sollten Ihren Code umgestalten, um auf Repositorys und Dienste zuzugreifen, die auf die Datenbank zugreifen, und Sie können diese Objekte dann verspotten oder stutzen, sodass das zu testende Objekt niemals die Datenbank berührt.Das ist viel schneller, als den Zustand der Datenbank zu speichern und nach jedem Test zurückzusetzen!

Ich empfehle sehr Moq als dein spöttisches Gerüst.Ich habe Rhino Mocks und NMock verwendet.Moq war so einfach und löste alle Probleme, die ich mit den anderen Frameworks hatte.

Ich hatte die gleiche Frage und bin zu den gleichen grundlegenden Schlussfolgerungen gekommen wie die anderen Antwortenden hier:Machen Sie sich nicht die Mühe, die eigentliche Datenbank-Kommunikationsschicht einem Unit-Test zu unterziehen, aber wenn Sie Ihre Modellfunktionen einem Unit-Test unterziehen möchten (um sicherzustellen, dass sie Daten richtig abrufen, richtig formatieren usw.), verwenden Sie eine Art Dummy-Datenquelle und Setup-Tests um die abgerufenen Daten zu überprüfen.

Ich finde auch, dass die einfache Definition von Unit-Tests für viele Webentwicklungsaktivitäten schlecht geeignet ist.Diese Seite beschreibt jedoch einige „fortgeschrittenere“ Unit-Test-Modelle und kann als Inspiration für einige Ideen für die Anwendung von Unit-Tests in verschiedenen Situationen dienen:

Unit-Testmuster

Ich habe eine Technik erklärt, die ich in genau dieser Situation verwendet habe Hier.

Die Grundidee besteht darin, jede Methode in Ihrem DAL auszuüben – Ihre Ergebnisse zu bestätigen – und nach Abschluss jedes Tests ein Rollback durchzuführen, damit Ihre Datenbank sauber ist (kein Junk/Testdaten).

Das einzige Problem, das Sie möglicherweise nicht „großartig“ finden, ist, dass ich normalerweise einen vollständigen CRUD-Test durchführe (nicht nur aus Unit-Test-Perspektive), aber dieser Integrationstest ermöglicht es Ihnen, Ihren CRUD + Mapping-Code in Aktion zu sehen.Auf diese Weise wissen Sie, ob es kaputt geht, bevor Sie die Anwendung starten (das erspart mir eine Menge Arbeit, wenn ich versuche, schnell zu arbeiten).

Sie sollten Ihre Tests mit einer leeren Kopie der Datenbank ausführen, die Sie über ein Skript generieren.Sie können Ihre Tests ausführen und dann die Daten analysieren, um sicherzustellen, dass sie nach der Ausführung Ihrer Tests genau das haben, was sie sollen.Dann löschen Sie einfach die Datenbank, da es sich um eine Wegwerfdatenbank handelt.Dies alles kann automatisiert werden und kann als atomare Aktion betrachtet werden.

Durch das Testen der Datenschicht und der Datenbank zusammen sind nur wenige Überraschungen für später im Projekt.Das Testen gegen die Datenbank hat jedoch ihre Probleme, wobei Sie die wichtigsten gegen den Staat testen, das von vielen Tests geteilt wird.Wenn Sie in einem Test eine Zeile in die Datenbank einfügen, kann der nächste Test auch diese Zeile sehen.
Sie benötigen eine Möglichkeit, die von Ihnen an der Datenbank vorgenommenen Änderungen rückgängig zu machen.
Der Transaktionsbereich Die Klasse ist intelligent genug, um sehr komplizierte Transaktionen sowie verschachtelte Transaktionen durchzuführen, bei denen Ihr zu testes Codesanruf für seine eigene lokale Transaktion begeht.Hier ist ein einfacher Code, der zeigt, wie einfach es ist, Ihren Tests eine Rollback -Fähigkeit hinzuzufügen:

    [TestFixture]
    public class TrannsactionScopeTests
    {
        private TransactionScope trans = null;

        [SetUp]
        public void SetUp()
        {
            trans = new TransactionScope(TransactionScopeOption.Required);
        }

        [TearDown]
        public void TearDown()
        {
            trans.Dispose();
        }

        [Test]
        public void TestServicedSameTransaction()
        {
            MySimpleClass c = new MySimpleClass();
            long id = c.InsertCategoryStandard("whatever");
            long id2 = c.InsertCategoryStandard("whatever");
            Console.WriteLine("Got id of " + id);
            Console.WriteLine("Got id of " + id2);
            Assert.AreNotEqual(id, id2);
        }
    }

Wenn Sie LINQ to SQL als ORM verwenden, können Sie die Datenbank im laufenden Betrieb generieren (vorausgesetzt, Sie verfügen über ausreichend Zugriff über das für die Komponententests verwendete Konto).Sehen http://www.aaron-powell.com/blog.aspx?id=1125

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