Wie TDD, dass die richtigen Ergebnisse zurückgegeben werden

12

Ich starte ein neues Projekt und bemühe mich sehr, TDD zu verwenden, um das Design voranzutreiben. Ich habe jahrelang Druck gemacht und endlich die Genehmigung erhalten, die zusätzliche Zeit für dieses Projekt zu verwenden, während ich lerne, wie man es richtig macht.

Dies ist ein neues Modul, um in ein bestehendes System eingebunden zu werden. Gegenwärtig erfolgt der gesamte Datenzugriff über Webservices, die größtenteils nur ein dünner Wrapper für gespeicherte Datenbankprozeduren sind.

Eine Voraussetzung ist, dass ich für ein bestimmtes Geschäft alle Bestellungen zurückschicke, die für diese Anwendung als gültig gelten. Eine Bestellung gilt als gültig, wenn ihr Versanddatum innerhalb eines bestimmten Bereichs vom Eröffnungsdatum des Geschäfts liegt (dies gilt für neue Geschäfte).

Jetzt kann ich diese Logik nicht in den Anwendungscode einfügen, da ich nicht eine Million Bestellungen zurückbringe, nur um das Dutzend zu erhalten, das für diesen Shop gelten könnte, da die oben genannte Einschränkung gilt.

Ich dachte, ich könnte den Datumsbereich an eine GetValidPOs-Prozedur übergeben und diese Werte verwenden, um die gültigen POs zurückzugeben. Aber was ist, wenn wir eine weitere Anforderung zu einer gültigen Bestellung hinzufügen?

Und wie teste ich das und stelle sicher, dass es weiterhin funktioniert? Wir verwenden kein ORM und es ist unwahrscheinlich, dass es passiert. Und ich kann die DB in meinem Test nicht aufrufen.

Ich stecke fest.

Mein anderer Gedanke ist, einige Mocks zu haben, die gültige Daten zurückgeben, andere, die ungültige Daten zurückgeben, und das lokale Repository eine Ausnahme auslösen zu lassen, wenn ungültige Daten von GetValidPOs proc (oder das zum Testen verwendete Mock).

Macht das Sinn? Oder gibt es einen besseren Weg?

UPDATE: Ich kann anscheinend EF verwenden. Jetzt muss ich nur noch herausfinden, wie man es verwendet und testbar macht, während ich mich immer noch auf gespeicherte Prozeduren verlassen kann und die Schwierigkeit, Daten auf mehrere Datenbanken verteilt zu haben.

CaffGeek
quelle
Warum können Sie aus Neugier nicht einfach die gültigen Bestellungen mit einer einfachen SQL-Anweisung auswählen? (Diese Frage oder die Antwort impliziert keine Lösung.)
scarfridge

Antworten:

7

Dies ist ein großer Nachteil von gespeicherten Prozeduren im Zeitalter von TDD. Selbst jetzt haben sie einige echte Vorteile, aber per Definition ist jeder Test, der einen gespeicherten Prozess ausführt, kein Komponententest. Es ist bestenfalls ein Integrationstest.

Die übliche Lösung unter der Annahme, dass sich die Architektur nicht dahingehend ändern kann, dass stattdessen ein ORM verwendet wird, besteht darin, diese Tests nicht in die Komponententestsuite aufzunehmen. Stellen Sie die Tests stattdessen in eine Integrationssuite. Sie können den Test immer noch ausführen, wenn Sie möchten, dass er funktioniert, aber da die Kosten für das Einrichten des Tests (Initialisieren einer Datenbank mit den richtigen Testdaten) hoch sind und die Ressourcen des Unit-Test-Agenten Ihres Build-Bot möglicherweise nicht betroffen sind Zugriff haben, sollte es nicht in der Unit-Testsuite sein.

Sie können weiterhin Code, der die Daten benötigt, einem Komponententest unterziehen, indem Sie alles, was Sie nicht einem Komponententest unterziehen können (ADO.NET-Klassen), in eine DAO-Klasse abstrahieren, die Sie dann verspotten können. Sie können dann überprüfen, ob die erwarteten Aufrufe durch den Verbrauch von Code getätigt wurden, und das reale Verhalten reproduzieren (z. B. keine Ergebnisse finden), um verschiedene Anwendungsfälle zu testen. Die eigentliche Einrichtung von SqlCommand zum Aufrufen des gespeicherten Prozesses ist jedoch so ziemlich das Letzte, was Sie testen können, indem Sie die Befehlserstellung von der Befehlsausführung trennen und den Befehlsausführer verspotten. Wenn dies nach einer starken Trennung von Bedenken klingt, kann es sein; Denken Sie daran: "Es gibt kein Problem, das nicht durch eine andere Indirektionsebene gelöst werden kann, außer durch zu viele Indirektionsebenen." Irgendwann muss man sagen "genug; ich kann das einfach nicht testen, wir '

