Separuh skenario Cucumber Anda diskip. Bukan gagal. Diskip.

Status kuning tersebut lebih buruk daripada build merah. Status itu membuat suite Anda terlihat sehat sementara menyembunyikan tiga masalah yang benar-benar berbeda di balik satu label yang sopan. Skenario yang diskip bisa berarti tag filter mengecualikannya, sebuah step definition hilang, atau hook Before melempar exception bahkan sebelum langkah pertama dijalankan. Tugas Anda adalah mencari tahu parasit mana yang hidup di suite Anda.

Mengapa “Skipped” Adalah Status Cucumber yang Paling Berbahaya

Cucumber memiliki tiga status terminal untuk sebuah skenario: passed, failed, dan skipped. Passed dan failed adalah jujur. Skipped adalah tempat sampah.

Ketika sebuah skenario diskip, Cucumber memberi tahu Anda bahwa beberapa prasyarat tidak terpenuhi. Masalahnya, “prasyarat” tersebut bisa berupa apa saja, mulai dari tag filter yang disengaja hingga null pointer di setup hook. Sebuah suite dengan 50% skenario diskip terlihat seperti menjalankan 500 tes. Padahal tidak. Suite itu menjalankan 250 tes dan melambaikan tangan kepada 250 lainnya tanpa memeriksanya.

Kebanyakan dashboard CI menganggap skipped sebagai netral. Pipeline Anda hijau. Namun separuh spesifikasi perilaku Anda tidak diverifikasi. Jika Anda menggunakan Cucumber sebagai living documentation, separuh dokumentasi Anda adalah kebohongan.

Tiga Penyebab Utama Skipping Massal

Ada tepat tiga cara untuk menghasilkan skenario yang diskip di Cucumber. Dua di antaranya adalah bug. Satu adalah miskonfigurasi yang terlihat seperti fitur.

Tag filter yang mengecualikan lebih dari yang Anda maksudkan

Penyebab paling umum dari 50% skenario diskip adalah ekspresi tag di konfigurasi runner Anda yang lebih luas dari yang Anda kira. Profil CI Anda mungkin terlihat seperti ini:

// cucumber.js
module.exports = {
  default: [
    '--format', 'progress',
    '--tags', 'not @wip',
  ].join(' '),

  ci: [
    '--format', 'json:reports/cucumber.json',
    '--tags', 'not @wip and not @slow',
  ].join(' '),
};

Kerusakan sebenarnya terjadi ketika tim menggunakan tag filter positif. Filter seperti --tags '@regression' hanya menjalankan skenario yang diberi tag @regression. Setiap skenario lainnya diskip. Inilah cara Anda beralih dari 100% dieksekusi ke 50% dieksekusi tanpa mengubah satu pun file feature.

Step definition yang tidak lagi cocok

Cucumber mengecualikan seluruh skenario jika ada langkah yang tidak memiliki step definition yang cocok. Ini bukan kegagalan. Ini adalah skip. CI Anda tetap hijau. Ini terjadi lebih sering dari yang Anda duga. Seorang product manager mengedit file Gherkin untuk memperbaiki typo di sebuah langkah:

# 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

Step definition Anda mengharapkan ini:

// features/step_definitions/checkout_steps.js
Given('a guest user with items in the cart', function () {
  // setup
});

Perhatikan “the” yang hilang di file feature. Regex Cucumber tidak lagi cocok. Seluruh skenario diskip. Kalikan ini di seluruh tim yang merefactor file feature tanpa menjalankan full suite secara lokal, dan Anda bisa kehilangan separuh coverage karena drift.

Hook yang gagal dan menyamar sebagai skip

Jika hook Before melempar exception, Cucumber mengecualikan setiap skenario di file feature tersebut. Tidak menggagalkannya. Melainkan mengecualikannya.

// 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);
});

Jika TEST_URL tidak terdefinisi di CI, hook melempar exception dan setiap skenario yang terkait dengannya diskip. Build Anda hijau. Tes Anda tidak berharga.

Cara Mendiagnosis Parasit Mana yang Anda Miliki

Anda tidak bisa memperbaiki 50% skenario diskip tanpa mengetahui mana dari tiga penyebab yang bertanggung jawab. Output default Cucumber tidak memberi tahu Anda. Anda harus menginterogasinya.

Jalankan suite Anda dengan --dry-run dan tanpa tag filter:

npx cucumber-js --dry-run --tags ''

Dry run menguraikan setiap file feature dan mencocokkan setiap langkah dengan step definition tanpa mengeksekusi apa pun. Jika menunjukkan 100% undefined, Anda mengalami step definition drift. Jika menunjukkan 100% passed, skip Anda berasal dari tag filter atau runtime hook.

JSON formatter menyertakan field status untuk setiap langkah. Skenario yang diskip oleh tag filter tidak akan memiliki langkah sama sekali. Skenario yang diskip karena step definition yang hilang akan menunjukkan undefined di langkah yang tidak cocok. Skenario yang diskip karena hook yang gagal akan menunjukkan skipped di setiap langkah, dan array embeddings mungkin berisi error dari hook tersebut.

