sheets of paper on a wooden surface showing images of unit tests and integration tests
Tutorials |

Unit vs. Integration – warum die beste Antwort „kommt drauf an“ ist

tanner.jpg.png

Tanner

30. August 2018

Hinweis: Dieser Artikel wurde unter Zuhilfenahme von KI aus dem Englischen übersetzt. Hier geht's zum Originaltext.

Als ich meinen neuen Job bei Peerigon – einer Webagentur, die sich auf JavaScript-Anwendungen spezialisiert hat – begann, kam ich aus einer Welt der Frontend-Entwicklung, die hauptsächlich aus HTML, CSS und jQuery bestand. Ich dachte beim Wechsel, dass die Diskussionen über Code weniger von Meinungen geprägt sein würden. Ich meine, was kann subjektiver sein als die Anzahl der Pixel, die man für die Dicke eines box-shadow verwendet? Anfangs schien meine Annahme auch richtig zu sein, bis ich meinen ersten API-Wrapper entwickeln sollte.

Ein Kinderspiel, dachte ich – was kann schon schiefgehen? Der Wrapper sollte einfach die Kommunikation mit unserer Zeiterfassungs-API verschönern. Ich würde ein paar schicke Endpunkt-Methoden zaubern, die Parameter entgegennehmen, sie durch eine HTTP-Bibliothek jagen und zwischendurch ein bisschen Datenmassage betreiben. Klingt simpel, oder? Abgesehen von ein paar kniffligen Design-Pirouetten lief die Implementierung auch tatsächlich wie geschmiert. Doch dann kam der Showstopper: Wie zum Teufel schreibt man sinnvolle Tests für Code, der dünner ist als eine Scheibe Edamer aus dem Supermarkt?!

Auf meiner verzweifelten Odyssee befragte ich das Orakel meiner Kollegen, wälzte stapelweise Stack Overflow-Threads, verschlang Jest-Dokus bis zum Erbrechen und saugte Konferenzvorträge und Blogbeiträge über Testdesign wie ein Schwamm auf. Und was bekam ich? Ein wildes Durcheinander sich widersprechender Meinungen – als hätte ich gleichzeitig einen Veganer und einen BBQ-Meister nach dem perfekten Abendessen gefragt!

Was mir schließlich wie Schuppen von den Augen fiel: Der heilige Gral der Testmethodik existiert nicht! Wenn es ums Testen geht, ist die einzig wahrhaft beste Methode wie ein maßgeschneiderter Anzug – sie muss verdammt nochmal zu DEINEM Projekt passen!

Jetzt nehme ich dich mit auf die wahnwitzige Reise, die zu meiner Erleuchtung führte. Schnallt euch an für die Turbo-Version meiner Commit-Historie – oder wie ich sie nenne: "Eine Parade epischer Fehlschläge." Wenn dir schlecht wird, kannst du natürlich direkt zu Commit 8 und dem Fazit springen. Aber ehrlich, wo bleibt da der Spaß? Komm mit auf diese emotionale Achterbahnfahrt durch die Geisterbahn der Ungewissheit – Kotztüten werden nicht gestellt!

Erste Anmerkung: Ich werde jetzt ständig von "Unit-Tests" und "Integrationstests" faseln – Begriffe, die in der Testing-Community für mehr Glaubenskriege sorgen als Tabs vs. Spaces! Meine eigene Meinung dazu fluktuiert wie die Kryptokurse, aber für diesen Beitrag gilt: Unit-Tests sind für mich diese kleinen Quälgeister, die isolierte Code-Häppchen (Funktionen) unter die Lupe nehmen und sich vor externen Diensten verstecken wie Vampire vor Knoblauch. Integrationstests dagegen sind die neugierigen Nachbarn, die alles über die Beziehungsprobleme zwischen deinen Code-Komponenten und externen Diensten wissen wollen.

