wake-up-neo.com

So schreiben Sie Komponententests für Datenbankaufrufe

Ich stehe kurz vor dem Beginn eines neuen Projekts und (keuch!) Versuche zum ersten Mal, Unit-Tests in ein Projekt von mir aufzunehmen.

Ich habe Probleme, einige der Komponententests selbst zu entwickeln. Ich habe ein paar Methoden, die sich leicht testen ließen (zwei Werte eingeben und auf erwartete Ausgabe prüfen). Ich habe andere Teile des Codes, die komplexere Aufgaben ausführen, z. B. das Ausführen von Abfragen für die Datenbank, und ich bin nicht sicher, wie ich sie testen soll.

public DataTable ExecuteQuery(SqlConnection ActiveConnection, string Query, SqlParameterCollection Parameters)
{
    DataTable resultSet = new DataTable();
    SqlCommand queryCommand = new SqlCommand();
    try
    {
        queryCommand.Connection = ActiveConnection;
        queryCommand.CommandText = Query;

        if (Parameters != null)
        {
            foreach (SqlParameter param in Parameters)
            {
                 queryCommand.Parameters.Add(param);
            }
        }

        SqlDataAdapter queryDA = new SqlDataAdapter(queryCommand);
        queryDA.Fill(resultSet);
    }
    catch (Exception ex)
    {
        //TODO: Improve error handling
        Console.WriteLine(ex.Message);
    }

    return resultSet;
}

Diese Methode nimmt im Wesentlichen alle erforderlichen Bits und Teile auf, um einige Daten aus der Datenbank zu extrahieren, und gibt die Daten in einem DataTable-Objekt zurück.

Die erste Frage ist wahrscheinlich die komplexeste: Was sollte ich in einer solchen Situation überhaupt testen?

Sobald dies erledigt ist, stellt sich die Frage, ob die Datenbankkomponenten verspottet oder gegen die eigentliche Datenbank getestet werden sollen.

61
kdmurray

Was testen Sie?

Es gibt drei Möglichkeiten:

A. Sie testen die DAO-Klasse (Data Access Object), um sicherzustellen, dass die Werte/Parameter, die an die Datenbank übergeben werden, richtig gemarshallt werden und die aus der Datenbank erhaltenen Ergebnisse korrekt gemarshallt/transformiert/gepackt werden.

In diesem Fall müssen Sie keine Verbindung zur Datenbank herstellen. Sie benötigen lediglich einen Komponententest, der die Datenbank (oder Zwischenschicht, z. B. JDBC, (N) Hibernate, iBatis) durch einen Schein ersetzt.

B. Sie testen die syntaktische Korrektheit von (generiertem) SQL.

In diesem Fall möchten Sie, da SQL-Dialekte unterschiedlich sind, die (möglicherweise generierte) SQL mit der richtigen Version Ihres RDBMS ausführen, anstatt zu versuchen, alle Macken Ihres RDBMS zu verspotten (und damit alle RDBMS-Upgrades, die die Funktionalität ändern, abgefangen werden Ihre Tests).

C. Sie testen die semantische Korrektheit Ihres SQL, dh, dass für einen bestimmten Basisdatensatz Ihre Operationen (Zugriffe/Auswahlen und Mutationen/Einfügungen) und Aktualisierungen) erzeugen den erwarteten neuen Datensatz.

Dazu möchten Sie so etwas wie dbunit verwenden (mit dem Sie eine Baseline erstellen und eine Ergebnismenge mit einer erwarteten Ergebnismenge vergleichen können) oder Ihre Tests mithilfe der hier beschriebenen Technik vollständig in der Datenbank durchführen: Bester Weg, um SQL-Abfragen zu testen .

46
tpdi

Dies ist der Grund, warum Unit-Tests (IMHO) bei Entwicklern manchmal ein falsches Sicherheitsgefühl hervorrufen können. Nach meiner Erfahrung mit Anwendungen, die mit einer Datenbank kommunizieren, sind Fehler häufig das Ergebnis eines unerwarteten Zustands von Daten (ungewöhnliche oder fehlende Werte usw.). Wenn Sie den Datenzugriff in Ihren Komponententests routinemäßig nachahmen, werden Sie denken, dass Ihr Code einwandfrei funktioniert, wenn er tatsächlich immer noch für diese Art von Fehler anfällig ist.