Andere Optionen:

  • Testen Sie den gespeicherten Prozess mit einer "kurzlebigen" DBMS-Instanz wie SQLite. Bei Verwendung eines ORM ist dies in der Regel einfacher, der Test kann jedoch "im Arbeitsspeicher" (oder mit einer voreingestellten Datenbankdatei, die der Testsuite beigefügt ist) durchgeführt werden. Es ist immer noch kein Komponententest, kann jedoch mit einem hohen Grad an Isolation ausgeführt werden (das DBMS ist Teil des laufenden Prozesses und nichts, mit dem Sie eine Remote-Verbindung herstellen, das sich möglicherweise in der Mitte der widersprüchlichen Testsuite einer anderen Person befindet). Der Nachteil besteht darin, dass Änderungen am gespeicherten Prozess in der Produktion vorgenommen werden können, ohne dass der Test die Änderung widerspiegelt. Sie müssen also diszipliniert vorgehen, um sicherzustellen, dass die Änderung zuerst in einer Testumgebung vorgenommen wird.

  • Erwägen Sie ein Upgrade auf ein ORM. Ein ORM mit einem Linq-Anbieter (praktisch alle gängigen Anbieter haben einen) würde es Ihnen ermöglichen, die Abfrage als Linq-Anweisung zu definieren. Diese Anweisung kann dann an ein verspottetes Repository übergeben werden, das über eine speicherinterne Sammlung von Testdaten verfügt, auf die es angewendet werden kann. Auf diese Weise können Sie überprüfen, ob die Abfrage korrekt ist, ohne die Datenbank zu berühren (Sie sollten die Abfrage dennoch in einer Integrationsumgebung ausführen, um zu testen, ob der Linq-Anbieter die Abfrage korrekt verarbeiten kann).

KeithS
quelle
2
-1 weil TDD! = Komponententest. Völlig in Ordnung, um Integrationstests bei TDD einzuschließen.
Steven A. Lowe
Unit Testing ist eine Teilmenge der testgetriebenen Entwicklung. In einer testgetriebenen Entwicklung würden Sie ein laufendes Gerüst Ihres Systems erstellen und dann Unit-, Integrations- und Funktionstests auf diesem System ausführen. Ihre Integrations-, Einheiten- oder Abnahmetests schlagen fehl, Sie lassen sie bestehen und schreiben weitere Tests.
CodeART
1
Ich verstehe das alles von euch beiden. Wo habe ich gesagt, dass es ein Integrationstest sein muss, der bedeutet, dass Sie es nicht TDD können? Mein Punkt war, dass eine gespeicherte Prozedur nicht isoliert getestet werden kann. Das ist etwas, was Sie tun möchten, um so viel von Ihrer Codebasis wie möglich zu erreichen. Das Testen von SPs erfordert komplexere Integrationstests mit längerer Laufzeit. Obwohl sie immer noch besser sind als manuelle Tests, kann die Ausführung einer Test-Suite mit hohem Integrationsaufwand Stunden dauern und sich nachteilig auf die CI-Bemühungen auswirken.
KeithS
Für SP-Tests ist häufig auch ein bestimmter Datensatz in der Testdatenbank erforderlich. Der Code, mit dem die Datenbank in den richtigen Zustand versetzt wird, um die erwarteten Ergebnisse zu erzielen, ist sehr oft langsamer und um ein Vielfaches länger als der Code, den Sie tatsächlich ausführen. Dies erhöht die zeitliche Komplexität der Testsuite weiter und häufig muss der Aufbau für jeden einzelnen Test wiederholt werden (und es sollten wahrscheinlich mehrere für jeden SP vorhanden sein, um zu testen, ob jede funktionale Anforderung der darin enthaltenen Abfrage erfüllt ist).
KeithS
Gespeicherte Prozeduren können isoliert getestet werden. Wie würden sie sonst validiert werden? Für Transact SQL gibt es tSQLt ( tsqlt.org )
Kevin Cline
4

Mein Rat ist, sich zu teilen und zu erobern . Vergessen Sie vorerst Datenbank und Persistenz und konzentrieren Sie sich darauf, gefälschte Implementierungen Ihrer Repositorys oder Datenzugriffsobjekte zu testen.

Jetzt kann ich diese Logik nicht in den Anwendungscode einfügen, da ich nicht eine Million Bestellungen zurückbringe, nur um das Dutzend zu erhalten, das für diesen Shop gelten könnte, da die oben genannte Einschränkung gilt.

Ich würde das Repository verspotten, das Bestellungen zurückgibt. Erstellen Sie ein Modell mit zwanzig ungeraden Bestellungen.

