Warum benötige ich Komponententests zum Testen von Repository-Methoden?

19

Ich muss Teufel spielen, die sich ein bisschen für diese Frage einsetzen, weil ich sie wegen mangelnder Erfahrung nicht gut verteidigen kann. Hier ist der Deal, ich verstehe konzeptionell die Unterschiede zwischen Unit-Test und Integrationstest. Wenn sich ein Unit-Test speziell auf Persistenzmethoden und das Repository konzentriert, wird möglicherweise über ein Framework wie Moq ein Mock verwendet, um zu bestätigen, dass eine gesuchte Bestellung wie erwartet zurückgegeben wurde.

Angenommen, ich habe den folgenden Komponententest erstellt:

[TestMethod]
public void GetOrderByIDTest()
{
   //Uses Moq for dependency for getting order to make sure 
   //ID I set up in 'Arrange' is same one returned to test in 'Assertion'
}

Wenn ich es also einrichte OrderIdExpected = 5und mein Scheinobjekt 5als ID zurückgibt, besteht mein Test. Ich verstehe es. Ich habe den Code getestet , um sicherzustellen, dass meine Code-Preforms das erwartete Objekt und die erwartete ID und nicht etwas anderes zurückgeben.

Das Argument, das ich bekommen werde, ist folgendes:

"Warum nicht einfach die Komponententests überspringen und Integrationstests durchführen? Es ist wichtig, die gespeicherte Datenbankprozedur und Ihren Code zusammen zu testen . Es scheint zu viel zusätzliche Arbeit zu sein, Komponententests und Integrationstests durchzuführen, wenn ich letztendlich wissen möchte, ob die Datenbank aufruft und der Code funktionieren. Ich weiß, dass die Tests länger dauern, aber sie müssen ausgeführt und getestet werden, so dass es für mich sinnlos erscheint, beides zu haben. Testen Sie nur, worauf es ankommt. "

Ich könnte es mit einer Lehrbuchdefinition verteidigen wie: "Nun, das ist ein Integrationstest und wir müssen den Code separat als Unit-Test testen und, yada, yada, yada ..." Dies ist ein Fall, in dem eine puristische Erklärung der Praktiken erfolgt Die Realität verliert sich. Ich stoße manchmal darauf und wenn ich die Argumentation hinter Unit-Testing-Code, der letztendlich von externen Abhängigkeiten abhängt, nicht verteidigen kann, kann ich keine Argumentation dafür abgeben.

Jede Hilfe zu dieser Frage wird sehr geschätzt, danke!

atconway
quelle
2
Ich sage, senden Sie es direkt an die Benutzer testen ... sie werden die Anforderungen sowieso ändern ...
Nathan Hayfield
+1 für den Sarkasmus, um die Dinge von Zeit zu Zeit leichter zu machen
atconway
2
Die einfache Antwort ist, dass jede Methode x Anzahl von Kantenfällen haben kann (Angenommen, Sie möchten positive IDs, IDs von 0 und negative IDs testen). Wenn Sie einige Funktionen testen möchten, die diese Methode verwenden, die selbst 3 Kantenfälle enthält, müssen Sie 9 Testfälle schreiben, um jede Kombination von Kantenfällen zu testen. Wenn Sie sie isolieren, brauchen Sie nur 6 zu schreiben. Außerdem geben Ihnen die Tests eine genauere Vorstellung davon, warum etwas kaputt gegangen ist. Möglicherweise gibt Ihr Repository bei einem Fehler null zurück, und die null-Ausnahme wird mehrere hundert Zeilen im Code ausgegeben.
Rob
Ich denke, Sie sind zu streng bei der Definition eines "Einheitentests". Was ist eine "Arbeitseinheit" für eine Repository-Klasse?
Caleb
Und stellen Sie sicher, dass Sie Folgendes in Betracht ziehen: Wenn Ihr Komponententest alles ausblendet, was Sie zu testen versuchen, was testen Sie wirklich?
Caleb

