TDD Mock Call Verification - ist es ein Anti-Pattern?

11

Ich mache jetzt seit einem Jahr TDD, ich fühle mich ziemlich gut dabei, ich liebe meine Testsuiten und alles. Ich habe jedoch festgestellt, dass ich in letzter Zeit viele Scheinanrufüberprüfungen durchgeführt habe. Zum Beispiel hätte ich einen Dienst, dem ein Repository injiziert wird - in meinem Komponententest würde ich ein Modell des Repositorys übergeben und überprüfen, ob es innerhalb der von mir getesteten Methode aufgerufen wurde. Ich würde dann prüfen, ob die zurückgegebenen Ergebnisse korrekt sind (in einem anderen Test). Dies "fühlt" sich definitiv falsch an, da meine Unit-Tests jetzt sehr stark an die Implementierungsdetails gekoppelt sind. Ich habe gehört, dass Sie "Verhalten" testen sollten, aber in vielen Situationen, die ... emm - nicht möglich sind? Wenn Sie eine habenvoidMethode zum Beispiel testen Sie in der Regel Nebenwirkungen. Ich meine, es ist einfach, einige einfache Code-Kata zu zeigen, in denen dies demonstriert werden kann, aber meiner Meinung nach spiegelt es die von uns geschriebenen Programme der realen Welt nicht sehr gut wider. Ist das, was ich falsch mache? Ist diese Art des Testens eine Art Anti-Muster? Ich würde mich über Ihre Meinung dazu freuen. Ich bin immer noch ein Neuling, wenn es um TDD geht.

Dimitar Dimitrov
quelle
2
Kurze Antwort: ja. Es gibt bereits irgendwo hier sehr interessante Fragen zu diesem Thema. Ihre Komponententests sollten nicht fragil sein und stark von Ihrer Implementierung abhängen. Aus diesem Grund sind Tests auf höherer Ebene für (Integration usw.) vorgesehen. Hier: programmers.stackexchange.com/questions/198453/…
Kemoda
@Kemoda Ich würde mich freuen, wenn Sie mich mit einer Diskussion oder einem weiteren Material dazu verknüpfen können. Ich würde meine Techniken sehr gerne verbessern.
Dimitar Dimitrov
1
Sie haben dies zum Beispiel programmers.stackexchange.com/questions/198453/… ich werde später andere Links finden
Kemoda

Antworten:

8

Nun, Sie sollten versuchen, Ein- und Ausgänge zu testen. Sie sollten das extern sichtbare Verhalten überprüfen. Die "Versprechen" oder "Verträge", die Ihre Klasse macht.

Gleichzeitig gibt es manchmal keinen besseren Weg, eine Methode zu testen, als das zu tun, was Sie gesagt haben.

Ich denke, dass Ihr Test dadurch spröder wird. Sie sollten daher Tests vermeiden, die sich auf Implementierungsdetails stützen, wenn Sie können, aber es ist kein Alles-oder-Nichts-Geschäft. Manchmal ist es in Ordnung. Das Schlimmste, was passiert, ist, dass Sie die Implementierung ändern und den Test aktualisieren müssen.

M. Dudley
quelle
2

Der Zweck eines Tests besteht darin, die möglichen produktiven Implementierungen einzuschränken. Stellen Sie sicher, dass Sie nur die Implementierung einschränken, die Sie tatsächlich benötigen. Normalerweise ist dies , was sollte Ihr Programm tun, und nicht , wie sie es tut.

Wenn Ihr Service beispielsweise dem Repository etwas hinzufügt, sollten Sie testen, ob der neue Eintrag anschließend im Repository enthalten ist und nicht, dass die Aktion zum Hinzufügen ausgelöst wird.

Damit dies funktioniert, müssen Sie in der Lage sein, die Repository-Implementierung (an anderer Stelle getestet) für den Test des Dienstes zu verwenden. Ich fand, dass die Verwendung der realen Implementierung eines Mitarbeiters im Allgemeinen ein guter Ansatz ist - weil es wirklich die beste Implementierung ist, die es gibt.


"Also, aber was ist, wenn die Verwendung der realen Implementierungen im Test teuer ist (z. B. weil sie Ressourcen erfordern, deren Einrichtung kompliziert ist)? Ich muss in diesem Fall Mocks verwenden, richtig?"

In jedem Fall möchten Sie wahrscheinlich einen Integrationstest, der prüft, ob die tatsächlichen Implementierungen zusammenarbeiten. Stellen Sie sicher, dass dieser eine Integrationstest alles ist, was zum Testen Ihres Dienstes erforderlich ist. Oder mit anderen Worten: Wenn ein Dienst viele Mitarbeiter zusammenbringt (und daher möglicherweise schwer zu testen ist), stellen Sie sicher, dass er keine Logik enthält. Wenn dies der Fall ist und Sie mehrere (Integrations-) Tests benötigen, müssen Sie die Struktur Ihres Codes ändern, z. B. indem Sie die Logik isolieren und damit testbarer machen.

Die Verwendung von Mocks in diesem Fall erleichtert das Testen einer schlecht isolierten Logik und verbirgt somit ein architektonisches Problem . Verwenden Sie also keine Mocks, um schlecht strukturierten Code zu testen, sondern korrigieren Sie stattdessen die Struktur.

oberlies
quelle
1
Ich verstehe, was du sagst. Dieses Thema ist etwas verwirrend in Bezug auf "wie viel Testen zu viel Testen ist". Nehmen wir an, ich habe einen "aggregierten Service", der im Grunde eine Fassade ist und nur eine Reihe anderer Services / Repositorys / Komponenten "zusammenklebt", welche Art von Tests schreibst du dafür Ich kann mir nur eine Anrufüberprüfung vorstellen. Ich hoffe ich mache Sinn.
Dimitar Dimitrov
2

Meine Gedanken zu: "Aggregatdienste".

Die Anrufüberprüfung wird dies tun, aber nicht viel Wert liefern. Sie überprüfen nur Ihre Verkabelung.

Es gibt drei nicht exklusive andere Möglichkeiten:

  1. Erweitern Sie die Tests, die Sie für jeden einzelnen Dienst haben, um das Verhalten auf höherer Ebene zu überprüfen. Wenn Sie beispielsweise in Ihren Komponententests des Dienstes auf eine In-Memory-Datenbank zugreifen, erhöhen Sie diese, sodass Sie den Dienst anhand einer tatsächlichen Datenbank testen. Die Service-Schicht befindet sich weiter oben im Abstraktionsbaum, ebenso wie Ihr Test.

  2. Verwenden Sie die Codegenerierung, um den Service direkt aus den aggregierten Services zu erstellen.

  3. Verwenden Sie eine Art Reflexion oder eine dynamische Sprache, um dasselbe zu tun. In Java kann es beispielsweise möglich sein, eine groovige Schnittstelle zu verwenden, die den Anruf direkt weiterleitet.

Es gibt wahrscheinlich andere Möglichkeiten, dies zu tun, aber nur die Überprüfung der Verkabelung hat eine sehr geringe Amortisation und wird Sie hart in diese Implementierung einbinden.

Andrew Oxenburgh
quelle