npx cucumber-js --format json:report.json

Parse JSON tersebut dan hitung berapa banyak skenario yang memiliki nol langkah dibandingkan dengan yang memiliki langkah dengan status undefined atau skipped. Itu memberi tahu Anda ke mana harus mencari.

Perbaikan jangka panjang yang paling aman adalah menganggap setiap skip yang tidak diharapkan sebagai kegagalan build. Sebuah post-processor kecil untuk JSON report dapat melakukan tugas tersebut:

// 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);
}

Jalankan ini setelah setiap pemanggilan Cucumber di CI. Ini akan menangkap miskonfigurasi tag, step definition yang hilang, dan hook yang gagal sebelum mereka membusuk suite Anda secara diam-diam.

Pertukaran: Mengapa Sebagian Skipping Itu Baik

Skipping yang disengaja adalah alat yang valid. Tag @wip ada untuk suatu alasan. Anda menulis skenario sebelum implementasi, menandainya @wip, dan membiarkan runner mengecualikannya hingga kode siap.

Perbedaan antara skipping yang sehat dan pembusukan suite adalah kebersihan. @wip harus bersifat sementara. Ia harus berada di branch, bukan di main selama enam bulan. Jika 50% skenario Anda diberi tag @wip di default branch, Anda tidak memiliki test suite. Anda memiliki daftar keinginan.

Filtering berbasis tag juga masuk akal untuk tes yang spesifik lingkungan. Skenario yang memerlukan terminal pembayaran fisik tidak boleh dijalankan di CI. Namun skenario tersebut harus diberi tag @hardware, bukan @slow atau @manual. Jelaskan secara eksplisit mengapa sesuatu dikecualikan, dan audit pengecualian tersebut saat code review sama seperti Anda mengaudit kode itu sendiri.

Cara Menghentikan Pembusukan

Jika Anda berada di 50% diskip hari ini, berikut adalah jalur tercepat kembali ke kejujuran.

  1. Jalankan dry run tanpa tag. Hitung langkah-langkah undefined. Perbaiki setiap ketidakcocokan. Ini biasanya pekerjaan find-and-replace lima menit.

  2. Audit profil runner Anda. Daftarkan setiap ekspresi tag di cucumber.js, Maven pom.xml, atau konfigurasi Gradle Anda. Konfirmasikan bahwa masing-masing adalah disengaja. Ganti filter positif (--tags '@regression') dengan filter negatif (--tags 'not @wip') sehingga skenario baru dijalankan secara default.

  3. Tambahkan script skip-check ke CI. Buat agar build gagal pada setiap skip yang tidak diharapkan. Ini adalah pengaturan satu kali yang membuahkan hasil selamanya.

  4. Jadwalkan audit tag bulanan. Cari file feature Anda untuk @wip dan hitung jumlahnya. Jika angkanya bertambah, Anda memiliki masalah proses, bukan masalah tooling.


FAQ

Mengapa Cucumber mengecualikan skenario alih-alih menggagalkannya ketika step definition hilang? Cucumber menganggap step definition yang hilang sebagai spesifikasi yang tidak lengkap, bukan defect kode. Skenario diskip karena Cucumber tidak dapat mengeksekusi apa yang tidak dipahaminya. Ini adalah perilaku historis dari implementasi Ruby asli, dan bertahan di berbagai port. Satu-satunya cara untuk membuatnya gagal adalah menambahkan post-processor yang memeriksa status undefined.

Apa perbedaan antara skenario yang diskip dan skenario yang pending? Di versi Cucumber modern, “pending” adalah status tingkat langkah yang dilempar secara eksplisit oleh step definition (misalnya, memanggil pending() di Ruby atau mengembalikan 'pending' di JavaScript). “Skipped” adalah status tingkat skenario yang diterapkan ketika prasyarat gagal, tag filter mengecualikan skenario, atau langkah sebelumnya gagal. Dalam praktiknya, keduanya muncul sebagai kuning dan keduanya berarti “ini tidak berjalan hingga selesai.”

Bisakah saya membuat Cucumber gagal ketika step definition hilang? Tidak ada flag CLI bawaan untuk ini di sebagian besar port Cucumber. Anda harus memparse output JSON atau JUnit dan menggagalkan build sendiri. Script di posting ini adalah contoh kerja minimal.

Bagaimana cara menemukan tag mana yang sebenarnya digunakan di suite saya? Jalankan grep di seluruh file feature Anda: grep -roh '@[a-zA-Z0-9_-]*' features/ | sort | uniq -c | sort -rn. Ini memberi Anda tabel frekuensi untuk setiap tag. Cross-reference hasil tersebut dengan profil runner Anda untuk menemukan tag yang memfilter tanpa dokumentasi.