Spesifikasi Gherkin Anda sedang berbohong kepada Anda.
Bukan dengan sengaja. Mereka dimulai dengan setia. Namun enam sprint kemudian, seseorang me-refactor alur checkout dan lupa memperbarui langkah When the user submits payment. File .feature masih lolos, karena step definition masih ada. Ia hanya memanggil kode yang tidak lagi cocok dengan apa yang sebenarnya dijelaskan skenario tersebut. Anda memiliki pengujian hijau dan rasa percaya diri yang palsu. Inilah trajektori default BDD kecuali Anda secara aktif melawannya.
Masalahnya bukan karena developer malas. Melainkan hubungan antara file .feature dan step definition secara fundamental longgar. Skenario Gherkin adalah string. Step definition adalah regex atau anotasi yang cocok dengan string tersebut. Tidak ada compiler yang memaksa perubahan skenario memerlukan perubahan kode yang sesuai, atau sebaliknya. Toolchain mengasumsikan Anda akan secara manual menjaga keduanya selaras. Anda tidak akan melakukannya.
Mengapa disiplin manual gagal pada skala besar
Setiap tim memulai dengan rencana yang sama: tulis spesifikasi, implementasikan langkah-langkahnya, perbarui keduanya bersamaan. Ini berhasil di minggu pertama.
Ia rusak saat refactoring. Anda mengubah nama konsep domain dalam kode, namun Gherkin masih menggunakan terminologi lama karena mengubahnya berarti memperbarui dua belas file fitur dan meninjau ulang bersama product. Atau Anda mengekstrak aturan validasi baru, namun skenario yang ada secara implisit mengandalkan perilaku lama, dan tidak ada yang menyadarinya karena step definition secara diam-diam digeneralisasi agar pengujian tetap lolos. Spesifikasi menjadi semacam paralel universe yang semakin tidak akurat.
Biayanya bukan sekadar dokumentasi yang usang. Melainkan kepercayaan. Begitu developer berhenti percaya bahwa file fitur menggambarkan realitas, mereka berhenti membacanya. Lalu mereka berhenti menulisnya. Lalu Anda kembali ke unit test dengan nama yang tidak transparan dan tidak ada bahasa bersama dengan stakeholder.
Apa artinya “selaras” sebenarnya
Menjaga spesifikasi tetap selaras bukan tentang membuat pengujian lolos. Lolos itu mudah. Selaras berarti tiga hal:
- Setiap langkah Gherkin memiliki step definition yang sesuai yang melakukan apa yang dikatakan spesifikasi.
- Setiap step definition benar-benar diraih oleh setidaknya satu skenario.
- Bahasa dalam spesifikasi cocok dengan bahasa dalam basis kode.
Kebanyakan tim hanya memverifikasi poin pertama, dan mereka melakukannya saat runtime. Anda perlu memverifikasi ketiganya, dan Anda perlu melakukannya di CI sebelum kode di-merge.
Validasi langkah otomatis dengan binding yang ketat
String matching yang longgar pada tools seperti Cucumber adalah akar masalahnya. Anda bisa memperketatnya dengan menjadikan step definition sebagai first-class reference yang dapat divalidasi oleh build.
Pada proyek TypeScript atau JavaScript, Anda bisa mengganti step definition berbasis regex dengan generated step registry yang memetakan langkah Gherkin ke referensi fungsi yang sebenarnya. Kuncinya adalah pemetaan tersebut di-generate, bukan ditulis tangan, sehingga build gagal jika skenario mereferensikan langkah yang tidak ada.
Berikut setup minimal menggunakan parser kustom dan generated registry. Pertama, parse file .feature Anda saat build time:
// scripts/validate-steps.ts
import { readFileSync, readdirSync } from 'fs';
import { parse } from '@cucumber/gherkin';
import { IdGenerator } from '@cucumber/messages';
const featureFiles = readdirSync('./features').filter(f => f.endsWith('.feature'));
const allSteps = new Set<string>();
for (const file of featureFiles) {
const content = readFileSync(`./features/${file}`, 'utf-8');
const gherkinDocument = parse(content, new IdGenerator());
for (const feature of gherkinDocument.feature?.children || []) {
for (const step of feature.scenario?.steps || []) {
allSteps.add(step.text);
}
}
}
// Import the actual step registry from your test code
import { stepRegistry } from '../steps/registry';
const registeredSteps = new Set(Object.keys(stepRegistry));
const undefinedSteps = [...allSteps].filter(s => !registeredSteps.has(s));
const orphanedSteps = [...registeredSteps].filter(s => !allSteps.has(s));
if (undefinedSteps.length > 0) {
console.error('Undefined steps:', undefinedSteps);
process.exit(1);
}
if (orphanedSteps.length > 0) {
console.error('Orphaned steps:', orphanedSteps);
process.exit(1);
}
console.log(`Validated ${allSteps.size} steps against ${registeredSteps.size} definitions.`);
Step registry Anda mengekspos fungsi berdasarkan teks Gherkin yang tepat:
// steps/registry.ts
import { given, when, then } from './step-helpers';
export const stepRegistry: Record<string, Function> = {
'the user is logged in': given.theUserIsLoggedIn,
'the user adds an item to the cart': when.theUserAddsAnItemToTheCart,
'the total should be {int}': then.theTotalShouldBe,
};
Objek given, when, dan then adalah module biasa dengan fungsi-fungsi. Tidak ada keajaiban regex. Jika seorang developer mengubah teks Gherkin, mereka harus menambahkan entri yang sesuai ke registry, atau build akan gagal. Jika mereka menghapus skenario, deteksi orphaned step akan menangkap definisi yang tersisa.
Hubungkan ke CI sebelum merge
Script yang dijalankan developer secara lokal adalah script yang developer lupa jalankan. Anda perlu membuat validasi gagal saat build.
Tambahkan ke pipeline pengujian Anda:
# .github/workflows/ci.yml
jobs:
validate-specs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx ts-node scripts/validate-steps.ts
- run: npm test
Detail pentingnya adalah validate-steps.ts dijalankan sebelum test suite yang sebenarnya. Jika ada ketidakcocokan antara file fitur dan step definition, Anda ingin gagal cepat dengan error yang jelas, bukan menjalankan seratus skenario cucumber yang mungkin diam-diam lolos pada logika yang sudah usang.
Living documentation memerlukan laporan yang di-generate
Validasi menjaga sintaks tetap selaras, namun tidak menjamin spesifikasi mudah dibaca atau bermanfaat. Untuk itu, Anda memerlukan pipeline living documentation yang menghasilkan laporan HTML dari file fitur Anda dan menerbitkannya setiap kali merge ke main.
Tools seperti Cucumber Reports atau Pickles dapat mengubah file .feature Anda menjadi dokumentasi yang dapat ditelusuri. Kuncinya adalah dokumen tersebut di-generate dari file yang sama yang divalidasi CI. Jika skenario dihapus, ia menghilang dari dokumen. Jika bahasanya berubah, dokumen terupdate secara otomatis. Tidak ada sumber kebenaran kedua yang perlu dipelihara.
Terbitkan laporan sebagai artifact di CI, atau deploy ke static site:
# .github/workflows/docs.yml
jobs:
publish-docs:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- run: npm install -g @picklesdoc/pickles
- run: pickles --feature-directory=./features --output-directory=./docs
- uses: actions/upload-pages-artifact@v3
with:
path: ./docs
Stakeholder tidak perlu membaca Gherkin mentah. Mereka memerlukan halaman yang mudah dibaca dan mereka percaya halaman tersebut selalu terbaru. Otomasi membangun kepercayaan tersebut.
Trade-off: ketat versus ekspresif
Pendekatan registry memiliki biaya. Anda kehilangan fleksibilitas pola regex seperti /^the user adds (\d+) items? to the cart$/. Setiap varian menjadi entri eksplisit, atau langkah terparameter dengan typed placeholder. Ini verbose.
Alternatifnya adalah mempertahankan regex namun menambahkan linter yang lebih ketat yang memperingatkan ketika pola terlalu luas atau ketika teks langkah tidak cocok dengan pola yang dikenal. Anda bisa mendapatkan 80% keamanan dengan 20% verbosity dengan menggunakan flag bawaan Cucumber dry-run dan publish, dikombinasikan dengan linter kustom yang memeriksa step definition yang tidak terpakai.
# Dry-run parses all features without executing them, surfacing undefined steps
npx cucumber-js --dry-run
Ini kurang ketat dibanding pendekatan registry. Ia menangkap undefined steps, namun tidak orphaned steps, dan tidak memaksa semantic alignment. Untuk tim dengan suite yang sudah besar, ini adalah titik awal yang pragmatis. Untuk proyek baru, pendekatan registry membuahkan hasil dalam sebulan.
Apa yang sudah kami coba dan tidak berhasil
Kami bereksperimen dengan menghasilkan Gherkin dari code comments. Idinya adalah developer akan menganotasi method test mereka, dan sebuah tool akan menghasilkan file .feature. Ia gagal karena Gherkin seharusnya dapat dibaca oleh non-developer. Prosa yang di-generate dari nama method tidak mudah dibaca. Ia bahkan bukan prosa.
Kami juga mencoba menegakkan pair programming untuk setiap perubahan spesifikasi. Ini membantu, namun tidak scalable. Masalahnya bersifat mekanis, dan perbaikannya juga harus bersifat mekanis.
Mulai dengan deteksi undefined step hari ini
Jika Anda memiliki suite Cucumber yang sudah ada, perubahan paling kecil yang paling bermanfaat adalah menambahkan --dry-run ke pipeline CI Anda. Butuh waktu lima menit dan akan menangkap drift paling umum: skenario yang di-refactor sehingga tidak lagi cocok dengan step definition mana pun.
Jika Anda memulai dari awal, pertimbangkan pendekatan berbasis registry. Biaya awal dari pemetaan eksplisit diganti dengan jaminan saat build-time dan kepercayaan untuk me-refactor secara bebas tanpa khawatir spesifikasi Anda secara diam-diam menjadi usang.
Spesifikasi Gherkin Anda harus menggambarkan apa yang dilakukan sistem. Jika Anda tidak dapat mempercayainya untuk melakukan itu, mereka hanyalah komentar yang mahal. Otomatiskan pemeriksaan yang menjaga agar mereka tetap jujur, atau terimalah bahwa mereka akan berbohong kepada Anda.