Apa yang Terjadi pada Kode Anda Sebelum Masuk ke Produksi
Anda baru saja menyelesaikan sebuah fitur. Anda menjalankannya secara lokal, berhasil, lalu push kode. Lalu apa?
Antara push tersebut dan saat kode Anda berjalan di produksi, banyak hal harus terjadi. Bukan sekadar membangun artefak, tetapi memeriksa apakah kode tersebut benar-benar aman, benar, dan mudah dipelihara. Jika Anda melewatkan pemeriksaan ini, Anda bertaruh bahwa setiap pengembang di tim menulis kode sempurna setiap saat. Tidak ada yang bisa.
Proses menjalankan pemeriksaan otomatis setiap kali seseorang push kode disebut continuous integration, atau CI. Idenya sederhana: tangkap masalah sejak dini, saat biaya perbaikannya murah. Bug yang ditemukan lima menit setelah push hanya menghabiskan biaya secangkir kopi. Bug yang ditemukan di produksi berarti laporan insiden, rollback, dan lembur.
Mulai dengan Unit Test
Hal pertama yang dijalankan sebagian besar pipeline adalah unit test. Namun, apa yang dimaksud dengan unit test yang baik lebih penting dari yang disadari kebanyakan tim.
Unit test yang baik tidak menguji satu fungsi atau metode hanya karena fungsi itu ada. Ia menguji perilaku yang bermakna dari titik masuk yang relevan. Untuk backend service, titik masuk itu biasanya adalah endpoint API atau use case. Tes memanggil endpoint tersebut, dan memeriksa apakah responsnya sesuai dengan yang diharapkan. Di belakang layar, permintaan mengalir melalui controller, service layer, logika domain, dan batas repository. Jalur aplikasi internal berjalan secara nyata; layanan eksternal seperti database produksi, message queue, dan API pihak ketiga digantikan dengan test doubles yang terkendali, instance tes lokal, mock, atau stub.
Pendekatan ini memiliki keuntungan praktis: Anda dapat mengubah implementasi internal suatu fungsi tanpa merusak tes. Tes peduli pada apa yang dilakukan sistem, bukan bagaimana cara kerjanya. Itu berarti Anda dapat melakukan refactor dengan bebas, selama perilakunya tetap sama.
Unit test harus cepat. Jika memakan waktu lebih dari beberapa detik, ada yang tidak beres. Karena cepat, Anda bisa menjalankannya di setiap commit. Itu memberikan umpan balik langsung kepada pengembang: "Perubahan Anda merusak sesuatu, perbaiki sekarang."
Linting Menangkap Gaya dan Code Smell
Setelah unit test lulus, pemeriksaan berikutnya biasanya linting. Linting tidak menguji logika. Ia memeriksa apakah kode mengikuti aturan gaya yang konsisten dan apakah ada pola yang sering menyebabkan bug.
Linter akan menangkap hal-hal seperti:
- Variabel yang dideklarasikan tetapi tidak pernah digunakan
- Fungsi yang terlalu panjang dan harus dipecah
- Indentasi atau penamaan yang tidak konsisten
- Pola yang berpotensi tidak aman yang terlihat benar tetapi sebenarnya tidak
Linting mungkin terasa seperti masalah kecil dibandingkan kebenaran, tetapi ini lebih penting dari yang Anda kira. Kode yang terlihat konsisten lebih mudah dibaca. Kode yang lebih mudah dibaca lebih mudah di-review. Kode yang lebih mudah di-review memiliki lebih sedikit bug. Ini adalah rantai yang dimulai dengan pemeriksaan otomatis sederhana.
Integration Test Memeriksa Koneksi
Unit test membuktikan bahwa perilaku aplikasi berfungsi sementara tetangga eksternal dikendalikan. Integration test membuktikan bahwa aplikasi dapat bekerja dengan dependensi nyata.
Untuk backend service, integration test mungkin memutar database tes nyata, memulai service, dan memverifikasi bahwa panggilan API benar-benar membaca dan menulis data dengan benar. Atau mungkin menguji bahwa background worker dapat mengirim pesan ke antrian dan memproses responsnya.
Integration test lebih lambat dari unit test. Mereka membutuhkan dependensi nyata: database, antrian, mungkin cache. Itulah mengapa mereka dijalankan setelah unit test, bukan sebelumnya. Jika unit test gagal, tidak ada gunanya menjalankan integration test. Perubahannya sudah rusak.
Beberapa tim menjalankan integration test terhadap lingkungan tes khusus yang mencerminkan produksi semirip mungkin. Yang lain menjalankannya di dalam pipeline CI menggunakan kontainer. Either way, tujuannya sama: tangkap masalah yang hanya muncul saat komponen berinteraksi.
Security Scan Mencari Kerentanan pada Kode Anda
Security scanning memeriksa kode yang Anda tulis untuk kerentanan umum. Hal-hal seperti SQL injection, cross-site scripting, atau penggunaan fungsi kriptografi yang salah.
Pemindaian ini adalah alat otomatis yang mencari pola yang diketahui berbahaya. Mereka tidak sempurna. Mereka bisa melewatkan sesuatu, dan bisa menghasilkan false positive. Tapi mereka menangkap low-hanging fruit yang mungkin terlewatkan oleh reviewer manusia.
Kerentanan yang ditemukan selama CI hanya membutuhkan tiket dan perbaikan. Kerentanan yang ditemukan di produksi berarti pelanggaran, pengungkapan, dan banyak kepercayaan yang hilang.
Dependency Check Mencari Kerentanan di Pustaka Anda
Hampir tidak ada backend service yang ditulis dari awal. Anda menggunakan framework, library, dan package. Masing-masing adalah kode yang ditulis oleh orang lain, dan kode itu bisa memiliki kerentanan.
Dependency checking membandingkan versi library Anda dengan database kerentanan publik. Jika library yang Anda gunakan memiliki masalah keamanan yang diketahui, pemeriksaan akan menandainya. Beberapa tim mengonfigurasi pipeline untuk memblokir build jika kerentanan kritis ditemukan. Yang lain membiarkannya lolos tetapi memberi tahu tim untuk review manual.
Pemeriksaan ini penting karena kerentanan pada dependensi itu umum dan seringkali parah. Insiden Log4j pada tahun 2021 adalah contoh yang terkenal: library logging yang banyak digunakan memiliki kerentanan yang memungkinkan eksekusi kode jarak jauh. Tim yang memiliki dependency checking di pipeline mereka mengetahuinya dengan cepat. Tim yang tidak menghabiskan waktu berhari-hari untuk mencari tahu apa yang mereka gunakan.
Urutan Pemeriksaan Itu Penting
Urutan pemeriksaan ini tidak acak. Ini mengikuti logika praktis:
Diagram di bawah menunjukkan alur pipeline dan pemeriksaan mana yang memblokir build.
Berikut adalah workflow GitHub Actions minimal yang menerapkan urutan ini:
name: CI Pipeline
on: [push]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- run: npm run lint
unit-test:
needs: lint
runs-on: ubuntu-latest
steps:
- run: npm test -- --coverage
integration-test:
needs: unit-test
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
steps:
- run: npm run test:integration
security-scan:
needs: integration-test
runs-on: ubuntu-latest
steps:
- run: npm run audit
dependency-check:
needs: security-scan
runs-on: ubuntu-latest
steps:
- run: npm audit --audit-level=high
- Unit test dan linting dijalankan terlebih dahulu karena cepat. Mereka segera menyaring perubahan yang jelas-jelas rusak atau berantakan.
- Integration test dijalankan berikutnya. Mereka lebih lambat tetapi tetap penting. Jika unit test lulus tetapi integration test gagal, Anda tahu komponen tidak bekerja sama.
- Security scan dan dependency check dijalankan terakhir. Mereka seringkali yang paling lambat, dan lebih kecil kemungkinannya untuk gagal pada perubahan biasa.
Urutan ini memberikan umpan balik cepat kepada pengembang untuk masalah umum. Jika Anda merusak unit test, Anda akan tahu dalam hitungan detik. Jika Anda memperkenalkan dependensi yang rentan, Anda akan mengetahuinya dalam hitungan menit, bukan hari.
Tidak Semua Pemeriksaan Harus Memblokir Pipeline
Beberapa tim menjalankan semua pemeriksaan sebagai blocking: jika ada pemeriksaan yang gagal, pipeline berhenti, dan artefak tidak dibangun. Tim lain mengizinkan pemeriksaan tertentu untuk lolos dengan peringatan, terutama security scan yang mungkin menghasilkan false positive.
Yang penting adalah konsistensi. Setiap perubahan melewati pemeriksaan yang sama. Kualitas tidak bergantung pada apakah seorang pengembang ingat menjalankan tes secara lokal. Itu bergantung pada pipeline.
Daftar Periksa Praktis untuk Pipeline CI Anda
Jika Anda menyiapkan atau meninjau pipeline CI untuk backend service, berikut adalah daftar periksa singkat:
- Unit test berjalan di setiap commit dan selesai dalam waktu kurang dari satu menit
- Linting berjalan sebelum atau bersamaan dengan unit test
- Integration test berjalan dengan dependensi nyata di lingkungan yang terkendali
- Security scanning memeriksa kode Anda sendiri untuk kerentanan umum
- Dependency checking membandingkan library Anda dengan database kerentanan yang diketahui
- Pipeline gagal dengan cepat: pemeriksaan cepat dijalankan terlebih dahulu, pemeriksaan lambat dijalankan kemudian
- Setiap perubahan melewati pemeriksaan yang sama, terlepas dari siapa yang menulisnya
Apa Selanjutnya
Setelah semua pemeriksaan ini lulus, artefak dibangun dan siap. Tetapi mendapatkan artefak yang dibangun hanyalah setengah dari cerita. Pertanyaan selanjutnya adalah bagaimana menempatkan versi baru itu ke server tanpa mengganggu pengguna. Di sinilah strategi deployment berperan, dan itulah yang akan kita bahas selanjutnya.
Untuk saat ini, intinya adalah: kualitas sistem produksi Anda ditentukan jauh sebelum mencapai produksi. Itu ditentukan dalam hitungan menit setelah setiap push, saat pemeriksaan otomatis memutuskan apakah kode Anda aman untuk dilanjutkan. Buat pemeriksaan itu cepat, buat konsisten, dan jalankan setiap saat.