Ich dachte, ich könnte den Datumsbereich an eine GetValidPOs-Prozedur übergeben und diese Werte verwenden, um die gültigen POs zurückzugeben. Aber was ist, wenn wir eine weitere Anforderung zu einer gültigen Bestellung hinzufügen?

Unterbrechen Sie einen Aufruf von GetValidPOs, damit er Ihre Scheinprozedur anstelle der Datenbankprozedur aufruft.

Und wie teste ich das und stelle sicher, dass es weiterhin funktioniert? Wir verwenden kein ORM und es ist unwahrscheinlich, dass es passiert. Und ich kann die DB in meinem Test nicht aufrufen.

Sie benötigen einen Unit-Test, um sicherzustellen, dass korrekte Daten von einem Mock zurückgegeben werden.

Sie benötigen auch einen Integrationstest, um sicherzustellen, dass korrekte Daten aus einer Datenbank zurückgegeben werden. Der Integrationstest würde einige Konfigurations- und Aufräumarbeiten erfordern. Bevor Sie beispielsweise den Integrationstest ausführen, müssen Sie Ihre Datenbank durch Ausführen eines Skripts sortieren. Stellen Sie sicher, dass Ihr Skript funktioniert hat. Fragen Sie die Datenbank ab, indem Sie Ihre gespeicherten Prozeduren aufrufen. Stellen Sie sicher, dass Ihre Ergebnisse korrekt sind. Bereinigen Sie die Datenbank.

Mein anderer Gedanke ist, einige Mocks zu haben, die gültige Daten zurückgeben, andere, die ungültige Daten zurückgeben, und das lokale Repository eine Ausnahme auslösen zu lassen, wenn ungültige Daten von GetValidPOs proc (oder das zum Testen verwendete Mock).

Wie ich bereits sagte, benötigen Sie einen Mock, der mindestens einige Daten zurückgibt, die Sie abfragen können.

Wenn Sie Daten abfragen, möchten Sie sicherstellen, dass Ihr System ordnungsgemäß mit Ausnahmen umgehen kann. Daher verspotten Sie das Verhalten, sodass es in bestimmten Szenarien Ausnahmen auslöst. Anschließend schreiben Sie Tests, um sicherzustellen, dass Ihr System diese Ausnahmen ordnungsgemäß verarbeiten kann.

CodeART
quelle
Das versuche ich zu tun. Es ist einfach schwierig, eine echte Implementierung zu schreiben, die genauso funktioniert wie die Scheinimplementierung, da unser Datenzugriff der Verwendung eines ORM nicht förderlich ist. Der Großteil der Daten, die ich benötige, befindet sich in mehreren Systemen und soll über Webservices abgerufen werden können ... auch beim Aktualisieren.
CaffGeek
0

So wie Unit-Tests für Java oder Javascript das Schreiben von Unit-Tests in der Java-Sprache und Unit-Tests für JavaScript-Funktionen mit Javascript bedeuten, bedeutet das Schreiben automatisierter Tests, die Sie zum Schreiben gespeicherter Prozeduren anregen, dass die gesuchte Unit-Test-Bibliothek auf gespeichert basiert Verfahren.

Anders ausgedrückt: Verwenden Sie gespeicherte Prozeduren, um gespeicherte Prozeduren zu testen, weil:

  • Da Sie sich in der Verfahrenssprache entwickeln, sollten Sie die Fähigkeit haben, Ihre Tests in der Verfahrenssprache zu schreiben
  • Das Schreiben von Tests in Ihrer Verfahrenssprache erhöht Ihre Kenntnisse in der Verfahrenssprache, was wiederum Ihrer Produktentwicklung hilft
  • Sie haben direkten Zugriff auf alle Tools, die Ihre DB bereitstellt, und Sie können diese Tools auch verwenden, um Ihre Komponententests so einfach wie möglich zu gestalten
  • Komponententests, die in derselben Datenbank gespeichert sind wie die zu testenden Prozeduren, sind schnell (ähnlich wie die Geschwindigkeiten bei Komponententests), da Sie keine Systemgrenzen überschreiten

Genau wie bei TDD in einer OO-Sprache soll bei einem Unit-Test nur eine Reihe von Daten eingerichtet werden, um zu testen, was für die Prozedur erforderlich ist (Minimalismus, nur das, was für einfache Tests erforderlich ist). Das Ergebnis ist, dass Sie für jede gespeicherte Prozedur mehrere einfache Komponententests durchführen müssen. Diese einfachen Tests sind einfacher zu warten als komplizierte Tests, die von einem großen Dataset abhängen, das nicht so einfach den tatsächlichen Anforderungen des Tests entspricht.

Lance Kind
quelle