Zweite Anmerkung: Bevor wir in den Kaninchenbau abtauchen – hier ein Schnellkurs zur Projektstruktur, die sich zwar über die Zeit mutierte wie ein X-Men-Charakter, aber im Kern gleich blieb. Da war zum einen meine *lib.js*-Datei – der Schweizer Taschenmesser-Code, der Parameter und Header einsammelt und mit einer Methode namens *apiRequest()* Axios-HTTP-Anfragen in die weite Welt schleudert. Die api.js beherbergte währenddessen ein ganzes Dutzend nach außen gerichteter Endpunkt-Methoden – quasi die hübschen Fassaden, die Benutzerparameter aufsammeln und sie an den Drecksarbeiter *apiRequest()* weiterreichen.

Beschriftung: Der Code aus lib.js und api.js, den ich testen wollte.

Commit 1 — Die React-Infektion

Als grünschnäbliger Trainee mit noch glänzendem Uni-Diplom sollte dieser API-Wrapper meine Feuertaufe mit der sagenumwobenen Test-Driven Development (TDD)-Methode werden. Dummerweise hatte ich erst kürzlich meine Test-Jungfräulichkeit mit ein paar simplen React-Apps verloren – und dieser kurze Flirt hinterließ tiefere Spuren als ein Tattoo nach durchzechter Nacht.

Du ahnst es schon: Meine ersten Tests waren so React-verseucht wie ein Hipster-Café:

  • Wird apiRequest() überhaupt aufgerufen? (Wie bei React, wo man zuerst testet: "Erscheint dieses verdammte Ding überhaupt auf dem Bildschirm?")
  • Werden Parameter durch die Methode geschleust? (Als ob das keine Grundfunktion von JavaScript wäre!)

Ich höre förmlich dein ungläubiges Schnauben: "Moment mal – testete dieser Trottel wirklich die GRUNDLEGENDEN MECHANISMEN VON JAVASCRIPT?" Oh ja, das tat ich! Eine Dummheit monumentalen Ausmaßes. Tu dir selbst einen Gefallen und verbanne solchen Unsinn aus deinen Tests.

Commit 1.b — Das große Kollegen-Konsortium der konfusen Konzepte

Nun folgt der erste von zwei epischen Showdowns innerhalb meines Teams – perfektes Beweismaterial für meine These, dass JavaScript-Testing mehr Meinungen hervorbringt als eine Politikdebatte auf Twitter.

Michael und ich waren zunächst felsenfest überzeugt, dass wir die GET-Methode von Axios mocken sollten, die in apiRequest() zum Einsatz kam. Mit dieser Strategie könnten wir Parameter und Aufrufhäufigkeit mit Jests eingebauten Funktionen überprüfen – kinderleicht! Nachdem wir ein paar Tests fabriziert hatten, stolperte Paul über unseren Code – der damals so chaotisch war wie meine Wohnung am Sonntagmorgen – und runzelte die Stirn. "Leute", sagte er mit diesem beunruhigenden Tonfall eines Arztes, der schlechte Nachrichten überbringt, "ihr verbaut euch die Zukunft!" Sein Argument: Unser Jest-Mock-Gebastel würde uns an die Axios-Bibliothek ketten wie Prometheus an den Felsen. Sollten wir je auf die verrückte Idee kommen, Axios durch Request zu ersetzen, würden unsere Tests implodieren wie ein Kartenhaus im Sturm. Und bei einem Framework-Wechsel könnte ich meinen mühsam erlernten Jest-Code gleich in die digitale Mülltonne werfen.

In einem Anfall brillanter Überkompensation zauberte Paul apiRequest() in eine Higher-Order-Funktion um – ein elegantes Konstrukt, das unabhängig vom verwendeten HTTP-Zauberstab eine GET-Funktion ausspuckte – und mockte das Ganze mit einer einzigen Zeile in unserer Test-Datei. EINE ZEILE, Leute! Mit dieser Code-Chirurgie hatten wir bibliotheksspezifischen Schnickschnack durch lupenreines JavaScript ersetzt (was meiner Erfahrung nach oft so klug ist wie Schokolade statt Brokkoli zu essen). Es war zweifellos schicker als unser vorheriges Gebastel – aber hier kam der philosophische Knackpunkt: Rechtfertigt ein winziger Komplexitätsabbau in den Tests wirklich die zusätzliche Hirnakrobatik im eigentlichen Code? Michael und ich tauschten vielsagende Blicke und entschieden mit der Weisheit von zwei Entwicklern, die zu faul sind, Bibliotheken zu wechseln: nein, verdammt nochmal!

