Testpyramide: Unit-, Integrations- und E2E-Tests richtig verteilen
Was die Testpyramide nach Mike Cohn und ISTQB besagt, wie sich Unit-Test und Integrationstest unterscheiden und wie ein kleines Team Tests verteilt.
Thema: Web-Entwicklung
Inhalt (8 Abschnitte)
Die Testpyramide ist ein Modell für automatisierte Tests: unten viele kleine, schnelle Unit-Tests, in der Mitte weniger Integrations- oder Service-Tests, oben wenige End-to-End-Tests über die Oberfläche. Je höher die Ebene, desto langsamer, teurer und weniger isoliert sind die Tests. Bekannt gemacht hat sie Mike Cohn 2009 im Buch „Succeeding with Agile“.
Stand: 13. Oktober 2026. Die Angaben stammen aus dem ISTQB-Lehrplan Foundation Level 4.0.1, den Texten von Mike Cohn, Martin Fowler, Ham Vocke, Kent C. Dodds und Spotify Engineering sowie der Dokumentation von Node.js und Playwright, abgerufen an diesem Tag. Die Quellen stehen am Ende.
Was die Testpyramide besagt
Die Pyramide beantwortet eine Frage: Auf welcher Ebene solltest du wie viele automatisierte Tests schreiben? Der Lehrplan des ISTQB (International Software Testing Qualifications Board) für die Foundation Level beschreibt sie als Modell, das zeigt, dass Tests eine unterschiedliche Granularität haben. Je höher die Schicht, desto geringer die Granularität und die Isolation und desto länger die Laufzeit.
| Ebene | Was geprüft wird | Laufzeit und Kosten | Anzahl |
|---|---|---|---|
| Unit-Tests (Komponententests) | eine kleine Einheit, etwa eine Funktion oder Klasse, isoliert | schnell, günstig | viele |
| Integrations- oder Service-Tests | das Zusammenspiel mehrerer Teile, etwa Code und Datenbank oder eine API ohne Oberfläche | mittel | weniger |
| End-to-End- oder UI-Tests | ein ganzer Ablauf durch die Oberfläche, wie ein Nutzer ihn ausführt | langsam, teuer, anfälliger | wenige |
Laut ISTQB sind die Tests der unteren Schicht klein, isoliert und schnell und prüfen ein kleines Stück Funktion, sodass man viele davon braucht. Die obere Schicht besteht aus komplexen End-to-End-Tests, die große Teile prüfen, sodass wenige genügen. Anzahl und Namen der Schichten können abweichen. Das Original von Cohn nennt „unit tests“, „service tests“ und „UI tests“; ein anderes verbreitetes Modell nennt Unit-, Integrations- und End-to-End-Tests.
Ham Vocke fasst die Pyramide in seinem Artikel auf Fowlers Website in zwei Regeln: Tests unterschiedlicher Granularität schreiben, und je höher die Ebene, desto weniger Tests.
Das Gegenbild heißt Eistüte (ice-cream cone): wenige Unit-Tests und sehr viele Tests über die Oberfläche. Martin Fowler beschreibt solche Tests als brüchig, teuer zu schreiben und langsam. Schon eine Erweiterung des Systems kann viele davon brechen.
Woher die Testpyramide kommt: Mike Cohn
Laut Martin Fowler kennen die meisten die Pyramide durch Mike Cohn, der sie 2009 im Buch „Succeeding with Agile“ beschrieben hat. Cohn nennt sie dort „Test Automation Pyramid“. Gezeichnet hat er sie nach Fowlers Darstellung schon 2003 und 2004 im Gespräch mit Lisa Crispin; Jason Huggins kam um 2006 unabhängig auf dieselbe Idee.
Cohn selbst begründet die drei Ebenen so:
- Unit-Tests sind das Fundament und der größte Teil. Sie zeigen dem Entwickler genau, wo ein Fehler sitzt, und werden meist in derselben Sprache geschrieben wie das System.
- UI-Tests stehen oben, weil man davon so wenige wie möglich will. Sie sind brüchig, teuer und zeitaufwendig und prüfen dieselben Teile der Oberfläche immer wieder.
- Service-Tests in der Mitte prüfen, was die Anwendung auf eine Eingabe hin tut, ohne die Oberfläche. Cohn nennt diese Schicht die „vergessene“: Viele Teams ließen sie aus und testeten dann alles über die Oberfläche.
Ein „Service“ meint bei Cohn ausdrücklich keine serviceorientierte Architektur, sondern etwas, das die Anwendung auf eine Eingabe hin tut, etwa „multiplizieren“ in einem Taschenrechner.
Unit-Test vs. Integrationstest: der Unterschied
Hier beginnt die meiste Verwirrung, weil beide Begriffe unterschiedlich benutzt werden. Die Definitionen des ISTQB:
| Unit-Test (Komponententest) | Integrationstest (Komponentenintegrationstest) | |
|---|---|---|
| Ziel laut ISTQB | Komponenten isoliert testen | Schnittstellen und Zusammenspiel zwischen Komponenten testen |
| typischer Umfang | eine Funktion, Methode oder Klasse | mehrere Module, etwa Code und Datenbank, Dateisystem oder ein anderer Dienst |
| Abhängigkeiten | oft durch Test-Doubles (Mocks, Stubs) ersetzt | ganz oder teilweise echt |
| findet vor allem | Logikfehler in einer Einheit | falsche Annahmen über die andere Seite: Abfragen, Formate, Konfiguration |
| Rückmeldung | sehr schnell, genau lokalisiert | langsamer, Ursache muss gesucht werden |
Das ISTQB unterscheidet außerdem den Systemintegrationstest, der die Schnittstellen zu anderen Systemen und externen Diensten prüft und eine Testumgebung braucht, die der Produktion möglichst ähnelt.
Martin Fowler weist auf zwei Lesarten von „Integrationstest“ hin:
- schmale Integrationstests prüfen nur den Teil des Codes, der mit einem anderen Dienst spricht, und setzen dafür Test-Doubles ein. Sie sind oft nicht größer als ein Unit-Test, laufen meist mit demselben Framework und oft sehr schnell.
- breite Integrationstests brauchen laufende Versionen aller Dienste und prüfen Abläufe durch alle hindurch. Fowler nennt sie lieber System- oder End-to-End-Tests.
Auch beim Unit-Test gibt es zwei Schulen: solitäre Unit-Tests ersetzen alle Mitspieler durch Test-Doubles, gesellige (sociable) Unit-Tests lassen echte Mitspieler zu. Die Begriffe stammen laut Fowler von Jay Fields. Wenn jemand sagt, man solle „mehr Integrations- als Unit-Tests“ schreiben, meint er nach Fowlers Beobachtung oft gesellige Unit-Tests.
Ein Beispiel: Unit-Test und Integrationstest in Node.js
Das Beispiel nutzt den eingebauten Test-Runner von Node.js (node:test), der als stabil gekennzeichnet ist. Eine reine Funktion rechnet Bruttopreise, ein zweites Modul speichert Bestellungen in einer Datei.
// preis.mjs
export function bruttoPreis(netto, satz = 0.19) {
return Math.round(netto * (1 + satz) * 100) / 100;
}
// bestellungen.mjs
import { appendFile, readFile } from 'node:fs/promises';
import { bruttoPreis } from './preis.mjs';
export async function bestellungSpeichern(datei, { artikel, netto }) {
const eintrag = { artikel, brutto: bruttoPreis(netto) };
await appendFile(datei, JSON.stringify(eintrag) + '\n');
return eintrag;
}
export async function bestellungenLaden(datei) {
const text = await readFile(datei, 'utf8');
return text.trim().split('\n').map((zeile) => JSON.parse(zeile));
}
Der Unit-Test prüft nur die Rechenlogik, ohne Dateisystem und ohne Netz:
// preis.test.mjs
import test from 'node:test';
import assert from 'node:assert/strict';
import { bruttoPreis } from './preis.mjs';
test('rechnet 19 Prozent auf', () => {
assert.equal(bruttoPreis(100), 119);
});
test('rundet auf ganze Cent', () => {
assert.equal(bruttoPreis(9.99), 11.89);
});
test('akzeptiert den ermäßigten Satz', () => {
assert.equal(bruttoPreis(10, 0.07), 10.7);
});
Der Integrationstest prüft das Zusammenspiel von Preislogik, Speicherformat und echtem Dateisystem in einem frischen temporären Ordner:
// bestellungen.test.mjs
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { bestellungSpeichern, bestellungenLaden } from './bestellungen.mjs';
test('speichert Bestellungen und liest sie wieder', async (t) => {
const ordner = await mkdtemp(join(tmpdir(), 'bestellungen-'));
t.after(() => rm(ordner, { recursive: true, force: true }));
const datei = join(ordner, 'bestellungen.jsonl');
await bestellungSpeichern(datei, { artikel: 'Tasse', netto: 10 });
await bestellungSpeichern(datei, { artikel: 'Kanne', netto: 9.99 });
assert.deepEqual(await bestellungenLaden(datei), [
{ artikel: 'Tasse', brutto: 11.9 },
{ artikel: 'Kanne', brutto: 11.89 },
]);
});
node --test findet Dateien nach dem Muster **/*.test.mjs von selbst. Das Beispiel wurde mit Node.js 22.22 ausgeführt; alle vier Tests bestehen. Mit einer echten Datenbank statt einer Datei sieht ein Integrationstest genauso aus; nur das Anlegen und Aufräumen der Testdatenbank kommt hinzu.
Für die Spitze der Pyramide gibt es eigene Werkzeuge. Playwright etwa beschreibt sich als End-to-End-Test-Framework für Web-Apps, testet in Chromium, WebKit und Firefox und wird mit npm init playwright@latest eingerichtet.
Ist die Testpyramide noch aktuell? Pokal und Wabe
Neben der Pyramide gibt es zwei bekannte Gegenentwürfe:
| Modell | Herkunft | Schwerpunkt |
|---|---|---|
| Testpyramide | Mike Cohn, 2009 | die meisten Tests als Unit-Tests |
| Testing Trophy (Pokal) | Kent C. Dodds, aus der JavaScript-Praxis, für eine einzelne Codebasis | vor allem Integrationstests; zusätzlich statische Analyse als unterste Schicht |
| Testing Honeycomb (Wabe) | Spotify Engineering, 2018, für Microservices | vor allem Integrationstests, wenige Tests von Implementierungsdetails, möglichst keine „integrated tests“ über mehrere Dienste |
Kent C. Dodds betont selbst, dass er beim Pokal an eine einzelne Codebasis gedacht hat, nicht an Microservices. Unit-Tests sind bei ihm Tests von Einheiten ohne Abhängigkeiten oder mit gemockten Abhängigkeiten, Integrationstests solche, in denen mehrere Einheiten zusammenspielen. Spotify wiederum behandelt den einzelnen Microservice als Einheit und testet ihn über seine Schnittstellen.
Martin Fowler hält den Streit für großteils scheinbar: Die Befürworter von Wabe und Pokal meinten mit „Unit-Test“ meist solitäre Tests, mit „Integrationstest“ oft gesellige Unit-Tests. Die Beschreibungen der Pyramide schlössen beide Arten ein. Wichtiger als Prozentanteile ist ihm, wie er mit einem Zitat von Justin Searls sagt, dass Tests klare Grenzen haben, schnell und zuverlässig laufen und nur aus nützlichen Gründen fehlschlagen. In einer Fußnote zur Pyramide schreibt er außerdem: Sind die Tests auf hoher Ebene schnell, zuverlässig und billig zu ändern, braucht man die darunter nicht.
Wie ein kleines Team die Pyramide anwendet
Feste Quoten nennen weder Cohn noch das ISTQB. Für ein Team mit wenigen Entwicklern ist nach Einschätzung diese Reihenfolge ein guter Start:
- Ein End-to-End-Test für den wichtigsten Ablauf, etwa Registrieren, Anmelden, Kernaktion. Er findet Fehler, die zwischen den Teilen entstehen: fehlende Umgebungsvariablen, kaputte Builds, gescheiterte Migrationen.
- Unit-Tests für echte Logik: alles, was rechnet, entscheidet oder umwandelt, etwa Preise, Fristen, Parser und Zustände. Code, der nur durchreicht, braucht nach Einschätzung keinen eigenen Unit-Test.
- Integrationstests an den Stellen mit Außenkontakt: Datenbankabfragen, Dateiformate, Aufrufe fremder APIs. Ein Mock der Datenbank prüft nach Einschätzung deine Annahmen über die Abfrage, nicht die Abfrage selbst.
- Ein Regressionstest für jeden Fehler, der in Produktion auftaucht. Fowler rät, einen Fehler, den ein Test auf hoher Ebene findet, vor der Behebung zusätzlich mit einem Unit-Test nachzustellen. Dann bleibt er behoben.
Damit die Tests etwas nützen, müssen sie bei jedem Push laufen. Wer oft und klein ausliefert, versteckt unfertige Funktionen hinter Feature Flags, statt lange Branches zu pflegen. Wer fremden Code ohne Tests übernimmt, beginnt besser mit Charakterisierungstests; das erklärt der Leitfaden zu Legacy Code.
Auch KI-Assistenten schreiben inzwischen Tests. Nach Einschätzung gilt dabei: Was ein Test als richtig festhält, musst du selbst entscheiden, denn ein aus dem bestehenden Code erzeugter Test kann einen Fehler als Sollverhalten festschreiben. Welche KI-Werkzeuge beim Prüfen von Code helfen und wo sie an Grenzen stoßen, zeigt der Artikel AI Code Review.
Quellen und Stand
Alle Angaben wurden am 13. Oktober 2026 geprüft an:
- ISTQB: Certified Tester Foundation Level, Lehrplan v4.0.1 (PDF), Abschnitte 2.2.1 (Teststufen) und 5.1.6 (Testpyramide)
- Mike Cohn: The Forgotten Layer of the Test Automation Pyramid
- Martin Fowler: Test Pyramid, Integration Test und On the Diverse And Fantastical Shapes of Testing
- Ham Vocke: The Practical Test Pyramid
- Kent C. Dodds: The Testing Trophy and Testing Classifications
- Spotify Engineering: Testing of Microservices
- Node.js: Test runner
- Playwright: Installation
Häufige Fragen
Was ist die Testpyramide?
Ein Modell für die Verteilung automatisierter Tests: viele schnelle, isolierte Unit-Tests unten, weniger Integrations- oder Service-Tests in der Mitte, wenige langsame End-to-End-Tests über die Oberfläche oben. Laut ISTQB nehmen nach oben Granularität und Isolation ab und die Laufzeit zu.
Wer hat die Testpyramide erfunden?
Bekannt gemacht hat sie Mike Cohn 2009 im Buch „Succeeding with Agile“ unter dem Namen „Test Automation Pyramid“. Laut Martin Fowler hat Cohn sie schon 2003 und 2004 im Gespräch mit Lisa Crispin gezeichnet; Jason Huggins entwickelte um 2006 unabhängig dieselbe Idee.
Was sagt das ISTQB zur Testpyramide?
Der Lehrplan Foundation Level 4.0.1 beschreibt sie als Modell dafür, dass Tests unterschiedliche Granularität haben, und als Hilfe für Testautomatisierung und Aufwandsverteilung. Anzahl und Namen der Schichten können abweichen; genannt werden Cohns Original und das Modell mit Unit-, Integrations- und End-to-End-Tests.
Was ist der Unterschied zwischen Unit-Test und Integrationstest?
Ein Unit-Test prüft eine Komponente isoliert, oft mit Test-Doubles für ihre Abhängigkeiten. Ein Integrationstest prüft die Schnittstellen und das Zusammenspiel mehrerer Komponenten, etwa Code und Datenbank. Unit-Tests sind schneller und zeigen den Fehler genauer, Integrationstests finden falsche Annahmen über die andere Seite.
Wie heißt die Testpyramide auf Englisch?
„Test pyramid“. Mike Cohn nannte sie ursprünglich „Test Automation Pyramid“, laut Martin Fowler hat sich im Gebrauch die kürzere Form durchgesetzt. Das Gegenbild mit vielen Oberflächentests heißt „ice-cream cone“, also Eistüte.
Ist die Testpyramide noch aktuell?
Sie wird weiter gelehrt, etwa im ISTQB-Lehrplan. Gegenentwürfe wie die Testing Trophy von Kent C. Dodds und die Testing Honeycomb von Spotify setzen stärker auf Integrationstests. Martin Fowler sieht den Unterschied vor allem in verschiedenen Definitionen von „Unit-Test“ und hält schnelle, verlässliche Tests für wichtiger als Prozentanteile.