Ich habe mich im Kreis gedreht, um herauszufinden, wie ich eine API-Clientbibliothek, die ich entwickle, am besten einem Komponententest unterziehen kann. Die Bibliothek verfügt über eine Client
Klasse, die im Grunde genommen eine 1: 1-Zuordnung zur API hat, und eine zusätzliche Wrapper
Klasse, die eine benutzerfreundlichere Oberfläche bietet Client
.
Wrapper --> Client --> External API
Ich schrieb zuerst eine Reihe von Tests gegen beide Client
und Wrapper
testete effektiv nur, dass sie an die entsprechenden Funktionen weiterleiten, unabhängig davon, woran sie arbeiten ( Wrapper
arbeitet an Client
und Client
arbeitet an einer HTTP-Verbindung). Ich fühlte mich jedoch unwohl, weil ich das Gefühl hatte, die Implementierung dieser Klassen und nicht die Schnittstelle zu testen. Theoretisch könnte ich die Klassen so ändern, dass sie eine andere, perfekt gültige Implementierung haben, aber meine Tests würden fehlschlagen, weil die Funktionen, von denen ich erwartet hatte, dass sie aufgerufen werden, nicht aufgerufen werden. Das klingt für mich nach fragilen Tests.
Danach habe ich über die Schnittstelle der Klassen nachgedacht. Die Tests sollten sicherstellen, dass die Klassen tatsächlich die Arbeit leisten, für die sie vorgesehen sind, und nicht, wie sie es tun. Wie kann ich das machen? Das erste, was mir einfällt, ist das Stubben der externen API-Anforderungen. Ich bin jedoch nervös, den Außendienst zu stark zu vereinfachen. Viele der Beispiele für verkürzte APIs, die ich gesehen habe, geben nur vorgefertigte Antworten. Dies klingt nach einer sehr einfachen Möglichkeit, nur zu testen, ob Ihr Code zufällig ordnungsgemäß mit Ihrer gefälschten API ausgeführt wird. Die Alternative besteht darin, den Service zu verspotten, was einfach nicht machbar ist und auf dem neuesten Stand gehalten werden müsste, wenn sich der tatsächliche Service ändert - das fühlt sich nach Übermaß und Zeitverschwendung an.
Zum Schluss las ich dies aus einer anderen Antwort zum Programmierer SE :
Die Aufgabe eines Remote-API-Clients besteht darin, bestimmte Aufrufe auszulösen - nicht mehr und nicht weniger. Daher sollte sein Test sicherstellen, dass er diese Anrufe ausgibt - nicht mehr und nicht weniger.
Und jetzt bin ich mehr oder weniger davon überzeugt Client
, dass ich beim Testen nur die richtigen Anforderungen an die API stellen muss wo Integrationstests nützlich wären). Da Client
es sich nur um eine 1: 1-Zuordnung mit der API handelt, trifft meine Sorge vor dem Wechsel von einer gültigen Implementierung zu einer anderen nicht wirklich zu - es gibt nur eine gültige Implementierung für jede Methode von Client
.
Ich bin jedoch immer noch mit der Wrapper
Klasse fest. Ich sehe die folgenden Optionen:
Ich stecke die
Client
Klasse ab und teste nur, dass die entsprechenden Methoden aufgerufen werden. Auf diese Weise mache ich dasselbe wie oben, aber behandle dasClient
als Ersatz für die API. Damit bin ich wieder da, wo ich begonnen habe. Dies gibt mir wieder das unangenehme Gefühl, die Implementierung zu testen, nicht die Benutzeroberfläche. DasWrapper
könnte sehr gut mit einem ganz anderen Client umgesetzt werden.Ich mache einen Schein
Client
. Jetzt muss ich entscheiden, wie weit ich mit dem Verspotten gehen soll - ein komplettes Verspotten des Dienstes wäre sehr aufwändig (mehr Arbeit, als in die Bibliothek selbst geflossen ist). Die API selbst ist einfach, aber der Service ist ziemlich komplex (es ist im Wesentlichen ein Datenspeicher mit Operationen an diesen Daten). Und wieder muss ich mein Mock mit dem Real synchron haltenClient
.Ich teste nur, dass die entsprechenden HTTP-Anforderungen gemacht werden. Das bedeutet, dass
Wrapper
ein realesClient
Objekt aufgerufen wird, um diese HTTP-Anforderungen zu stellen. Ich teste es also nicht isoliert. Dies macht es ein bisschen wie ein schrecklicher Unit-Test.
Daher bin ich mit keiner dieser Lösungen besonders zufrieden. Was würden Sie tun? Gibt es einen richtigen Weg, um dies zu erreichen?
Antworten:
TLDR : Trotz der Schwierigkeit sollten Sie den Dienst beenden und für Client-Unit-Tests verwenden.
Ich bin mir nicht so sicher, ob es die "Aufgabe eines Remote-API-Clients ist, bestimmte Aufrufe auszulösen, nicht mehr und nicht weniger ...", es sei denn, die API besteht nur aus Endpunkten, die immer einen festen Status zurückgeben und weder verbrauchen noch produzieren irgendwelche Daten. Dies wäre nicht die nützlichste API ...
Sie sollten auch überprüfen, ob der Client nicht nur die richtigen Anforderungen sendet , sondern auch die Antwortinhalte, Fehler, Weiterleitungen usw. ordnungsgemäß verarbeitet. Testen Sie alle diese Fälle.
Wie Sie bemerken, sollten Sie Integrationstests haben, die den gesamten Stapel von Wrapper -> Client -> Service -> DB und darüber hinaus abdecken, aber um Ihre Hauptfrage zu beantworten, es sei denn, Sie haben eine Umgebung, in der Integrationstests als Teil von jedem ausgeführt werden können Wenn Sie einen CI-Build ohne große Probleme erstellen (gemeinsame Testdatenbanken usw.), sollten Sie die Zeit in die Erstellung eines Stubs der API investieren.
Mit dem Stub können Sie eine funktionsfähige Implementierung des Dienstes erstellen, ohne jedoch eine Ebene unterhalb des Dienstes selbst implementieren zu müssen.
Zu diesem Zweck können Sie eine DI-basierte Lösung mit einer Implementierung des Repository-Musters unter den REST-Ressourcen in Betracht ziehen:
Sowieso, es sei denn, Stubbing und / oder Refactoring des Dienstes kommen weder politisch noch praktisch in Frage, würde ich wahrscheinlich etwas Ähnliches wie das oben Genannte unternehmen. Wenn dies nicht möglich ist, stelle ich sicher, dass die Integrationsabdeckung wirklich gut ist, und führe sie so oft wie möglich aus, wenn Ihr Test-Setup verfügbar ist.
HTH
quelle