Antworten:

20

Unit-Tests und Integrationstests dienen unterschiedlichen Zwecken.

Unit-Tests verifizieren die Funktionalität Ihres Codes ... dass Sie das, was Sie von der Methode erwarten, zurückerhalten, wenn Sie sie aufrufen. Integrationstests testen, wie sich der Code verhält, wenn er als System kombiniert wird. Sie würden nicht erwarten, dass Unit-Tests das Systemverhalten bewerten, und Sie würden auch nicht erwarten, dass Integrationstests bestimmte Ausgaben einer bestimmten Methode verifizieren.

Unit-Tests sind bei korrekter Durchführung einfacher einzurichten als Integrationstests. Wenn Sie sich ausschließlich auf Integrationstests verlassen, werden Ihre Tests wie folgt ausgeführt:

  1. Seien Sie schwieriger zu schreiben, insgesamt,
  2. Seien Sie aufgrund aller erforderlichen Abhängigkeiten spröder
  3. Bieten Sie weniger Code-Abdeckung.

Fazit: Verwenden Sie Integrationstests , um zu überprüfen , dass die Verbindungen zwischen den Objekten richtig funktionieren, aber Anlehnen Einheit testen ersten funktionale Anforderungen zu überprüfen.


Trotzdem müssen Sie für bestimmte Repository-Methoden möglicherweise keine Komponententests durchführen. Unit - Tests sollten nicht auf getan werden trivial Methoden; Wenn Sie lediglich eine Anforderung für ein Objekt an den von ORM generierten Code übergeben und ein Ergebnis zurückgeben, müssen Sie dies in den meisten Fällen nicht in einem Komponententest durchführen. ein integrationstest ist ausreichend.

Robert Harvey
quelle
Ja, danke für die schnelle Antwort, ich weiß das zu schätzen. Meine einzige Kritik an der Antwort ist, dass sie in das Anliegen meines letzten Absatzes fällt. Meine Opposition wird immer noch abstrakte Erklärungen und Definitionen hören und keine tieferen Argumente. Könnten Sie zum Beispiel eine Begründung für meinen Anwendungsfall des Testens der gespeicherten Prozedur / DB in Bezug auf meinen Code angeben und warum sind Komponententests in Bezug auf diesen bestimmten Anwendungsfall immer noch wertvoll ?
Atconway
1
Wenn der SP nur ein Ergebnis aus der Datenbank zurückgibt, ist möglicherweise ein Integrationstest ausreichend. Wenn nicht-triviale Logik enthalten ist, werden Komponententests angezeigt. Siehe auch stackoverflow.com/questions/1126614/… und msdn.microsoft.com/en-us/library/aa833169(v=vs.100).aspx (SQL Server-spezifisch).
Robert Harvey
12

Ich bin auf der Seite der Pragmatiker. Testen Sie Ihren Code nicht zweimal.

Wir schreiben nur Integrationstests für unsere Repositories. Diese Tests sind nur von einer einfachen Testkonfiguration abhängig, die für eine speicherinterne Datenbank ausgeführt wird. Ich denke, sie bieten alles, was ein Komponententest leistet, und noch viel mehr.

  1. Sie können Unit-Tests bei TDD ersetzen. Bevor Sie mit dem eigentlichen Code beginnen können, müssen Sie noch ein paar Testcode-Boilerplates schreiben. Mit dem Red / Green / Refactor-Ansatz funktioniert dies jedoch sehr gut, sobald alles eingerichtet ist.
  2. Sie testen den realen Code des Repositorys - den Code, der in SQL-Zeichenfolgen oder ORM-Befehlen enthalten ist. Es ist wichtiger zu überprüfen, ob die Abfrage korrekt ist, als zu überprüfen, ob Sie tatsächlich eine Zeichenfolge an einen StatementExecutor gesendet haben.
  3. Sie eignen sich hervorragend als Regressionstests. Wenn sie fehlschlagen, liegt immer ein echtes Problem vor, z. B. eine nicht berücksichtigte Änderung des Schemas.
  4. Sie sind beim Ändern des Datenbankschemas unverzichtbar. Sie können sicher sein, dass Sie das Schema nicht so geändert haben, dass Ihre Anwendung beschädigt wird, solange der Test bestanden wurde. (In diesem Fall ist der Komponententest unbrauchbar, da der Komponententest weiterhin erfolgreich ist, wenn die gespeicherte Prozedur nicht mehr vorhanden ist.)