Meinungsverschiedenheiten im Test-Universum? So normal wie Koffein im Entwicklerblut! Du findest – wie ich in meiner naiven Recherchephase – Artikel und flammenspeiende Konferenzvorträge, die Integrationstests als schamlosen Betrug verteufeln, während andere Propheten Unit-Tests als überflüssige Zeitverfresser vom Sockel stoßen.

Hier mein goldener Rat: Fliehe vor allen, die absolute Wahrheiten predigen, als würden sie brennen! In meiner noch jungen Entwicklerkarriere habe ich unzählige Code-Gurus getroffen – darunter gute Freunde – die mit messianischem Eifer verkünden, SIE hätten DIE EINE Lösung gefunden. Spoiler-Alarm: Das ist meist genauso glaubwürdig wie ein Einhorn-Sichtungsbericht. Und beim Testen? Da ist diese "Eine Wahrheit" so realistisch wie kalorienfrei-schmeckendes Eis!

Commit 2 — Trotziges Weitermocken mit Axios

Trotz wachsender Zweifel an der Sinnhaftigkeit meiner Tests – etwa so wie man zweifelt, ob der fünfte Kaffee noch eine gute Idee ist – entschied ich mich mit der Entschlossenheit eines Sturschädels, beim Axios-Mocken mit Jest zu bleiben. Immerhin hatte ich jetzt eine Pro-und-Contra-Liste gemacht (auf einer Serviette, während der Mittagspause)! Als nächstes baute ich einen Test, der prüfte, ob die obligatorischen Header (die irgendwo in den Tiefen von lib.js lauerten) und die von meinen Endpunkt-Methoden angeschleppten Parameter ordnungsgemäß verschmolzen und per GET-Anfrage in die digitale Wildnis geschickt wurden.

Dieser Test deckte immerhin mehr Code ab als meine bisherigen Alibi-Tests – ein mikroskopischer Sieg, der mein Ego kurzzeitig aufpäppelte wie einen welken Salat.

Commit 3 — Die Integrations-Versuchung

Schließlich warf ich meine Unit-Tests mental über Bord (sie schwammen noch eine Weile im Unterbewusstsein) und stürzte mich kopfüber in die aufregende Welt der Integrationstests. Mein Ziel? Sicherstellen, dass tatsächlich ECHTE DATEN aus meinen Anfragen zurückkamen und nicht nur digitale Halluzinationen. Also erschuf ich Tests wie diesen hier:

Und – heiliger Programmiercode! – es fühlte sich tatsächlich COOL an! Im Vergleich zu meinen Unit-Tests, die so spannend waren wie trockener Toast, fühlten sich diese neuen Tests an wie echte Wachhunde, die laut bellen würden, sobald mein ständig mutierender Code mal wieder ungültige GET-Anfragen produzieren sollte. Der einzige Haken? Diese Tests waren so flexibel wie ein Betonblock. Und als ich dann noch einen Blick auf meine Testabdeckungs-Statistik warf, traf mich die brutale Erkenntnis wie ein Schlag in die Magengrube: Diese Tests prüften ausschließlich die interne apiRequest()-Funktion in lib.js. Keine einzige meiner eigentlichen benutzerorientierten Methoden hatte auch nur einen müden Test-Hauch abbekommen!

All das führte zu einer unvermeidlichen Konsequenz...

Commit 4&5 — Die große Test-Depression

