Unit-Test eines API-Clients und von Wrappern

14

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 ClientKlasse, die im Grunde genommen eine 1: 1-Zuordnung zur API hat, und eine zusätzliche WrapperKlasse, die eine benutzerfreundlichere Oberfläche bietet Client.

Wrapper --> Client --> External API

Ich schrieb zuerst eine Reihe von Tests gegen beide Clientund Wrappertestete effektiv nur, dass sie an die entsprechenden Funktionen weiterleiten, unabhängig davon, woran sie arbeiten ( Wrapperarbeitet an Clientund Clientarbeitet 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 Clientes 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 WrapperKlasse fest. Ich sehe die folgenden Optionen:

  1. Ich stecke die ClientKlasse ab und teste nur, dass die entsprechenden Methoden aufgerufen werden. Auf diese Weise mache ich dasselbe wie oben, aber behandle das Clientals 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. Das Wrapperkönnte sehr gut mit einem ganz anderen Client umgesetzt werden.

  2. 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 halten Client.

  3. Ich teste nur, dass die entsprechenden HTTP-Anforderungen gemacht werden. Das bedeutet, dass Wrapperein reales ClientObjekt 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?

Joseph Mansfield
quelle
Ich neige dazu, Unit-Tests in diesen Szenarien zu vermeiden, in denen eine Drittanbieter-Bibliothek die meiste Arbeit erledigt und ich nur einen Wrapper habe (hauptsächlich, weil ich keine Ahnung habe, wie ich dies auf eine Weise tun soll, die etwas wirklich Sinnvolles testet). Im Allgemeinen mache ich in diesen Fällen Integrationstests, möglicherweise mit einem Mock-Service. Vielleicht weiß jemand, wie man einen wirklich aussagekräftigen Komponententest für diese durchführt - ich neige dazu, die wichtigsten Komponenten im von uns kontrollierten System zu priorisieren. Hier liegt der kritische Teil außerhalb unserer Kontrolle. :-(

Antworten:

10

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:

  • Ersetzen Sie den gesamten Funktionscode in den REST-Handlern durch Aufrufe eines IWhateverRepository.
  • Erstellen Sie ein ProductionWhateverRepository mit dem Code, der aus den REST-Ressourcen extrahiert wurde, und ein TestWhateverRespository, das vorgefertigte Antworten zur Verwendung während des Komponententests zurückgibt.
  • Verwenden Sie den DI-Container, um je nach Konfiguration die DLL / Klasse ProductionWhateverRepository oder TestWhateverRepository usw. zu injizieren.

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

Dan1701
quelle