Zu Ihrer Information: Dieser Artikel ist eine erweiterte Version meines Vortrags an den SQA Days # 25.Aufgrund meiner Erfahrung mit Kollegen kann ich feststellen: Das Testen von DB-Code ist keine weit verbreitete Praxis. Dies kann möglicherweise gefährlich sein. Die DB-Logik wird von Menschen wie jeder andere "übliche" Code geschrieben. Es kann also zu Fehlern kommen, die negative Folgen für ein Produkt, ein Unternehmen oder Benutzer haben können. Unabhängig davon, ob es sich um gespeicherte Prozeduren handelt, die das Backend unterstützen, oder um ETL-modifizierende Daten in einem Warehouse - es besteht immer ein Risiko, und Tests helfen, diese zu verringern. Ich möchte Ihnen sagen, was tSQLt ist und wie es uns hilft, DB-Code zu testen.
Der Kontext
Es gibt ein großes Lager mit SQL Server, das verschiedene Daten zu klinischen Studien enthält. Es wird aus verschiedenen Quellen (hauptsächlich dokumentenorientierte Datenbanken) gefüllt. Viele ETLs transformieren bei vielen Gelegenheiten Daten innerhalb des Lagers. Diese Daten können in kleinere DBs geladen werden, um von Webanwendungen verwendet zu werden, die auf kleine spezifische Aufgaben ausgerichtet sind. Einige Kunden des Kunden baten darum, APIs für ihre Anforderungen zu implementieren. Solche APIs verwenden häufig gespeicherte Prozeduren und unterschiedliche Abfragen.
Im Allgemeinen gibt es auf der DBMS-Seite eine ziemlich große Menge an Code.
Warum brauchen wir das?
Wie Sie der Einführung entnehmen können, ist DB-Code Teil des Anwendungscodes und kann auch Fehler enthalten.
Ich denke, viele von uns kennen Boehms Kurve: Die Behebung von Fehlern ist später im Prozess immer teurer. Ein Fehler, der in einem früheren Entwicklungsstadium gemacht und in einem späteren lokalisiert wurde, kann mehr kosten. Dies liegt an der Notwendigkeit, viele Zwischenschritte (Codierung, Komponententests, Integrationstests, Systemtests usw.) zweimal durchzuführen: zum Debuggen und zum Zurücksetzen des Codes in die Phase, in der er gefunden wurde. Dieser Effekt gilt auch für den Lagerfall. Wenn bei einer ETL-Prozedur ein Fehler auftritt und die Daten mehrmals geändert werden, müssen wir:
- Führen Sie alle Datenumwandlungsschritte zurück zur Ursache des Problems
- Beheben Sie das Problem
- leiten Sie die richtigen Daten erneut ab (zusätzliche manuelle Änderungen können erforderlich sein)
- Stellen Sie sicher, dass keine anderen durch den Fehler verursachten Daten beschädigt sind.
Vergessen Sie nicht, dass wir kein Stofftier verkaufen. Ein Fehler in Bereichen wie klinischen Studien kann nicht nur das Geschäft, sondern auch die menschliche Gesundheit schädigen.
Wie teste ich?
Da es sich um Codetests handelt, meinen wir Unit- und Integrationstests. Diese Dinge wiederholen sich sehr oft und implizieren eine anhaltende Regression. Genau genommen werden solche Tests niemals manuell durchgeführt (naja, mit Ausnahme einiger Einzelfälle wahrscheinlich).
Netter Bonus: Tests können unterstützende Materialien für die Codedokumentation sein. Beispielsweise können Anforderungen folgendermaßen aussehen (anklickbar):
XLS-Datei, 2 Spalten mit Anforderungen + fragmentierte zusätzliche Informationen in anderen Spalten + verwirrendes Markup. Es kann schwierig sein, die ursprünglichen Wünsche bei Bedarf wiederherzustellen. Tests können dabei helfen, Implementierungsnuancen aufzuzeichnen. Natürlich sollten sie nicht als Ersatz für die Dokumentation betrachtet werden.
Leider nimmt die Testkomplexität mit zunehmender Codekomplexität zu, sodass dieser Effekt geglättet werden kann.
Tests können eine zusätzliche Sicherheitsschicht gegen spontane Zusammenführungen sein. CI-Autotests helfen bei diesem Problem aufgrund ihres Formalismus.
Wenn wir uns also für die Automatisierung entschieden haben, müssen wir ein Werkzeug dafür auswählen.
Was zum Testen verwenden?
Beim Testen von DB-Code sehe ich zwei Ansätze: SQL-basiert (wenn ein Tool direkt in DBMS funktioniert) und nicht SQL-basiert. Hier sind die Hauptunterschiede, die ich gefunden habe:
Im Fall von SQL Server haben wir mehrere Möglichkeiten:
Die Skala "Ausgezeichnet - Fehlgeschlagen" ist subjektiv, leider ist es schwierig, einen Weg zu finden.
"Erster Auftritt" - das früheste Datum des Framework-Auftretens, das ich finden konnte - die früheste Veröffentlichung oder Verpflichtung.
Wie Sie sehen, wurden SQL-basierte Alternativen vor langer Zeit aufgegeben, und tSQLt ist das einzige derzeit unterstützte Produkt. Außerdem gewinnt tSQLt funktional. Das einzige ist, dass TST eine etwas umfangreichere Reihe von Behauptungen aufweist als tSQLt. Ich bezweifle jedoch, dass dies alle Nachteile überwiegen kann.
Die tSQLt-Dokumentation weist einige Nuancen auf, die ich später beschreiben werde.
In der Welt ohne SQL sind die Dinge nicht so klar. Alternativen entwickeln sich, wenn auch nicht superaktiv. DbFit ist ein ziemlich interessantes Tool, das auf dem FitNesse-Framework basiert. Dies impliziert die Verwendung von Wiki-Markups zum Schreiben von Tests. Slacker ist ebenfalls interessant: Für DB-Code-Tests wird ein BDD-Ansatz empfohlen.
Ich sollte über Behauptungen in nicht SQL-basierten Lösungen sprechen. Auf den ersten Blick ist die Anzahl der Behauptungen geringer, und wir können denken, dass solche Tools schlechter sind. Wir sollten jedoch bedenken, dass sie sich grundlegend von tSQLt unterscheiden. Daher ist ein solcher oberflächlicher Blick falsch.
Die letzte Zeile - "NUnit, etc." - ist eher eine Erinnerung. Mit Hilfe zusätzlicher Bibliotheken können viele übliche Unit-Testing-Frameworks auf den DB-Code angewendet werden. Es gibt viele N / A in dieser Zeile, da diese Zeile tatsächlich mehrere Werkzeuge enthält. Dies ist die Quelle für "Nuancen" in der Spalte "Zusicherungen". Verschiedene Tools können unterschiedliche Mengen bereitstellen, und es gibt keine Garantie dafür, dass alle Zusicherungen auf DB angewendet werden können.
Als weitere interessante Messgröße können wir
Google-Trends berücksichtigen.
Nuancen:
- Ich habe mich entschieden, Slacker nicht einzuschließen, da dieser Name verschiedene Bedeutungen haben kann (und Abfragen wie "Slacker Framework" sind in der Grafik kaum zu sehen).
- Nur aus Neugier (und weil ein Slot leer blieb) habe ich den TST-Trend hinzugefügt. Aber es zeigt uns kaum das wirkliche Bild, weil es eine Abkürzung ist, die auch verschiedene Dinge bedeuten kann.
- Ich habe NUnit und seine Analoga nicht aufgenommen. Diese Tools sind Frameworks für "übliche" Codetests, daher sind ihre Trends für unseren Kontext nicht beschreibend.
Wie Sie sehen können, ist tSQLt das am besten durchsuchbare Tool in der Liste. Ein weiteres (weniger) beliebtes Tool ist DbFit. Andere Tools sind nur begrenzt beliebt.
Im Großen und Ganzen können wir sehen, dass tSQLt vor dem Hintergrund leuchtet.
Was ist tSQLt?
Es ist leicht zu erraten, dass tSQLt ein SQL-basiertes Unit-Testing-Framework ist. Die offizielle Website ist
https://tsqlt.org .
Es wird versprochen, dass tSQLt SQL Server ab 2005 SP2 unterstützt. Ich habe solche frühen Revisionen nicht überprüft, aber ich sehe keine Probleme mit 2012 auf unserem Dev-Server und 2017 auf meinem lokalen Computer.
Open Source, Apache 2.0-Lizenz,
verfügbar auf GitHub . Wie üblich können wir in kommerziellen Projekten kostenlos Beiträge leisten, Beiträge leisten und, was noch wichtiger ist, keine Angst vor Spyware in CLR haben.
Mechanik
Testfälle sind gespeicherte Prozeduren. Sie können zu Testklassen kombiniert werden (Testsuite in xUnit-Terminologie).
Testklassen sind nichts anderes als DB-Schemata. tSQLt erfordert, sie bei der NewTestClass-Prozedur zu registrieren, die einer speziellen Tabelle Testklassen hinzufügt.
Es ist möglich, eine SetUp-Prozedur zu bestimmen. Diese Prozedur wird vor jedem einzelnen Testfall ausgeführt.
Teardown-Verfahren nach Testfalllauf ist nicht erforderlich. Jeder Testfall mit seinem SetUp wird in einer separaten Transaktion ausgeführt, die nach der Ergebniserfassung zurückgesetzt wird. Es ist sehr praktisch, hat aber einige negative Konsequenzen - ich werde sie etwas später beschreiben.
Das Framework ermöglicht das Ausführen von Testfällen nacheinander, die gesamten Testklassen auf einmal oder sogar alle registrierten Testklassen mit einem einzigen Befehl.
Funktionen und Beispiele
Da ich den offiziellen Leitfaden nicht wiederholen möchte, werde ich anhand von Beispielen die tSQLt-Funktionen zeigen.
Haftungsausschluss:- Beispiele werden vereinfacht
- Der ursprüngliche Code gehört nicht ganz mir - er ist eher eine kollektive Kreation
- Beispiel 2 wird von mir fiktionalisiert, um Funktionen vollständiger zu demonstrieren.
Beispiel 1: CsvSql
Das Folgende wurde auf Anfrage eines Kunden des Kunden implementiert. In Nvarchar (MAX) -Feldern sind SQL-Abfragen gespeichert. Für die Anzeige wird eine minimale Benutzeroberfläche erstellt. Durch diese Abfragen generierte Ergebnismengen werden im Backend zum weiteren Erstellen als CSV-Dateien verwendet. Die CSV-Dateien können durch einen API-Aufruf angefordert werden.
Ergebnissätze sind groß und enthalten eine große Anzahl von Spalten. Ein hypothetisches Beispiel für eine solche Ergebnismenge:
Diese Ergebnismenge repräsentiert Daten aus klinischen Studien. Schauen wir uns die Berechnung von [ClinicsNum] genauer an. Wir haben 2 Tabellen: [Studie] und [Klinik].
Es gibt eine FK: [Klinik]. [TrialID] -> [Trial]. [TrialID]. Offensichtlich reicht es aus, COUNT (*) zu verwenden, um eine Reihe von Kliniken abzuleiten:
SELECT COUNT(*), ... FROM dbo.Trial LEFT JOIN dbo.Clinic ON Trial.ID = Clinic.TrialID WHERE Trial.Name = @trialName GROUP BY ...
Wie können wir eine solche Abfrage testen? Verwenden wir zunächst stub FakeTable, was unsere weitere Arbeit erheblich erleichtert.
EXEC tSQLt.FakeTable 'dbo.Trial'; EXEC tSQLt.FakeTable 'dbo.Clinic';
FakeTable macht eine einfache Sache - benennt alte Tabellen um und erstellt neue mit demselben Namen. Dieselben Namen, dieselben Spalten, jedoch ohne Einschränkungen und Auslöser.
Wir brauchen das, weil:
- Die Test-DB kann einige Daten enthalten, die einen ordnungsgemäßen Testlauf verhindern können. Mit FakeTable können wir uns nicht auf sie verlassen.
- Normalerweise müssen wir zu Testzwecken nur wenige Spalten füllen. Die Tabelle kann viele davon enthalten, häufig mit Einschränkungen und Triggern. Wir erleichtern das spätere Einfügen von Daten - wir fügen nur die für die Testinformationen erforderlichen Informationen ein, um den Test so minimalistisch wie möglich zu halten.
- Es werden keine unerwünschten Triggerläufe ausgeführt, sodass wir uns keine Gedanken über Nachwirkungen machen müssen.
Dann fügen wir die erforderlichen Testdaten ein:
INSERT INTO dbo.Trial ([ID], [Name]) VALUES (1, 'Valerian'); INSERT INTO dbo.Clinic ([ID], [TrialID], [Name]) VALUES (1, 1, 'Clinic1'), (2, 1, 'Clinic2');
Wir leiten die Abfrage aus der Datenbank ab, erstellen eine [Ist] -Tabelle und füllen sie mit dem Ergebnis
aus der Abfrage festgelegt.
DECLARE @sqlStatement NVARCHAR(MAX) = (SELECT… CREATE TABLE actual ([TrialID], ...); INSERT INTO actual EXEC sp_executesql @sqlStatement, ...
Jetzt füllen wir [Erwartet] - unsere erwarteten Werte:
CREATE TABLE expected ( ClinicsNum INT ); INSERT INTO expected SELECT 2
Ich möchte Ihre Aufmerksamkeit darauf lenken, dass wir nur eine Spalte in der Tabelle [Erwartet] haben, obwohl wir den vollständigen Satz in der Spalte [Tatsächlich] haben.
Dies ist auf eine nützliche Funktion der AssertEqualsTable-Prozedur zurückzuführen, die wir zur Überprüfung der Werte verwenden werden.
EXEC tSQLt.AssertEqualsTable 'expected', 'actual', 'incorrect number of clinics';
Es werden nur die Spalten verglichen, die in beiden Tabellen dargestellt sind. In unserem Fall ist dies sehr praktisch, da die zu testende Abfrage viele Spalten zurückgibt, die jeweils mit einer ziemlich komplizierten Logik verbunden sind. Wir möchten keine Testfälle aufblasen, daher hilft diese Funktion wirklich. Natürlich ist diese Funktion ein zweischneidiges Schwert. Wenn [Ist] über SELECT TOP 0 gefüllt ist und an einer Stelle eine unerwartete Spalte angezeigt wird, wird dies in einem solchen Testfall nicht erfasst. Sie müssen zusätzliche Schecks ausstellen, um dies abzudecken.
AssertEqualsTable-Doppelprozeduren
Es ist erwähnenswert, dass tSQLt zwei Prozeduren wie AssertEqualsTable enthält. Dies sind AssertEqualsTableSchema und AssertResultSetsHaveSameMetaData. Der erste Vorgang entspricht dem von AssertEqualsTable, jedoch für die Metadaten der Tabellen. Der zweite macht dasselbe, jedoch für die Metadaten der Ergebnismengen.
Beispiel 2: Einschränkungen
Das vorige Beispiel hat uns gezeigt, wie wir Einschränkungen entfernen können. Aber was ist, wenn wir sie überprüfen müssen? Technisch gesehen sind Einschränkungen ebenfalls Teil der Logik und können als Kandidat für die Abdeckung durch Tests angesehen werden.
Betrachten Sie die Situation aus dem vorherigen Beispiel. 2 Tabellen - [Studie] und [Klinik]; [TrialID] FK:
Versuchen wir, einen Testfall zu schreiben, um ihn zu überprüfen. Zunächst fälschen wir wie im vorherigen Fall die Tabellen:
EXEC tSQLt.FakeTable '[dbo].[Trial]' EXEC tSQLt.FakeTable '[dbo].[Clinic]'
Das Ziel ist das gleiche - unnötige Grenzen loszuwerden. Wir wollen isolierte Kontrollen ohne übermäßigen Aufwand.
Als Nächstes geben wir die Einschränkung zurück, die wir mit ApplyConstraint testen möchten:
EXEC tSQLt.ApplyConstraint '[dbo].[Clinic]', 'Trial_FK';
Jetzt haben wir eine Konfiguration für die Prüfung. Die Überprüfung selbst ist, dass der Versuch, Daten einzufügen, eine Ausnahme verursacht. Für das Bestehen des Testfalls müssen wir diese Ausnahme abfangen. Exception-Handler ExpectException kann helfen.
EXEC tSQLt.ExpectException @ExpectedMessage = 'The INSERT statement conflicted...', @ExpectedSeverity = 16, @ExpectedState = 0;
Wir können versuchen, nicht einfügbare Elemente nach der Handlereinstellung einzufügen.
INSERT INTO [dbo].[Clinic] ([TrialID]) VALUES (1)
Die Ausnahme wurde abgefangen. Test bestanden.
ApplyConstraint-Doppelverfahren
Die von tSQLt-Autoren vorgeschlagene Methode zum Testen von Triggern ähnelt dem Testen von Einschränkungen. Wir können die ApplyTrigger-Prozedur verwenden, um den Trigger an die Tabelle zurückzugeben. Danach läuft alles wie im obigen Beispiel - starten Sie den Trigger, überprüfen Sie das Ergebnis.
ExpectNoException - das Antonyme der ExpectException
Es gibt eine ExpectNoException-Prozedur für die Fälle, in denen keine Ausnahme auftreten darf. Es funktioniert genauso wie ExpectException, außer dass der Test im Falle einer Ausnahme fehlschlägt.
Beispiel 3: Semaphor
Es gibt einige gespeicherte Prozeduren und Windows-Dienste. Der Beginn ihrer Ausführung kann durch verschiedene äußere Ereignisse verursacht werden. Die Reihenfolge ihrer Ausführung ist jedoch festgelegt. Daher ist es erforderlich, die Zugriffssteuerung auf der DB-Seite zu implementieren - d. H. Ein Semaphor. In unserem Fall ist das Semaphor eine Gruppe gespeicherter Prozeduren, die zusammenarbeiten.
Schauen wir uns eine Prozedur innerhalb des Semaphors an. Wir haben 2 Tabellen - [Process] und [ProcStatus]:
Die Tabelle [Prozess] enthält eine Liste der Prozesse, die zur Ausführung zugelassen sind. [ProcStatus] enthält offensichtlich die Liste der Status des Prozesses aus der vorherigen Tabelle.
Was macht unser Verfahren? Zunächst werden die folgenden Überprüfungen durchgeführt:
- Wir haben einen Prozessnamen als einen der Eingabeparameter der Prozedur übergeben. Dieser Name wird im Feld [Name] der Tabelle [Prozess] gesucht.
- Wenn der Name des Prozesses gefunden wurde, überprüft er das Flag [IsRunable] der Tabelle [Process].
- Wenn das Flag eingeschaltet ist, können wir davon ausgehen, dass der Prozess ausgeführt werden kann. Die letzte Überprüfung erfolgt in der Tabelle [ProcStatus]. Wir müssen sicherstellen, dass der Prozess derzeit nicht ausgeführt wird. Dies bedeutet, dass keine Datensätze über den Prozess mit dem Status "InProg" in der Tabelle [ProcStatus] vorhanden sind.
Wenn alles in Ordnung ist und alle Prüfungen bestanden wurden, fügen wir der Tabelle [ProcStatus] einen neuen Datensatz über unseren Prozess mit dem Status "InProg" hinzu. Die ID dieses neuen Datensatzes wird mit dem Ausgabeparameter ProcStatusId zurückgegeben.
Wenn etwas schief gelaufen ist, erwarten wir Folgendes:
- Eine E-Mail an einen Systemadministrator wird gesendet.
- ProcStatusId = -1 wird zurückgegeben.
- Es wurden keine neuen [ProcStatus] -Datensätze hinzugefügt.
Erstellen wir einen Testfall zum Überprüfen des Falls von Prozessabwesenheit in der Tabelle [Prozess].
Wir verwenden wieder FakeTable. Es ist hier nicht so kritisch, aber es kann praktisch sein, weil:
- Es ist garantiert, dass keine Daten vorhanden sind, die die Ausführung des Testfalls stören könnten.
- Die weitere Überprüfung der Abwesenheit neuer [ProcStatus] -Datensätze wird vereinfacht.
EXEC tSQLt.FakeTable 'dbo.Process'; EXEC tSQLt.FakeTable 'dbo.ProcStatus';
Es gibt eine [SendEmail] -Prozedur, deren Name für sich selbst spricht. Wir müssen seinen Anruf entgegennehmen. tSQLt schlägt vor, dafür SpyProcedure-Mock zu verwenden.
EXEC tSQLt.SpyProcedure 'dbo.SendEmail'
SpyProcedure führt Folgendes aus:
- Erstellt eine Tabelle mit einem Namen, der wie [dbo] aussieht. [ProcedureName_SpyProcedureLog]
- Ersetzt genau wie FakeTable die ursprüngliche Prozedur durch eine automatisch generierte Prozedur mit demselben Namen, jedoch mit darin enthaltener Protokollierungslogik. Bei Bedarf können Sie der generierten Prozedur auch Ihre eigene Logik hinzufügen.
Es ist nicht schwer zu erraten, dass Protokolle in der Tabelle [dbo]. [SendEmail_SpyProcedureLog] aufgezeichnet werden. Diese Tabelle enthält eine Spalte [_ID_] für die Sequenznummern der Anrufe. Nachfolgende Spalten werden nach Parametern benannt, die an die Prozedur übergeben und zum Sammeln verwendet werden, sodass auch die Werte der Parameter überprüft werden können.
Das Letzte, was wir vor dem Semaphoraufruf tun müssen, ist, eine Variable zum Speichern des [ProcStatusId] -Werts zu erstellen (genauer gesagt -1, da der Datensatz nicht hinzugefügt wird).
DECLARE @ProcStatusId BIGINT;
Wir nennen das Semaphor:
EXEC dbo.[Semaphore_JobStarter] 'SomeProcess', @ProcStatusId OUTPUT; -- here we get -1
Jetzt haben wir alle Daten, die für die Prüfungen benötigt werden. Beginnen wir mit der Überprüfung
dass die Nachricht gesendet wurde.
IF NOT EXISTS ( SELECT * FROM dbo.SendEmail_SpyProcedureLog) EXEC tSQLt.Fail 'SendEmail has not been run.';
In diesem Fall überprüfen wir nicht die übergebenen Parameter und testen nur die Tatsache des Sendens. Ich möchte Ihre Aufmerksamkeit auf das Fail-Verfahren lenken. Es ermöglicht uns, einen Testfall "offiziell" nicht zu bestehen. Wenn Sie eine anspruchsvolle Konstruktion erstellen müssen, kann Fail helfen.
Jetzt überprüfen wir das Fehlen von Datensätzen in der Tabelle [ProcStatus] mit der AssertEmptyTable-Prozedur.
EXEC tSQLt.AssertEmptyTable 'dbo.ProcStatus';
Hier hat uns FakeTable geholfen, das wir am Anfang verwendet haben. Damit können wir eine leere Tabelle erwarten und mit einer einzigen Codezeile testen. Die richtige Möglichkeit, dies ohne Tabellenfälschung zu überprüfen, besteht darin, die Anzahl der Zeilen vor und nach der Ausführung der Prozedur zu vergleichen. Dies würde mehr Aktionen erfordern.
Wir können die ProcStatusId = -1-Gleichheit leicht mit AssertEquals überprüfen.
EXEC tSQLt.AssertEquals -1, @ProcStatusId, 'Wrong ProcStatusId.';
AssertEquals ist minimalistisch. Es werden nur 2 Werte verglichen, nichts Außergewöhnliches.
AssertEquals Doppelprozeduren
Wir haben die folgenden Verfahren zum Wertevergleich:
- AssertEquals
- AssertNotEquals
- AssertEqualsString
- Assertike
Die Namen sind selbsterklärend, denke ich. Das einzige Verfahren, das ich hervorheben möchte, ist AssertEqualsString. Dies ist das Verfahren zur Überprüfung von Zeichenfolgenwerten. Warum brauchen wir ein weiteres Verfahren, wenn wir die universellen AssertEquals berücksichtigen? Die Sache ist, AssertEquals / AssertNotEquals / AssertLike arbeiten mit dem Typ SQL_VARIANT. NVARCHAR (MAX) ist in SQL_VARIANT nicht enthalten, daher mussten tSQLt-Entwickler eine zusätzliche Prozedur durchführen.
Gefälschte Funktion
Auf Knopfdruck können wir FakeFunction eine Prozedur aufrufen, die SpyProcedure ähnelt. Diese Fälschung ermöglicht es, jede Funktion durch eine einfachere zu ersetzen. Da SQL Server-Funktionen wie eine Zahnpastatube funktionieren (das Ergebnis wird durch das einzige „Loch“ zurückgegeben), ist es technisch unmöglich, eine Protokollierungsfunktion zu implementieren. Der Austausch der inneren Logik ist der einzige verfügbare Weg.
Fallstricke
Ich möchte Ihnen einige Fallstricke erläutern, denen Sie bei der Verwendung von tSQLt begegnen können. In diesem Fall bedeuten "Fallstricke" einige Probleme, die durch SQL Server-Einschränkungen verursacht werden und / oder von Framework-Entwicklern nicht behoben werden können.
Rollback und Dooming von Transaktionen
Das erste und Hauptproblem unseres Teams ist das Rollback und Dooming von Transaktionen. SQL Server kann verschachtelte Transaktionen nicht separat zurücksetzen. Es werden immer alle Transaktionen bis zum äußersten zurückgesetzt. Wenn man bedenkt, dass tSQLt jeden Test in eine separate Transaktion einschließt, kann dies zu einem Problem werden, da ein Rollback innerhalb einer gespeicherten Prozedur einen Testlauf mit einem nicht beschreibenden Ausführungsfehler unterbrechen kann.
Um dieses Problem zu umgehen, verwenden wir Sicherungspunkte. Die Idee ist einfach. Zu Beginn prüfen wir, ob wir uns in einer Transaktion befinden oder nicht. Wenn ja, nehmen wir an, dass es sich um eine tSQLt-Transaktion handelt, und setzen einen Sicherungspunkt, sodass wir bei Bedarf darauf zurückgreifen können. Wenn nein, starten wir eine neue Transaktion. Tatsächlich erlauben wir kein Verschachteln.
Das Problem wird durch das Scheitern von Transaktionen kompliziert - es kann passieren, wenn eine Ausnahme ausgelöst wurde. Eine zum Scheitern verurteilte Transaktion kann nicht festgeschrieben und auf einen Sicherungspunkt zurückgesetzt werden. Daher müssen wir sie erneut auf die äußerste Transaktion zurücksetzen.
In Anbetracht der oben beschriebenen Punkte müssen wir die folgende Struktur verwenden:
DECLARE @isNestedTransaction BIT = CASE WHEN @@trancount > 0 THEN 'true' ELSE 'false' END; BEGIN TRY IF @isNestedTransaction = 'false' BEGIN TRANSACTION ELSE SAVE TRANSACTION SavepointName;
Lassen Sie uns den Code Stück für Stück überprüfen. Zuerst müssen wir feststellen, ob wir uns in einer Transaktion befinden oder nicht.
DECLARE @isNestedTransaction BIT = CASE WHEN @@trancount > 0 THEN 'true' ELSE 'false' END;
Nach dem Ableiten des @ isNestedTransaction-Flags können wir den TRY-Block starten und je nach Situation einen Sicherungspunkt festlegen oder eine Transaktion starten.
BEGIN TRY IF @isNestedTransaction = 'false' BEGIN TRANSACTION ELSE SAVE TRANSACTION SavepointName;
Nachdem wir etwas Nützliches getan haben, schreiben wir die Ergebnisse fest, wenn es sich um einen "echten" Prozedurlauf handelt.
Wenn es sich um einen Testfall handelt, müssen wir natürlich nichts festlegen. tSQLt setzt die Änderungen am Ende automatisch zurück.
Wenn etwas schief gelaufen ist und wir in den CATCH-Block gelangen, müssen wir feststellen, ob die Transaktion festgeschrieben werden kann oder nicht.
BEGIN CATCH DECLARE @isCommitable BIT = CASE WHEN XACT_STATE() = 1 THEN 'true' ELSE 'false' END;
Wir können nur dann zum Sicherungspunkt zurückkehren, wenn:
- Die Transaktion ist festschreibbar
- Es ist ein Testlauf, daher ist ein Sicherungspunkt vorhanden.
In allen anderen Fällen müssen wir die gesamte Transaktion zurücksetzen.
IF @isCommitable = 'true' AND @isNestedTransaction = 'true' ROLLBACK TRANSACTION SavepointName; ELSE ROLLBACK; THROW; END CATCH;
Ja, leider erhalten wir immer noch den Ausführungsfehler, wenn wir während eines Testlaufs den nicht festschreibbaren Transaktionsstatus erreicht haben.
Faketable und das Fremdschlüsselproblem
Sehen wir uns die bekannten Tabellen [Test] und [Klinik] an
Wir erinnern uns an [TrialID] FK. Welches Problem kann es verursachen? In den obigen Beispielen haben wir FakeTable auf beide Tabellen angewendet. Wenn wir es nur auf einem von ihnen verwenden, erreichen wir das folgende Setup:
Ein Versuch, einen Datensatz in [Clinic] einzufügen, kann daher fehlschlagen, selbst wenn wir Daten in der gefälschten Version von [Trial] vorbereitet haben.
[dbo].[Test_FK_Problem] failed: (Error) The INSERT statement conflicted with the FOREIGN KEY constraint "Trial_Fk". The conflict occurred in database "HabrDemo", table "dbo.tSQLt_tempobject_ba8f36353f7a44f6a9176a7d1db02493", column 'TrialID'.[16,0]{Test_FK_Problem,14}
Fazit: Alle oder keine vortäuschen. Falls keine vorhanden ist, sollten Sie natürlich eine Datenbank mit allen erforderlichen Testdaten vorbereiten.
SpyProcedure zu Systemprozeduren
Leider können wir Systemprozeduren nicht ausspionieren:
[HabrDemo].[test_test] failed: (Error) Cannot use SpyProcedure on sys.sp_help because the procedure does not exist[16,10] {tSQLt.Private_ValidateProcedureCanBeUsedWithSpyProcedure,7}
Im Semaphor-Beispiel haben wir Aufrufe der Prozedur [SendEmail] verfolgt, die von unseren Entwicklern erstellt wurde. In diesem Fall war es nicht nur durch Testen erforderlich. Es war erforderlich, eine separate Prozedur zu erstellen, da vor dem Senden einige Daten vorbereitet werden müssen. Sie sollten jedoch mental darauf vorbereitet sein, ein Interlayer-Verfahren zu schreiben, um die Testziele zu erreichen.
Vorteile
Schnelle Installation
Die tSQLt-Installation besteht aus 2 Schritten und dauert ca. 2 Minuten. Sie müssen CLR aktivieren, wenn es derzeit nicht aktiv ist, und ein einzelnes SQL-Skript ausführen. Das ist alles: Jetzt können Sie Ihre erste Testklasse hinzufügen und Testfälle schreiben.
Schnelles Lernen
tSQLt ist leicht zu erlernen. Es hat etwas mehr als einen Arbeitstag gedauert. Ich habe Kollegen gefragt und es scheint ungefähr 1 Arbeitstag für andere zu dauern. Ich bezweifle, dass es viel länger dauern kann.
Schnelle CI-Integration
Die Einrichtung der CI-Integration für unser Projekt dauerte ca. 2 Stunden. Die Zeit kann natürlich variieren, aber es ist im Allgemeinen kein Problem und es kann schnell erledigt werden.
Eine breite Palette von Instrumenten
Es ist subjektiv, aber meiner Meinung nach ist die tSQLt-Funktionalität reichhaltig und der Löwenanteil der Bedürfnisse kann dadurch abgedeckt werden. Wenn dies nicht ausreicht, können Sie das Fail-Verfahren immer für seltene und anspruchsvolle Fälle verwenden.
Bequeme Dokumentation
Offizielle Führer sind bequem und konsistent. Sie können die Verwendung von tSQLt in kurzer Zeit leicht verstehen, selbst wenn es Ihr erstes Unit-Test-Tool ist.
Ausgabe löschen
Die Testausgabe kann in einem illustrativen Textformat erfolgen:
[tSQLtDemo].[test_error_messages] failed: (Failure) Expected an error to be raised. [tSQLtDemo].[test_tables_comparison] failed: (Failure) useful and descriptive error message Unexpected/missing resultset rows! |_m_|Column1|Column2| +---+-------+-------+ |< |2 |Value2 | |= |1 |Value1 | |= |3 |Value3 | |> |2 |Value3 | +----------------------+ |Test Execution Summary| +----------------------+ |No|Test Case Name |Dur(ms)|Result | +--+------------------------------------+-------+-------+ |1 |[tSQLtDemo].[test_constraint] | 83|Success| |2 |[tSQLtDemo].[test_trial_view] | 83|Success| |3 |[tSQLtDemo].[test_error_messages] | 127|Failure| |4 |[tSQLtDemo].[test_tables_comparison]| 147|Failure| ----------------------------------------------------------------------------- Msg 50000, Level 16, State 10, Line 1 Test Case Summary: 4 test case(s) executed, 2 succeeded, 2 failed, 0 errored. -----------------------------------------------------------------------------
Es kann auch aus der Datenbank abgeleitet werden (anklickbar) ...
... oder auch als XML.
<?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite id="1" name="tSQLtDemo" tests="3" errors="0" failures="1" timestamp="2019-06-22T16:46:06" time="0.433" hostname="BLAHBLAHBLAH\SQL2017" package="tSQLt"> <properties /> <testcase classname="tSQLtDemo" name="test_constraint" time="0.097" /> <testcase classname="tSQLtDemo" name="test_error_messages" time="0.153"> <failure message="Expected an error to be raised." type="tSQLt.Fail" /> </testcase> <testcase classname="tSQLtDemo" name="test_trial_view" time="0.156" /> <system-out /> <system-err /> </testsuite> </testsuites>
Das letzte Format ermöglicht eine problemlose CI-Integration. Insbesondere verwenden wir tSQLt zusammen mit Atlassian Bamboo.
Unterstützung von redgate
Als einer der Profis kann ich die Unterstützung eines der größten DBA-Tool-Anbieter nennen - RedGate. Das SQL Server Management Studio-Plugin mit dem Namen SQL Test funktioniert von Anfang an mit tSQLt. Darüber hinaus hilft RedGate dem Hauptentwickler von tSQLt mit der Dev-Umgebung, so seine Worte in
Google-Gruppen .
Nachteile
Keine temporären Tische fälschen
tSQLt erlaubt keine gefälschten temporären Tabellen. Gedanken, im Bedarfsfall können Sie ein inoffizielles Addon verwenden. Leider funktioniert dieses Addon nur mit SQL Server 2016+.
Arbeiten Sie mit äußeren DBs
tSQLt wurde entwickelt, um mit dem Code in derselben Datenbank zu arbeiten, in der das Framework installiert ist. Daher kann es unmöglich sein, es mit einer äußeren Datenbank zu verwenden. Zumindest funktionieren Fälschungen nicht.
CREATE PROCEDURE [tSQLtDemo].[test_outer_db] AS BEGIN SELECT TOP 10 * FROM [AdventureWorks2017].[Person].[Password] EXEC tSQLt.FakeTable '[AdventureWorks2017].[Person].[Password]' SELECT TOP 10 * FROM [AdventureWorks2017].[Person].[Password] END
Es sieht so aus, als ob Behauptungen funktionieren, aber ihre Verarbeitbarkeit ist natürlich nicht garantiert.
CREATE PROCEDURE [tSQLtDemo].[test_outer_db_assertions] AS BEGIN SELECT TOP 1 * INTO
Dokumentationsfehler
Obwohl ich oben erwähnt habe, dass die Anleitungen bequem und konsistent sind, weist die Dokumentation einige Probleme auf. Es enthält veraltete Teile.
Beispiel 1. In der
Kurzanleitung wird vorgeschlagen , das Framework von SourceForge herunterzuladen.
Sie sind von SourceForge
bis 2015 umgezogen.
Beispiel 2. Das
ApplyConstraint-Handbuch verwendet ein
umfangreiches Design mit der Fail-Prozedur in einem Ausnahmebeispiel. Dies kann mit ExpectException durch einfachen und klaren Code ersetzt werden.
CREATE PROCEDURE ConstraintTests.[test ReferencingTable_ReferencedTable_FK prevents insert of orphaned rows] AS BEGIN EXEC tSQLt.FakeTable 'dbo.ReferencedTable'; EXEC tSQLt.FakeTable 'dbo.ReferencingTable'; EXEC tSQLt.ApplyConstraint 'dbo.ReferencingTable','ReferencingTable_ReferencedTable_FK'; DECLARE @ErrorMessage NVARCHAR(MAX); SET @ErrorMessage = ''; BEGIN TRY INSERT INTO dbo.ReferencingTable ( id, ReferencedTableId ) VALUES ( 1, 11 ) ; END TRY BEGIN CATCH SET @ErrorMessage = ERROR_MESSAGE(); END CATCH IF @ErrorMessage NOT LIKE '%ReferencingTable_ReferencedTable_FK%' BEGIN EXEC tSQLt.Fail 'Expected error message containing ''ReferencingTable_ReferencedTable_FK'' but got: ''',@ErrorMessage,'''!'; END END GO
Und das wird erwartet wegen ...
Teilweise Aufgabe
Von Anfang 2016 bis Juni 2019 gab es eine längere Entwicklungspause. Ja, leider wird dieses Tool teilweise aufgegeben. Die Entwicklung hat
laut GitHub 2019 langsam begonnen. Obwohl offizielle Google Groups
einen Thread haben, in dem Sebastian, der Hauptentwickler von tSQLt, nach der Zukunft des Projekts gefragt wurde. Die letzte Frage wurde am 2. März 2019 ohne Antwort gestellt.
SQL Server 2017-Problem
Die Installation von tSQLt erfordert möglicherweise einige zusätzliche Aktionen, wenn Sie SQL Server 2017 verwenden. Microsoft hat in dieser Version die erste Sicherheitsänderung seit 2012 implementiert. Das Flag "CLR strikte Sicherheit" auf Serverebene wurde hinzugefügt. Dieses Flag verbietet die Erstellung von nicht signierten Assemblys (sogar SAFE). Eine ausführliche Beschreibung verdient einen separaten Artikel (und zum Glück haben wir
bereits einen guten; siehe auch die folgenden Artikel in der Reihenfolge. Seien Sie nur mental darauf vorbereitet.
Natürlich könnte ich dieses Problem den "Fallstricken" zuschreiben, aber dieses Problem kann von den tSQLt-Entwicklern behoben werden.
Das GitHub-Problem wurde bereits behoben . Es wurde jedoch seit Oktober 2017 nicht mehr gelöst.
Alternativen (±) für andere DBMS
tSQLt ist nicht einzigartig. Obwohl Sie es aufgrund von CLR- und T-SQL-Nuancen nicht in anderen DBMS verwenden können, finden Sie dennoch etwas Ähnliches. Es ist erwähnenswert, dass diese Alternativen tSQLt nicht sehr nahe kommen, also meine ich einen SQL-basierten Ansatz.
Beispielsweise können PostgreSQL-Benutzer
pgTAP ausprobieren. Es ist ein gut entwickeltes und aktiv entwickeltes Tool, das natives PL / pgSQL für Tests und das TAP-Ausgabeformat verwendet. Das ähnliche Tool
MyTap kann Ihnen bei Tests unter MySQL helfen. Dieses Framework ist etwas weniger funktional als pgTAP, kann aber dennoch nützlich sein. Und es befindet sich auch in der aktiven Entwicklung. Wenn Sie ein zufriedener Oracle-Benutzer sind, haben Sie die Möglichkeit, das sehr leistungsstarke Tool
utPLSQL zu verwenden . Es entwickelt sich sehr aktiv und bietet eine Vielzahl von Funktionen.
Fazit
Ich wollte 2 Ideen vermitteln:
Das erste: die Nützlichkeit des DB-Code-Testens. Es ist nicht wichtig, ob Sie SQL Server, Oracle, MySQL oder etwas anderes verwenden. Wenn Ihre Datenbank nicht getestete Logik enthält, gehen Sie Risiken ein. Wie alle anderen Fehler in allen anderen Codes können DB-Code-Fehler das Produkt und das Unternehmen, das es bereitstellt, beschädigen.
Das zweite: die Werkzeugauswahl. Für diejenigen, die mit SQL Server arbeiten, verdient tSQLt, auch wenn es kein 100% iger Gewinner ist, sicherlich Aufmerksamkeit. Trotz langsamer Entwicklung und einiger Probleme ist dies immer noch ein praktischer Rahmen, der Ihre Arbeit erheblich erleichtern könnte.
Quellen, die mir geholfen haben (nicht erschöpfende Liste)