Die Hälfte Ihrer Cucumber-Szenarien wird übersprungen. Nicht fehlgeschlagen. Übersprungen.
Dieser gelbe Status ist schlimmer als ein roter Build. Er lässt Ihre Suite gesund aussehen, während er drei völlig unterschiedliche Probleme unter einem höflichen Label verbirgt. Ein übersprungenes Szenario kann bedeuten, dass Ihr Tag-Filter es ausgeschlossen hat, eine Step Definition fehlt oder ein Before-Hook eine Exception geworfen hat, bevor der erste Schritt überhaupt ausgeführt wurde. Ihre Aufgabe ist es herauszufinden, welcher Parasit in Ihrer Suite lebt.
Warum “Skipped” Cucumbers gefährlichster Status ist
Cucumber hat drei Endzustände für ein Szenario: passed, failed und skipped. Passed und failed sind ehrlich. Skipped ist eine Mülltonne.
Wenn ein Szenario übersprungen wird, sagt Ihnen Cucumber, dass eine Voraussetzung nicht erfüllt wurde. Das Problem ist, dass “Voraussetzung” alles sein kann – von einem absichtlichen Tag-Filter bis zu einem Null-Pointer in einem Setup-Hook. Eine Suite mit 50% übersprungenen Szenarien sieht so aus, als hätte sie 500 Tests ausgeführt. Hat sie nicht. Sie hat 250 Tests ausgeführt und den anderen 250 zugewinkt, ohne sie zu prüfen.
Die meisten CI-Dashboards behandeln skipped als neutral. Ihre Pipeline ist grün. Aber die Hälfte Ihrer Verhaltensspezifikationen wird nicht überprüft. Wenn Sie Cucumber als lebende Dokumentation nutzen, ist die Hälfte Ihrer Dokumentation eine Lüge.
Die drei Ursachen für massenhaftes Skipping
Es gibt genau drei Möglichkeiten, ein übersprungenes Szenario in Cucumber zu erzeugen. Zwei davon sind Bugs. Eine ist eine Fehlkonfiguration, die wie ein Feature aussieht.
Tag-Filter, die mehr ausschließen als beabsichtigt
Die häufigste Ursache für 50% übersprungene Szenarien ist ein Tag-Ausdruck in Ihrer Runner-Konfiguration, der breiter ist als Sie denken. Ihr CI-Profil könnte so aussehen:
// cucumber.js
module.exports = {
default: [
'--format', 'progress',
'--tags', 'not @wip',
].join(' '),
ci: [
'--format', 'json:reports/cucumber.json',
'--tags', 'not @wip and not @slow',
].join(' '),
};
Der wirkliche Schaden entsteht, wenn Teams positive Tag-Filter verwenden. Ein Filter wie --tags '@regression' führt nur Szenarien aus, die mit @regression getaggt sind. Jedes andere Szenario wird übersprungen. So kommen Sie von 100% ausgeführt auf 50% ausgeführt, ohne eine einzige Feature-Datei zu ändern.
Step Definitions, die nicht mehr passen
Cucumber überspringt ein ganzes Szenario, wenn ein Schritt keine passende Step Definition hat. Das ist kein Failure. Es ist ein Skip. Ihr CI bleibt grün. Das passiert häufiger als Sie erwarten. Ein Produktmanager bearbeitet eine Gherkin-Datei, um einen Tippfehler in einem Schritt zu korrigieren:
# features/checkout.feature
Feature: Checkout
Scenario: Guest user completes purchase
Given a guest user with items in cart
When they proceed to checkout
Then the order total should include tax
Ihre Step Definitions erwarten dies:
// features/step_definitions/checkout_steps.js
Given('a guest user with items in the cart', function () {
// setup
});
Beachten Sie das fehlende “the” in der Feature-Datei. Cucumbers Regex passt nicht mehr. Das gesamte Szenario wird übersprungen. Multiplizieren Sie das über ein Team, das Feature-Dateien refactort, ohne die komplette Suite lokal auszuführen, und Sie können die Hälfte Ihrer Coverage durch Drift verlieren.
Failing Hooks, die sich als Skips tarnen
Wenn ein Before-Hook eine Exception wirft, überspringt Cucumber jedes Szenario in dieser Feature-Datei. Es failt sie nicht. Es überspringt sie.
// features/support/hooks.js
const { Before } = require('@cucumber/cucumber');
Before(async function () {
this.browser = await chromium.launch();
this.page = await this.browser.newPage();
// If this throws, every scenario in the file is skipped
await this.page.goto(process.env.TEST_URL);
});
Wenn TEST_URL in CI undefined ist, wirft der Hook und jedes damit verbundene Szenario wird übersprungen. Ihr Build ist grün. Ihre Tests sind wertlos.
Wie Sie diagnosestellen, welcher Parasit Sie haben
Sie können 50% übersprungene Szenarien nicht beheben, ohne zu wissen, welche der drei Ursachen verantwortlich ist. Cucumbers Standard-Output sagt es Ihnen nicht. Sie müssen es befragen.
Führen Sie Ihre Suite mit --dry-run und ohne Tag-Filter aus:
npx cucumber-js --dry-run --tags ''
Ein Dry Run parst jede Feature-Datei und matched jeden Schritt mit einer Step Definition, ohne etwas auszuführen. Wenn er 100% undefined anzeigt, haben Sie Step-Definition-Drift. Wenn er 100% passed anzeigt, kommen Ihre Skips von Tag-Filtern oder Runtime-Hooks.
Der JSON-Formatter enthält ein status-Feld für jeden Schritt. Ein Szenario, das durch einen Tag-Filter übersprungen wird, hat überhaupt keine Schritte. Ein Szenario, das durch eine fehlende Step Definition übersprungen wird, zeigt undefined beim nicht passenden Schritt. Ein Szenario, das durch einen failing Hook übersprungen wird, zeigt skipped bei jedem Schritt, und das embeddings-Array könnte den Hook-Error enthalten.
npx cucumber-js --format json:report.json
Parsen Sie das JSON und zählen Sie, wie viele Szenarien null Schritte haben im Vergleich zu solchen mit Schritten im Status undefined oder skipped. Das sagt Ihnen, wo Sie suchen müssen.
Die sicherste langfristige Lösung ist es, jeden unerwarteten Skip als Build-Failure zu behandeln. Ein kleiner Post-Processor für den JSON-Report erledigt das:
// scripts/fail-on-skips.js
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('report.json', 'utf8'));
let unexpectedSkips = 0;
for (const feature of report) {
for (const element of feature.elements) {
if (element.type !== 'scenario') continue;
const hasSkip = element.steps.some(s => s.result?.status === 'skipped');
const hasUndefined = element.steps.some(s => s.result?.status === 'undefined');
// Allow @wip to skip; everything else must run
const isWip = element.tags?.some(t => t.name === '@wip');
if ((hasSkip || hasUndefined) && !isWip) {
console.error(`Unexpected skip: ${feature.name} > ${element.name}`);
unexpectedSkips++;
}
}
}
if (unexpectedSkips > 0) {
console.error(`\n${unexpectedSkips} scenario(s) skipped unexpectedly. Failing build.`);
process.exit(1);
}
Führen Sie das nach jedem Cucumber-Aufruf in CI aus. Es wird Tag-Fehlkonfigurationen, fehlende Step Definitions und failing Hooks erwischen, bevor sie Ihre Suite stillschweigend verrotten lassen.
Der Kompromiss: Warum etwas Skipping in Ordnung ist
Absichtliches Skipping ist ein valides Werkzeug. Das @wip-Tag existiert aus einem Grund. Sie schreiben das Szenario vor der Implementierung, markieren es mit @wip, und lassen den Runner es überspringen, bis der Code bereit ist.
Der Unterschied zwischen gesundem Skipping und Suite-Verrottung ist Hygiene. @wip sollte temporär sein. Es sollte auf einem Branch leben, nicht sechs Monate lang auf main. Wenn 50% Ihrer Szenarien auf Ihrem Default-Branch mit @wip getaggt sind, haben Sie keine Test-Suite. Sie haben eine Wunschliste.
Tag-basiertes Filtering macht auch für umgebungsspezifische Tests Sinn. Ein Szenario, das ein physisches Payment-Terminal benötigt, sollte nicht in CI laufen. Aber dieses Szenario sollte mit @hardware getaggt sein, nicht @slow oder @manual. Seien Sie explizit darüber, warum etwas ausgeschlossen wird, und auditieren Sie diese Ausschlüsse im Code Review genauso wie den Code selbst.
Wie Sie die Verrottung stoppen
Wenn Sie heute bei 50% übersprungenen Szenarien sind, hier ist der schnellste Weg zurück zur Ehrlichkeit.
-
Führen Sie einen Dry Run ohne Tags aus. Zählen Sie die
undefined-Schritte. Beheben Sie jeden Mismatch. Das ist normalerweise ein fünfminütiger Find-and-Replace-Job. -
Auditieren Sie Ihre Runner-Profile. Listen Sie jeden Tag-Ausdruck in Ihrem
cucumber.js, Mavenpom.xmloder Gradle-Config auf. Bestätigen Sie, dass jeder absichtlich ist. Ersetzen Sie positive Filter (--tags '@regression') durch negative (--tags 'not @wip'), damit neue Szenarien standardmäßig ausgeführt werden. -
Fügen Sie das Skip-Check-Script zu CI hinzu. Lassen Sie den Build bei jedem unerwarteten Skip failen. Das ist ein einmaliges Setup, das sich für immer auszahlt.
-
Planen Sie einen monatlichen Tag-Audit. Durchsuchen Sie Ihre Feature-Dateien nach
@wipund zählen Sie sie. Wenn die Zahl wächst, haben Sie ein Prozessproblem, kein Tooling-Problem.
FAQ
Warum überspringt Cucumber Szenarien, anstatt sie zu failen, wenn eine Step Definition fehlt?
Cucumber behandelt eine fehlende Step Definition als unvollständige Spezifikation, nicht als Code-Defect. Das Szenario wird übersprungen, weil Cucumber nicht ausführen kann, was es nicht versteht. Das ist historisches Verhalten aus der ursprünglichen Ruby-Implementierung und es hält sich über alle Ports hinweg. Der einzige Weg, es zu einem Failure zu machen, ist ein Post-Processor, der auf undefined-Status prüft.
Was ist der Unterschied zwischen einem übersprungenen Szenario und einem pending Szenario?
In modernen Cucumber-Versionen ist “pending” ein Step-Level-Status, der explizit von einer Step Definition geworfen wird (zum Beispiel durch Aufruf von pending() in Ruby oder Rückgabe von 'pending' in JavaScript). “Skipped” ist ein Szenario-Level-Status, der angewendet wird, wenn eine Voraussetzung fehlschlägt, ein Tag-Filter das Szenario ausschließt oder ein vorheriger Schritt fehlgeschlagen ist. In der Praxis erscheinen beide als gelb und bedeuten beide “dies wurde nicht bis zum Ende ausgeführt.”
Kann ich Cucumber so konfigurieren, dass es bei fehlenden Step Definitions failt? Es gibt dafür in den meisten Cucumber-Ports kein built-in CLI-Flag. Sie müssen das JSON- oder JUnit-Output parsen und den Build selbst failen lassen. Das Script in diesem Post ist ein minimales funktionierendes Beispiel.
Wie finde ich heraus, welche Tags tatsächlich in meiner Suite verwendet werden?
Führen Sie einen grep über Ihre Feature-Dateien aus: grep -roh '@[a-zA-Z0-9_-]*' features/ | sort | uniq -c | sort -rn. Das gibt Ihnen eine Häufigkeitstabelle jedes Tags. Gleichen Sie das mit Ihren Runner-Profilen ab, um Tags zu finden, die ohne Dokumentation filtern.