TDD mit Repository-Muster

10

In meinem neuen Projekt habe ich beschlossen, es mit TDD zu versuchen. Und ganz am Anfang bin ich auf ein Problem gestoßen. Das erste, was ich in meiner Anwendung tun möchte, ist die Möglichkeit, Daten aus Datenquellen zu lesen. Zu diesem Zweck möchte ich ein Repository-Muster verwenden. Und nun:

  • Wenn der Test für die echte Implementierung der Repository-Schnittstelle bestimmt ist, werde ich eine Klasse testen, die Zugriff auf die Datenbank hat, und ich weiß, dass ich das vermeiden sollte.
  • Wenn Test für nicht echte Implementierung des Repository-Musters sind, werde ich gut testen ... nur verspotten. In diesen Komponententests wird kein Produktionscode getestet.

Ich denke seit zwei Tagen darüber nach und kann immer noch keine vernünftige Lösung finden. Was sollte ich tun?

Thaven
quelle

Antworten:

11

Ein Repository übersetzt von Ihrer Domain in Ihr DAL-Framework wie NHibernate oder Doctrine oder in Ihre SQL-ausführenden Klassen. Dies bedeutet, dass Ihr Repository Methoden für dieses Framework aufruft, um seine Aufgaben auszuführen: Ihr Repository erstellt die Abfragen, die zum Abrufen der Daten erforderlich sind. Wenn Sie kein ORM-Framework verwenden (ich hoffe, Sie sind ...), ist das Repository der Ort, an dem rohe SQL-Anweisungen erstellt werden.

Die grundlegendste dieser Methoden ist das Speichern: In den meisten Fällen wird das Objekt einfach aus dem Repository an die Arbeitseinheit (oder die Sitzung) übergeben.

public void Save(Car car)
{
    session.Save(car);
}

Aber schauen wir uns ein anderes Beispiel an, zum Beispiel das Abrufen eines Autos anhand seiner ID. Es könnte so aussehen

public function GetCarWithId(String id)
{
    return Session.QueryOver<Car>()
                    .Where(x => x.Id == id)
                    .SingleOrDefault();
}

Immer noch nicht zu komplex, aber Sie können sich vorstellen, dass dies unter verschiedenen Bedingungen (lassen Sie mir alle Autos nach 2010 für alle Marken der Volkswagen-Gruppe herstellen) schwierig wird. Auf echte TDD-Art müssen Sie dies also testen. Es gibt verschiedene Möglichkeiten, dies zu tun.

Option 1: Verspotten Sie die Aufrufe des ORM-Frameworks

Natürlich können Sie das Session-Objekt verspotten und einfach behaupten, dass die richtigen Aufrufe getätigt werden. Während dies das Repository testet, ist es nicht wirklich testgetrieben , da Sie nur testen, ob das Repository intern so aussieht, wie Sie es möchten. Der Test sagt im Grunde, dass der Code so aussehen sollte. Trotzdem ist es ein gültiger Ansatz, aber es scheint, dass diese Art von Test sehr wenig Wert hat.

Option 2: Erstellen Sie die Datenbank aus den Tests (neu)

Einige DAL-Frameworks bieten Ihnen die Möglichkeit, die vollständige Struktur der Datenbank basierend auf den Zuordnungsdateien zu erstellen, die Sie erstellen, um die Domäne den Tabellen zuzuordnen. Bei diesen Frameworks besteht die Möglichkeit zum Testen von Repositorys häufig darin, im ersten Schritt des Tests die Datenbank mit einer In-Memory-Datenbank zu erstellen und der In-Memory-Datenbank Objekte mithilfe des DAL-Frameworks hinzuzufügen. Danach können Sie das Repository in der In-Memory-Datenbank verwenden, um zu testen, ob die Methoden funktionieren. Diese Tests sind langsamer, aber sehr gültig und führen zu Ihren Tests. Es erfordert eine gewisse Zusammenarbeit von Ihrem DAL-Framework.

Option 3: Test an einer tatsächlichen Datenbank

Ein anderer Ansatz besteht darin, eine tatsächliche Datenbank zu testen und die unittest zu isolieren. Sie können dies auf verschiedene Arten tun: Umgeben Sie Ihre Tests mit einer Transaktion, bereinigen Sie sie manuell (würde nicht als sehr schwer zu warten empfehlen), erstellen Sie die Datenbank nach jedem Schritt vollständig neu ... Abhängig von der Anwendung, die Sie erstellen, kann dies oder möglicherweise nicht machbar sein. In meinen Anwendungen kann ich eine lokale Entwicklungsdatenbank vollständig aus der Quellcodeverwaltung erstellen, und meine Unittests in Repositorys verwenden Transaktionen, um die Tests vollständig voneinander zu isolieren (offene Transaktion, Daten einfügen, Test-Repository, Rollback-Transaktion). Jeder Build richtet zuerst die lokale Entwicklungsdatenbank ein und führt dann transaktionsisolierte Unittests für die Repositorys in dieser lokalen Entwicklungsdatenbank durch. Es'

Testen Sie den DAL nicht

Wenn Sie ein DAL-Framework wie NHibernate verwenden, müssen Sie dieses Framework nicht testen. Sie können Ihre Zuordnungsdateien testen, indem Sie ein Domänenobjekt speichern, abrufen und dann vergleichen, um sicherzustellen, dass alles in Ordnung ist (deaktivieren Sie unbedingt jede Art von Caching), aber es ist nicht so erforderlich wie viele andere Tests, die Sie schreiben sollten. Ich mache dies meistens für Sammlungen von Eltern mit Bedingungen für die Kinder.