Ich denke, Ihr bester Ansatz ist es, eine Testdatenbank griffbereit zu haben, die voll mit beschissenen Daten ist, und Ihre Datenbankkomponententests dagegen durchzuführen. Die ganze Zeit daran denken, dass Ihre Benutzer viel besser sind, als Sie Ihre Daten vermasseln.

28
MusiGenesis

Der springende Punkt bei einem Unit-Test ist, dass eine Unit testen (duh) für sich genommen gilt. Der springende Punkt eines Datenbankaufrufs ist integrieren mit einer anderen Einheit (der Datenbank). Ergo: Es ist nicht sinnvoll, Unit-Test-Datenbankaufrufe durchzuführen.

Sie sollten jedoch Datenbankaufrufe für Integrationstests ausführen (und Sie können die gleichen Tools verwenden, die Sie für Unit-Tests verwenden, wenn Sie möchten).

10
Jörg W Mittag

Um Gottes willen, testen Sie nicht gegen eine lebende, bereits bevölkerte Datenbank. Aber das wusstest du doch.

Im Allgemeinen haben Sie bereits eine Vorstellung davon, welche Art von Daten jede Abfrage abrufen wird, ob Sie Benutzer authentifizieren, Telefonbuch-/Organigrammeinträge nachschlagen oder was auch immer. Sie wissen, für welche Felder Sie sich interessieren, und Sie wissen, welche Einschränkungen für sie gelten (z. B. UNIQUE, NOT NULL, und so weiter). Sie testen Ihren Code, der mit der Datenbank interagiert, und nicht die Datenbank selbst. Überlegen Sie sich daher, wie Sie diese Funktionen testen. Wenn es möglich ist, dass ein Feld NULL ist, sollten Sie einen Test durchführen, der sicherstellt, dass Ihr Code NULL Werte korrekt verarbeitet. Wenn eines Ihrer Felder eine Zeichenfolge ist (CHAR, VARCHAR, TEXT, & c), prüfen Sie, ob Sie mit maskierten Zeichen richtig umgehen.

Nehmen Sie an, dass Benutzer versuchen, irgendetwas * in die Datenbank einzufügen, und generieren Sie dementsprechend Testfälle. Sie werden dafür Scheinobjekte verwenden wollen.

* Einschließlich unerwünschter, böswilliger oder ungültiger Eingaben.

Genau genommen ist ein Test, der aus einer Datenbank oder einem Dateisystem schreibt/liest, kein Komponententest. (Obwohl es sich möglicherweise um einen Integrationstest handelt und mit NUnit oder JUnit geschrieben wurde). Unit-Tests sollen Operationen einer einzelnen Klasse testen und ihre Abhängigkeiten isolieren. Wenn Sie also einen Komponententest für die Schnittstellen- und Geschäftslogikebene schreiben, sollten Sie überhaupt keine Datenbank benötigen.

