Pindah Tempat Test, Coverage Malah Naik
Migrasi test ke pohon terpusat dengan paket _test Go memaksa rewrite lewat API publik, menghapus helper mati, dan coverage naik tanpa menyentuh kode produksi.
Ringkasan
Gara-gara pindahin file test ke folder sentral, coverage malah naik, bukan stagnan. Kuncinya ganti deklarasi jadi package entityadmin_test, jadi test cuma bisa akses API publik dan kompilator yang jadi polisinya. Kalau internal memang wajib diuji, ada katup darurat export_test.go, jadi jangan harap sekadar git mv.
Pagi itu saya menjalankan go test ./... setelah memindahkan enam belas berkas test dari dalam internal/entityadmin/ ke pohon terpusat api/tests/unit/entityadmin/. Kode produksi tidak tersentuh satu baris pun. Dugaan saya wajar: ini cuma kerjaan pindah file, angka coverage pasti diam di tempat.
Tebakan itu meleset di dua tempat. Pertama, di modul yang migrasinya selesai lebih dulu, coverage justru naik setelah pindahan, bukan turun atau stagnan. Kedua, deretan commit move tests to central tree ini ternyata tidak semekanisme itu. Pesan commitnya menulis mechanical test relocation, tapi isi diff bicara lain: nol simbol diekspor, sementara test yang sebelumnya memanggil fungsi internal seperti mapError, clampDays, atau normalizeStatus harus ditulis ulang lewat jalur publik.
Deklarasi paket yang mengubah aturan main
Kuncinya satu baris: semua test pindahan tidak lagi membuka berkas dengan package entityadmin, melainkan package entityadmin_test.
package entityadmin_test // bukan package entityadmin
import (
"testing"
"github.com/example/kota/internal/entityadmin"
)
Di Go, test dalam paket ber-akhiran _test hanya boleh memakai identifier yang diekspor dari paket yang diuji. Dokumentasi resmi menyebut pola ini black box testing [1], dan style guide Google menempatkan akhiran _test khusus untuk test yang hanya menyentuh API publik paket [2]. Kompilator yang jadi polisi. Begitu deklarasi berganti, semua jendela ke internal tertutup otomatis, tanpa linter tambahan atau janji di code review.
Efek sampingnya terasa langsung. Test handler kini menembus router chi sungguhan lewat RegisterAdminRoutes, dengan request yang dibangun pakai httptest [3][5].
Test kasus kecil seperti pemetaan error atau normalisasi status juga tetap ter-cover, kali ini begitu versi publik Create dan Get dipanggil. Helper mati seperti routeCtx terhapus begitu saja karena pemanggil langsungnya sudah tidak ada.
Contoh konkret yang membuat saya yakin ini bukan kosmetik: fungsi kecil seperti slugify atau isEmailFormat yang tadinya punya berkas test sendiri sekarang diuji lewat svc.Create dan svc.CreateCategory versi publik. Matriks validasinya tetap utuh, tapi semuanya masuk lewat pintu yang sama seperti pemanggil nyata.
Test integrasi ikut pindah ke api/tests/integration/ dengan build tag masing-masing, jadi go test ./... harian tetap ringan sementara suite berat tinggal dipanggil saat dibutuhkan.
Di sini bagian yang melawan intuisi saya: coverage naik justru karena pembersihan. Jalur yang tadinya dicapai lewat helper internal ternyata tetap terjangkau dari API publik, jadi cakupan tidak runtuh meski test dibatasi. Kode test mati yang dibuang juga mengurangi beban pemeliharaan. Paksaan batas paket berfungsi sekaligus sebagai pembersih arsitektur test sekalian.
Katup darurat kalau internal memang harus diuji
Ada kasus yang tidak bisa diselesaikan niat baik saja: logika internal yang berat dan mustahil dijangkau dari API publik. Pustaka standar Go menyediakan jawaban resminya lewat berkas export_test.go, berkas test di dalam paket yang mengekspor ulang internal secara eksplisit untuk keperluan pengujian [4]. Paket bufio di pustaka standar membawa satu; isinya cuma alias ke simbol internal di dalam paketnya.
Saya suka pola ini karena jujur. Satu berkas kecil yang menyatakan niat, gampang dicari, dan gampang dihapus saat internalnya berubah. Bedanya dengan test white-box lama: yang lama menyebar ke mana-mana, yang ini terkonsentrasi di satu titik yang bisa diaudit.
Yang saya bawa pulang
Setelah seluruh modul lewat, pohon api/tests/ berisi satu tempat untuk semua test, nama paketnya konsisten ber-akhiran _test, dan tidak ada satu pun simbol produksi yang dibuka cuma demi test.
Migrasi tipe ini murah direview karena kode produksi tak tersentuh, tapi jangan mengharapkan sekadar git mv. Begitu deklarasi paket berganti jadi foo_test, kompiler yang memaksa test berperilaku seperti klien eksternal, dan semua test yang menyentuh internal ketahuan seketika.
Untuk repo berikutnya, paket _test jadi default untuk test unit, export_test.go cuma katup darurat, dan pesan commit yang mengklaim mechanical akan saya baca lebih sinis.