Ich kollabierte innerlich wie ein schlecht gebackener Soufflé. Diese Tests, die meine eigentlichen Benutzermethoden links liegen ließen und nur den apiRequest()-Kern beäugten, trieben mich in den Wahnsinn. Aber was sollte ich mit diesen Mini-Methoden machen? 3-6 Zeilen Code, die nichts anderes taten, als apiRequest() hübsch zu verpacken! Was um alles in der Welt sollte ich da Sinnvolles testen? Die ganze Situation spottete der heiligen Test-Weisheit von Kent C. Dodds, dass unsere Tests "weniger Löcher in die Realität bohren sollten" – bei mir waren es mittlerweile so viele Löcher, dass die Realität wie ein schweizer Käse aussah!

Die logische Folge meiner Test-Verzweiflung? Ich warf innerlich die Hände hoch und beschloss: "Zum Teufel mit Tests!" Stattdessen widmete ich mich dem, was sich wie "echter Code" anfühlte – und ignorierte die leise Stimme in meinem Hinterkopf, die "Schaaaande!" flüsterte.

mt6NXBzbDk2bjZxejZ0Z2NjdTZodyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/A0FGCbbooHe1y/giphy.gif)

Tage später, nachdem ich ohne Tests entwickelt hatte, wurde mir klar, dass wir als Entwickler so oder so unseren Code testen werden. Ich hatte meine eigene Methode entwickelt, eine wirklich unordentliche Lösung, bei der ich eine Reihe von Beispielfunktionsaufrufen in eine example.js-Datei warf und verschiedene Codeabschnitte auskommentierte, je nachdem, worauf ich mich konzentrieren wollte. Irgendwann wurde ich dessen müde und ließ mich inspirieren. "Ich werde nützliche Tests schreiben!", dachte ich.

Also schrieb ich anständige Integrationstests, die das Vorhandensein von Eigenschaften in der Antwort prüften. Jetzt würden wir fehlgeschlagene Tests bekommen, wenn sich auf einer der beiden Seiten etwas ändern würde oder wenn wir einfach komplett die Daten verlieren würden.

Und noch wichtiger: Mit diesen Tests fühlte ich mich jetzt sicher, meinen Implementierungscode zu ändern und zu bereinigen, weil ich meine Tests konsultieren konnte. Dieses Gefühl der Sicherheit half mir, den bisher meisten Spaß an der Arbeit an diesem Projekt zu haben.

Commit 7 — Lebewohl, alte Unit-Tests

Endlich hatte ich den Mut in meinem gewachsenen Verständnis des Testens, zu erkennen, dass meine ursprünglichen Unit-Tests aus den ersten Commits unnütz waren, und ich löschte sie. Wir haben ja immer noch Git, falls wir sie brauchen, richtig?

Aber kurz bevor ich dachte, ich wäre fertig, schlug ein dritter Kollege, Johannes, vor, dass einige Unit-Tests immer noch nützlich sein könnten. Und das führte zu den Tests, die diesen ganzen Blogbeitrag inspirierten:

Commit 8 — Abfangen von Anfragen mit Nock

Johannes wollte schon länger Nock einen zweiten Blick geben, ein Node-Paket, das HTTP-Anfragen abfangen und mocken kann. Als wir uns hinsetzten und überlegten, wie wir sinnvolle "Unit"-Tests ohne tatsächliche API-Abfragen erstellen könnten, schlug er vor, dass ich es ausprobieren sollte. Und es war genau das, was ich brauchte.

So sehen meine Tests jetzt aus:

Natürlich war das nicht uneingeschränkt beliebt. Die erste Person, die diese Tests sah, war Michael, der besorgt war, dass die Tests etwas an Lesbarkeit/Klarheit einbüßen könnten, und sich an seine früheren Erfahrungen mit Nock erinnerte, bei denen das Abgleichen von HTTP-Headern mit all den Daten, die mit ihnen kommen, etwas knifflig sein kann. Als ich Johannes all das erzählte und ihm denselben Code zeigte, hatte er die perfekte Antwort, um diese verrückte Hin-und-Her-Bewegung zusammenzufassen, die ich erlebt hatte: "Ich weiß, es klingt seltsam, aber ich mag es."