OK, aber wie können Sie die Datenbankzugriffsschicht Unit-testen? Ich mag die Ratschläge aus diesem Buch: xUnit Test Patterns (der Link verweist auf das Kapitel "Testing w/DB" des Buches. Die Schlüssel sind:

  • verwenden Sie Round-Trip-Tests
  • schreiben Sie nicht zu viele Tests in Ihren Datenzugriffstest, da diese viel langsamer ablaufen als Ihre "echten" Unit-Tests
  • wenn Sie das Testen mit einer realen Datenbank vermeiden können, testen Sie ohne Datenbank
4
azheglov

Sie können alles testen, außer: queryDA.Fill(resultSet);

Sobald Sie queryDA.Fill(resultSet) ausführen, müssen Sie entweder die Datenbank verspotten/fälschen oder Sie führen Integrationstests durch.

Ich für meinen Teil sehe Integrationstests nicht als schlecht an, es ist nur so, dass sie eine andere Art von Fehler aufdecken, unterschiedliche Chancen für falsch negative und falsch positive Ergebnisse haben und wahrscheinlich nicht sehr oft durchgeführt werden, weil dies so ist schleppend.

Wenn ich diesen Code als Unit testen würde, würde ich überprüfen, ob die Parameter korrekt erstellt wurden. Erstellt der Befehlsgenerator die richtige Anzahl von Parametern? Haben sie alle einen Wert? Werden Nullen, leere Strings und DbNull korrekt behandelt?

Das tatsächliche Füllen des Datensatzes testet Ihre Datenbank, die eine unzuverlässige Komponente außerhalb des Geltungsbereichs Ihrer DAL ist.

4
MatthewMartin

Für Unit-Tests verspotte oder fälsche ich normalerweise die Datenbank. Verwenden Sie dann Ihre Schein- oder Scheinimplementierung per Abhängigkeitsinjektion, um Ihre Methode zu testen. Möglicherweise verfügen Sie auch über einige Integrationstests, mit denen Einschränkungen, Fremdschlüsselbeziehungen usw. in Ihrer Datenbank getestet werden.

Was Sie testen würden, würden Sie sicherstellen, dass die Methode die Verbindung aus den Parametern verwendet, dass die Abfragezeichenfolge dem Befehl zugewiesen ist und dass Ihre zurückgegebene Ergebnismenge mit der übereinstimmt, die Sie über eine Erwartung bereitstellen auf der Fill-Methode. Hinweis - Es ist wahrscheinlich einfacher, eine Get-Methode zu testen, die einen Wert zurückgibt, als eine Fill-Methode, mit der ein Parameter geändert wird.

2
tvanfosson

Um dies richtig zu machen, sollten Sie jedoch eine Abhängigkeitsinjektion (DI) verwenden, und für .NET gibt es mehrere. Ich verwende derzeit das Unity Framework, aber es gibt andere, die einfacher sind.

Hier ist ein Link von dieser Site zu diesem Thema, aber es gibt noch andere: Abhängigkeitsinjektion in .NET mit Beispielen?

Dies würde es Ihnen ermöglichen, andere Teile Ihrer Anwendung einfacher zu verspotten, indem Sie die Schnittstelle nur von einer Verspottungsklasse implementieren lassen, sodass Sie steuern können, wie sie reagiert. Dies bedeutet jedoch auch, eine Schnittstelle zu entwerfen.

Da Sie nach Best Practices gefragt haben, wäre dies eine, IMO.

Dann gehen Sie nicht zur Datenbank, es sei denn, Sie müssen, wie vorgeschlagen, eine andere.

Wenn Sie bestimmte Verhaltensweisen testen müssen, z. B. Fremdschlüsselbeziehungen mit Kaskadenlöschung, möchten Sie möglicherweise Datenbanktests dafür schreiben, aber im Allgemeinen ist es nicht empfehlenswert, auf eine echte Datenbank zuzugreifen, zumal unter Umständen mehrere Personen einen Komponententest durchführen Zu einem bestimmten Zeitpunkt und wenn dieselben Datenbanktests ausgeführt werden, schlagen die Tests möglicherweise fehl, da sich die erwarteten Daten möglicherweise ändern.

Bearbeiten: Mit Datenbank-Unit-Test meine ich dies, da es entwickelt wurde, um nur t-sql zu verwenden, um einige Setups, Tests und Teardowns durchzuführen. http://msdn.Microsoft.com/en-us/library/aa833233%28VS.80%29.aspx

1
James Black

In JDBC-basierten Projekten kann die JDBC-Verbindung gesperrt werden, sodass Tests ohne Live-RDBMS ausgeführt werden können, wobei jeder Testfall isoliert ist (kein Datenkonflikt).

Es ermöglicht zu überprüfen, ob der Persistenzcode die richtigen Abfragen/Parameter übergibt (z. B. https://github.com/playframework/playframework/blob/master/framework/src/anorm/src/test/scala/anorm/ParameterSpec). scala ) und behandeln JDBC-Ergebnisse (Parsing/Mapping) wie erwartet ("nimmt alle erforderlichen Bits und Teile auf, um einige Daten aus der Datenbank zu extrahieren, und gibt die Daten in einem DataTable-Objekt zurück").

Framework wie jOOQ oder my framework Acolyte kann verwendet werden für: https://github.com/cchantep/acolyte .

0
cchantep