Seidenschwanz
quelle
Sehr pragmatisch. Ich habe den Fall erlebt, dass sich die speicherinterne Datenbank aufgrund eines geringfügig anderen ORM tatsächlich anders (nicht in Bezug auf die Leistung) als meine reale Datenbank verhält. Kümmere dich darum in deinen Integrationstests.
Marcel
1
@Marcel, ich bin darauf gestoßen. Ich habe es gelöst, indem ich manchmal alle meine Tests mit einer echten Datenbank abgeglichen habe.
Winston Ewert
4

Unit-Tests bieten einen Grad an Modularität, den Integrationstests (von Entwurf her) nicht bieten können. Wenn ein System überarbeitet oder neu zusammengestellt wird (und dies wird passieren), können Komponententests häufig wiederverwendet werden, während Integrationstests häufig neu geschrieben werden müssen. Integrationstests, die versuchen, die Arbeit von etwas zu erledigen, das in einem Komponententest enthalten sein sollte, sind oft zu viel, was es schwierig macht, sie zu warten.

Darüber hinaus bietet das Testen von Einheiten die folgenden Vorteile:

  1. Mit Unit-Tests können Sie einen fehlgeschlagenen Integrationstest (möglicherweise aufgrund eines Regressionsfehlers) schnell auflösen und die Ursache ermitteln. Darüber hinaus wird das Problem schneller an das gesamte Team weitergegeben als in Diagrammen oder anderen Dokumentationen.
  2. Unit-Tests können als Beispiele und Dokumentation (eine Art Dokumentation, die tatsächlich kompiliert wird ) zusammen mit Ihrem Code dienen. Im Gegensatz zu anderen Dokumentationen wissen Sie sofort, wenn es veraltet ist.
  3. Unit-Tests können als Basisleistungsindikatoren dienen, wenn versucht wird, größere Leistungsprobleme zu lösen, während Integrationstests häufig eine Vielzahl von Instrumenten erfordern, um das Problem zu finden.

Sie können Ihre Arbeit aufteilen (und einen DRY-Ansatz durchsetzen), indem Sie sowohl Unit- als auch Integrationstests verwenden. Verlassen Sie sich bei kleinen Funktionseinheiten einfach auf Komponententests, und wiederholen Sie keine Logik, die bereits in einem Komponententest enthalten ist, in einem Integrationstest. Dies führt häufig zu weniger Arbeit (und damit zu weniger Nacharbeit).

Zachary Yates
quelle
4

Tests sind nützlich, wenn sie unterbrochen werden: Proaktiv oder reaktiv.

Unittests sind proaktiv und können eine kontinuierliche Funktionsüberprüfung sein, während Integrationstests auf inszenierte / gefälschte Daten reagieren.

Wenn das betrachtete System mehr von Daten als von der Berechnungslogik abhängig ist, sollten Integrationstests an Bedeutung gewinnen.

Wenn wir zum Beispiel ein ETL-System erstellen, werden die relevantesten Tests Daten betreffen (inszeniert, gefälscht oder live). Das gleiche System wird einige Unit-Tests haben, aber nur in Bezug auf Validierung, Filterung usw.

Das Repository-Muster sollte keine Computer- oder Geschäftslogik enthalten. Es ist sehr nah an der Datenbank. Das Repository steht jedoch auch in enger Beziehung zur Geschäftslogik, die es verwendet. Es geht also darum, ein GLEICHGEWICHT zu erzielen.