Und letztendlich war es genau das, was ich brauchte.

Fazit: Die Erkenntnis durch Nock

Während dieser Handvoll Wochen, in denen ich meinen Code und meine Tests schrieb, schwankte ich in meiner Meinung zu Unit-Tests vs. Integrationstests – sowie dazu, was diese Begriffe überhaupt bedeuten! Wenn du ein junger Entwickler bist, können deine Ansichten leicht von deinen erfahreneren Kollegen – wie Michael, Paul und Johannes – beeinflusst werden, weil du dazu neigst, anzunehmen, dass es immer eine Antwort auf jede Frage gibt. Und es hilft nicht, wenn einige Entwickler in Medium-Beiträgen so tun, als wäre ihr Weg der einzig richtige!

Deine Ansichten zum Testen können auch von den einzigartigen Aspekten des Projekts beeinflusst werden, an dem du arbeitest. Reine Unit-Tests können großartig sein, besonders für streng funktionale Programmierung. Aber bei unserem API-Wrapper war es einfach unmöglich, sinnvolle Unit-Tests ohne die Hilfe von Nock zu schreiben.

Johannes sagte mir, dass er das Testen nicht als entweder Unit-Tests oder Integrationstests sieht, sondern eher als ein Spektrum, wobei diese beiden Arten an den Enden sitzen. Und wenn das der Fall ist, liegt Nock irgendwo in der Mitte.

Infografik, die Nock zwischen Integrationstests und Unit-Tests zeigt

Wir kümmern uns wirklich nur um die benutzerorientierten Methoden in unserer api.js-Datei, und es gibt keine Verbindungen zu externen Servern oder Hardware. Aber mit der Ergänzung durch Nock anstelle einer Menge Jest-Mocking führen wir mehr echten Code aus und überwinden die Hürde, dass meine Endpunktfunktionen wie die getUsers()-Methode etwa 3 Zeilen lang und unmöglich wirklich isoliert zu testen sind.

Das wird in deinen frühen Versuchen mit dem Testen überhaupt nicht deutlich. Aber Tatsache ist, dass die Bedürfnisse deines Projekts viel stärker bestimmen, wie du deine Tests schreiben solltest, als die Meinungen der JS-Community. Mein Rat ist, anstatt dir Sorgen darüber zu machen, deine Tests zwischen Unit und Integration aufzuteilen, versuche, dafür zu sorgen, dass alle Tests, die du schreibst, so nah wie möglich an Kent Becks grundlegende Arbeit zu TDD heranreichen. Mit den Tests, die aus diesem Prozess entstehen, wird es keine Rolle spielen, zu welcher Kategorie oder Praxis sie gehören.

Aber vor allem hoffe ich, dass meine Kämpfe dir Seelenfrieden bringen können, indem du weißt, dass du mehr als nur zwei Optionen hast, um deine Testbedürfnisse zu erfüllen. Und ich verspreche dir, dass das Schreiben von Tests, wenn du die Frustration überwindest, eine lohnende Erfahrung sein kann.

Dank an Johannes Ewald und topa.

JavaScript

Tutorial

API

Nock

Tdd

Jest

Weitere Themen

Lea, 04.04.2025

Vue clever nutzen – Wiederverwendbarkeit für Einsteiger:innen

Vue

JavaScript

Reusability

DRY Principle

Components

Composables

Zum Blogartikel
Header-top-ten-mistakes-to-avoid.png

Francesca, Ricarda, 02.04.2025

Die 10 häufigsten Fehler bei der Entwicklung digitaler Produkte – und wie du sie vermeidest

MVP development

UX/UI design

product vision

agile process

user engagement

product development

Zum Blogartikel

Judith, 31.03.2025

Von der Hypothese zur echten Erkenntnis: MVPs richtig einsetzen

Zum Blogartikel