Skip to content

Coverage 0 Persen Padahal Semua Test Hijau

Adityo Guni Waluyo

Menyusun ulang test ke pohon terpusat membuat coverage package terbaca 0 persen; satu flag -coverpkg pada mode coverage mengembalikan angkanya.

Ringkasan

Test pindah ke folder terpusat bikin coverage Go jadi 0.0% padahal semua test lulus. Soalnya secara default tiap test cuma ngukur package-nya sendiri, bukan package lain yang dipanggil. Solusinya gampang, tinggal tambah flag -coverpkg=./internal/...

Baris akhir laporan test menampilkan dua hal yang bertolak belakang: seluruh test lulus, sementara package yang memuat hampir semua logika bisnis tercatat coverage: 0.0% of statements. Tidak ada error, tidak ada test yang gagal, cukup satu angka nol berdiri di tempat yang seharusnya penuh.

Kejadian ini mengikuti satu langkah restrukturisasi: seluruh file test dipindah dari sisi package ke pohon test terpusat pada direktori unit dan integration, dengan nama package berakhiran _test yang mengimpor package di dalam modul secara black-box. CI tidak berubah dan tetap menjalankan go test ./..., sehingga semua test tetap tereksekusi. Yang berubah hanya angkanya: dari angka wajar langsung jatuh ke nol.

Aturan Default: Tiap Test Mengukur Package-nya Sendiri

Semantik resminya ada di dokumentasi flag go test: pola pada -coverpkg "Apply coverage analysis in each test to packages whose import paths match the patterns. The default is for each test to analyze only the package being tested" [1]. Diterjemahkan ke situasi di atas: setelah test pindah ke package lain, package yang diukur oleh tiap test bukan lagi package yang isinya dijalankan.

Mekanismenya ada di tahap instrumentasi. Saat go test -cover berjalan, Go hanya memilih package tertentu untuk dipasangi penghitung; package lain yang ikut terpanggil tidak disisipi apa pun [2]. Test black-box tetap mengeksekusi kode di pohon internal/ dari awal sampai akhir, tetapi tidak ada satu baris pun di sana yang tercatat. Angka nol itu jujur: dari sisi tool, memang tidak ada yang diukur di sana.

Kebiasaan lama menutupi aturan ini. Selama test hidup di package yang sama, package yang diukur dan package yang diuji adalah objek yang sama, sehingga perilaku default terasa seperti mengukur semuanya. Pengukuran coverage tingkat package bukan fitur baru; mekanismenya ada sejak Go 1.2 [4]. Yang berubah pada kasus ini hanya letak testnya.

Ada juga perubahan tampilan yang membuat jebakan ini lebih mudah dikenali sekarang. Sebelum Go 1.22, package tanpa file test sendiri dilaporkan [no test files]. Sejak Go 1.22, fungsi pada package tersebut diperlakukan sebagai kode tak tercakup dan dilaporkan eksplisit: coverage: 0.0% of statements [3]. Dengan kata lain, kondisi ini bukan rusak, melainkan laporan yang benar atas ukuran yang memang kosong.

Demo kecil pada Go 1.27.1 menutup keraguan itu. Satu modul berisi satu package internal berisi dua fungsi dan satu package test eksternal yang mengujinya. Tanpa flag tambahan, hasilnya persis seperti kasus di atas; dengan satu flag, baris akhirnya berubah.

$ go test -count=1 -cover ./...
    demo.example/coverdemo/internal/greeter  coverage: 0.0% of statements
ok   demo.example/coverdemo/tests  0.003s  coverage: [no statements]
$ go test -count=1 -cover -coverpkg=./internal/... ./...
    demo.example/coverdemo/internal/greeter  coverage: 0.0% of statements
ok   demo.example/coverdemo/tests  0.003s  coverage: 75.0% of statements in ./internal/...

Perhatikan bahwa baris 0.0% pada package yang diuji tetap ada pada run kedua: baris itu mencerminkan tidak adanya test di dalam package itu sendiri. Angka agregat yang dicari justru hadir di baris ok, lengkap dengan akhiran yang menyebut cakupan ukurnya.

Satu Flag pada Mode Coverage

Perbaikannya bukan memindahkan test kembali, melainkan memberi tahu tool package mana yang harus dipasangi penghitung. Pada repo hasil migrasi, ini didaftarkan sebagai mode tersendiri di runner script, terpisah dari run test harian:

go test -count=1 -p 1 -tags=integration -cover \
  -coverpkg=./internal/... ./...

Angka hasil mode ini bisa dipercaya karena dua detail. Pertama, bentuk outputnya sudah dikunci oleh test regressi resmi di cmd/go sejak perbaikan issue #58770, yang mengakhiri laporan menyesatkan pada Go 1.19 dan 1.20 ketika semua package dipaksa ikut diimpor [5]. Akhiran in ./internal/... pada persentase itu bagian dari kontrak output, bukan tempelan. Kedua, flag -p 1 pada perintah yang sama melayani kebutuhan lain: seluruh test integration berbagi satu database test, sehingga test tidak boleh berjalan paralel. Keduanya independen dan boleh hadir bersama.

Untuk membaca hasilnya per fungsi, simpan profil lalu render:

go test -count=1 -tags=integration -p 1 -coverprofile=coverage.out \
  -coverpkg=./internal/... ./...
go tool cover -func=coverage.out

Baris total: pada keluaran terakhir adalah angka yang layak dilaporkan ke tim, bukan angka per-package yang terlihat saat run tanpa flag.

Eksekusi dan Pengukuran Dua Jalur Berbeda

CI tidak perlu disentuh: go test ./... menjalankan seluruh package test, pindah ke mana pun lokasi filenya. Test yang pindah tetap tereksekusi; yang tidak otomatis adalah pengukurannya. Itu sebabnya mode coverage dipisah sebagai mode tersendiri, bukan digabung ke run harian: run biasa bebas dari biaya kompilasi instrumentasi, dan biaya itu hanya dibayar saat angkanya benar-benar dibutuhkan.

Pola pemeriksaannya bisa digunakan ulang di repo mana pun yang baru memindahkan test keluar dari package. Saat suite hijau tetapi angkanya nol, jalankan dua perintah demo di atas pada modul terkecil yang mewakili. Bila satu flag mengubah 0.0% menjadi angka nyata, diagnosis selesai tanpa menyentuh satu baris pun kode produksi. Bila tidak berubah juga, barulah skenario lain dicari, misalnya build tag yang menyembunyikan test dari pola ./....

Sources

Artikel terkait