Wenn Sie die Rückgabe Ihrer Repositorys testen, können Sie einfach überprüfen, ob eine identifizierende Eigenschaft Ihres Domänenobjekts übereinstimmt. Dies kann eine ID sein, aber in Tests ist es oft vorteilhafter, eine vom Menschen lesbare Eigenschaft zu überprüfen. In "Hol mir alle Autos, die nach 2010 hergestellt wurden ..." könnte einfach überprüft werden, ob fünf Autos zurückgegeben wurden und die Nummernschilder "Liste hier einfügen" sind. Ein zusätzlicher Vorteil ist, dass Sie gezwungen sind, über das Sortieren nachzudenken, UND Ihr Test die Sortierung automatisch erzwingt. Sie wären überrascht, wie viele Anwendungen entweder mehrmals sortieren (aus der Datenbank sortiert zurückgeben, vor dem Erstellen eines Ansichtsobjekts sortieren und dann das Ansichtsobjekt sortieren, alle für alle Fälle auf derselben Eigenschaft ) oder implizit davon ausgehen, dass das Repository sortiert und versehentlich entfernt wird das war irgendwo auf dem Weg und brach die Benutzeroberfläche.

'Unit Test' ist nur ein Name

Meiner Meinung nach sollten Unit-Tests meistens nicht in die Datenbank gelangen. Sie erstellen eine Anwendung so, dass jeder Code, der Daten aus einer Quelle benötigt, dies mit einem Repository tut und dieses Repository als Abhängigkeit eingefügt wird. Dies ermöglicht ein einfaches Verspotten und die gewünschte TDD-Güte. Aber am Ende möchten Sie sicherstellen, dass Ihre Repositorys ihre Aufgaben erfüllen, und wenn der einfachste Weg, dies zu tun, darin besteht, eine Datenbank zu erreichen, dann sei es so. Ich habe die Vorstellung, dass Unit-Tests die Datenbank nicht berühren sollten, lange losgelassen und festgestellt, dass es sehr reale Gründe dafür gibt. Aber nur, wenn Sie dies automatisch und wiederholt tun können. Und das Wetter, das wir einen solchen Test als "Unit Test" oder "Integrationstest" bezeichnen, ist umstritten.

JDT
quelle
3
Unit-Tests und Integrationstests haben unterschiedliche Zwecke. Die Namen für diese Tests sind nicht nur dekorativ; Sie sind auch beschreibend.
Robert Harvey
9
  1. Testen Sie keine trivialen oder offensichtlichen Repository-Methoden.

    Wenn es sich bei den Methoden um triviale CRUD-Operationen handelt, testen Sie nur, ob die Parameter korrekt zugeordnet sind. Wenn Sie Integrationstests haben, werden solche Fehler ohnehin sofort sichtbar.

    Dies ist das gleiche Prinzip, das für triviale Eigenschaften wie dieses gilt:

    public property SomeProperty
    {
        get { return _someProperty; }
        set { _someProperty = value; }
    }
    

    Sie testen es nicht, weil es nichts zu testen gibt. Es gibt keine Validierung oder andere Logik in der Eigenschaft, die überprüft werden muss.

  2. Wenn Sie diese Methoden noch testen möchten ...

    Mocks sind der Weg, es zu tun. Denken Sie daran, dies sind Unit-Tests. Sie testen die Datenbank nicht mit Komponententests. Dafür sind Integrationstests gedacht.

Weitere Informationen
Der vollständige Stapel, Teil 3: Erstellen eines Repositorys mit TDD (beginnen Sie nach etwa 16 Minuten mit dem Anschauen).

Robert Harvey
quelle
3
Klar, ich verstehe das. Wenn es sich jedoch um einen TDD-Ansatz handelt, sollte ich keinen Code schreiben, wenn ich nicht zuerst Tests für diesen Code habe, oder?
Thaven
1
@Thaven - Auf Youtube gibt es eine Reihe von Videos mit dem Titel "is tdd dead?". Beobachte sie. Sie befassen sich mit vielen interessanten Punkten, unter anderem mit der Vorstellung, dass die Anwendung von TDD auf jeder Ebene Ihrer Anwendung nicht unbedingt die beste Idee ist. "Kein Code ohne fehlgeschlagenen Test" ist eine zu extreme Position, ist eine der Schlussfolgerungen.
Jules
2
@ Jules: Was ist der tl; dw?
Robert Harvey
1
@ RobertHarvey Es ist schwer zusammenzufassen, aber der wichtigste Punkt war, dass es ein Fehler ist, TDD als eine Religion zu behandeln, die immer beachtet werden muss. Die Wahl, es zu verwenden, ist Teil eines Kompromisses, und Sie müssen berücksichtigen, dass (1) Sie möglicherweise in der Lage sind, bei einigen Problemen schneller zu arbeiten, und (2) Sie möglicherweise zu einer komplexeren Lösung führen, als Sie benötigen. vor allem, wenn Sie viele Mocks verwenden.
Jules
1
+1 für Punkt 1. Tests können falsch sein, es ist nur so, dass sie normalerweise trivial sind. Es ist sinnlos, eine Funktion zu testen, deren Richtigkeit offensichtlicher ist als die des Tests. Es ist nicht so, dass Sie mit einer 100% igen Codeabdeckung fast jede mögliche Ausführung des Programms testen können. Sie können also genauso gut klug sein, wo Sie Testaufwand aufwenden.
Doval