Unit Tests können testen, wie sich das Repository verhält. Integrationstests können testen, WAS WIRKLICH PASSIERT, als das Repository aufgerufen wurde.

Nun, "WAS WIRKLICH PASSIERT" scheint sehr verlockend zu sein. Dies kann jedoch sehr teuer sein, wenn Sie ständig und wiederholt ausgeführt werden. Hier gilt die klassische Definition von Sprödigkeitstests.

Die meiste Zeit finde ich es einfach gut genug, Unit-Test für ein Objekt zu schreiben, das das Repository verwendet. Auf diese Weise testen wir, ob die richtige Repository-Methode aufgerufen wurde und ob die richtigen verspotteten Ergebnisse zurückgegeben werden.

Otpidus
quelle
Ich mag das: "Nun," WAS WIRKLICH PASSIERT IST "scheint sehr verlockend zu sein. Aber dies könnte sehr teuer sein, um konsequent und wiederholt zu laufen."
Atconway,
2

Es gibt drei verschiedene Dinge, die getestet werden müssen: die gespeicherte Prozedur, den Code, der die gespeicherte Prozedur aufruft (dh Ihre Repository-Klasse), und den Consumer. Die Aufgabe des Repositorys besteht darin, eine Abfrage zu generieren und den zurückgegebenen Datensatz in ein Domänenobjekt zu konvertieren. Es gibt genug Code, um Unit-Tests zu unterstützen, die von der tatsächlichen Ausführung der Abfrage und der Erstellung des Datensatzes getrennt sind.

Also (dies ist ein sehr sehr vereinfachtes Beispiel):

interface IOrderRepository
{
    Order GetOrderByID(Guid id);
}

class OrderRepository : IOrderRepository
{
    private readonly ISqlExecutor sqlExecutor;
    public OrderRepository(ISqlExecutor sqlExecutor)
    {
        this.sqlExecutor = sqlExecutor;
    }

    public Order GetOrderByID(Guid id)
    {
        var sql = "SELECT blah, blah FROM Order WHERE OrderId = @p0";
        var dataset = this.sqlExecutor.Execute(sql, p0 = id);
        var result = this.orderFromDataset(dataset);
        return result;
    }
}

Übergeben Sie dann beim Testen OrderRepositoryein verspottetes ISqlExecutorObjekt, und überprüfen Sie, ob das zu testende Objekt in der richtigen SQL (das ist sein Job) übergeben wurde, und geben Sie ein korrektes OrderObjekt mit einem bestimmten Ergebnisdatensatz zurück (ebenfalls verspottet). Wahrscheinlich ist der einzige Weg, die konkrete SqlExecutorKlasse zu testen, mit Integrationstests, fair genug, aber das ist eine Thin-Wrapper-Klasse und wird sich selten ändern, so große Sache.

Sie müssen Ihre gespeicherten Prozeduren auch noch Unit-testen.

Scott Whitlock
quelle
0

Ich kann dies auf einen sehr einfachen Gedankengang reduzieren: Bevorzugen Sie Unit-Tests, weil sie das Auffinden von Fehlern erleichtern. Und tun Sie dies konsequent, weil Inkonsistenzen Verwirrung stiften.

Weitere Gründe leiten sich aus denselben Regeln ab, die die OO-Entwicklung leiten, da sie auch für Tests gelten. Zum Beispiel das Prinzip der einmaligen Verantwortung.

Wenn einige Komponententests als nicht wert erscheinen, ist dies möglicherweise ein Zeichen dafür, dass das Thema nicht wirklich wert ist. Oder vielleicht ist seine Funktionalität abstrakter als sein Gegenstück, und die Tests dafür (sowie sein Code) können auf eine höhere Ebene abstrahiert werden.

Wie bei allem gibt es Ausnahmen. Und das Programmieren ist immer noch eine Kunstform, so dass viele Probleme unterschiedlich genug sind, um jeden für den besten Ansatz zu bewerten.

CL22
quelle