Belajar Haskell - Testing (HUnit/Hspec/QuickCheck) & Debug
Episode 10 of 23

Belajar Haskell - Testing (HUnit/Hspec/QuickCheck) & Debug

Menguji kode Haskell dengan HUnit untuk unit test, Hspec untuk spesifikasi gaya BDD, dan QuickCheck untuk property-based testing dengan forAll, menghitung coverage lewat hpc, serta strategi debugging memakai GHCi :t dan :i, Debug.Trace, dan :set +s untuk mengukur performa evaluasi.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah di episode 9 kalian membangun proyek cabal yang rapi, episode ini tentang satu hal yang membedakan "kode jalan" dari "kode benar": testing. Karena fungsi Haskell pure, testing di sini adalah pengalaman yang luar biasa menyenangkan — tanpa setup, tanpa mock, tanpa database palsu. Uji sebuah fungsi pure hanyalah "panggil fungsi, cek hasilnya".

Mengapa bab ini penting? Karena kekuatan sesungguhnya Haskell muncul justru di sini: kombinasi pure function + property-based testing memungkinkan kalian menemukan bug yang tidak akan pernah ditemukan oleh contoh-contoh tangan. Kita juga akan menyiapkan perangkat debugging yang akan menemani kalian di semua episode berikutnya.

HUnit: Unit Test Paling Dasar

HUnit adalah framework unit test ala JUnit untuk Haskell. Test dinyatakan sebagai assertion:

test/MathOpsTest.hs
import Test.HUnit
import MathOps (double)
 
tests :: Test
tests = TestList
    [ "double 2 adalah 4" ~: double 2 ~?= 4
    , "double 0 adalah 0" ~: double 0 ~?= 0
    ]
 
main :: IO ()
main = runTestTT tests >>= print

~: menamai test, ~?= memeriksa kesetaraan. Jalankan dengan:

Menjalankan HUnit
cabal run MathOpsTest

HUnit sederhana dan eksplisit, tapi untuk test suite yang besar, Hspec menawarkan keterbacaan yang lebih baik.

Hspec: Spesifikasi Gaya BDD

Hspec menulis test sebagai spesifikasi yang terbaca seperti kalimat — gaya BDD (Behavior-Driven Development). Contohnya:

test/MathOpsSpec.hs
import Test.Hspec
import MathOps (double)
 
main :: IO ()
main = hspec $ do
    describe "double" $ do
        it "menggandakan angka positif" $ do
            double 2 `shouldBe` 4
        it "menggandakan nol menjadi nol" $ do
            double 0 `shouldBe` 0

Perhatikan describe (grup), it (satu contoh perilaku), dan shouldBe (assertion). Output Hspec adalah daftar spesifikasi hijau/merah yang sangat informatif. Hspec adalah pilihan default untuk kebanyakan proyek Haskell modern.

QuickCheck: Property-Based Testing

Inilah senjata rahasia Haskell. Alih-alih menulis contoh per contoh, kalian menulis properti yang harus selalu benar, dan QuickCheck membangkitkan ribuan input acak untuk mengujinya:

Property-based test dengan QuickCheck
import Test.QuickCheck
 
-- properti: double (double x) selalu 4x
prop_double :: Int -> Bool
prop_double x = double (double x) == 4 * x
 
main :: IO ()
main = quickCheck prop_double

QuickCheck memanggil prop_double dengan ratusan Int acak. Jika ada satu pun yang gagal, ia melaporkan counterexample:

Output QuickCheck
+++ OK, passed 100 tests.

Kekuatan sebenarnya: properti menangkap kontrak fungsi, bukan satu contoh. Jika kalian punya properti seperti "parse lalu print kembali menghasilkan nilai asal", test otomatis menelusuri ruang input yang sangat luas.

forAll: Mengendalikan Generator

forAll membatasi generator untuk properti tertentu:

forAll dengan generator khusus
prop_rev :: [Int] -> Property
prop_rev xs = forAll (choose (0, 9)) $ \_ ->
    reverse (reverse xs) == xs

Untuk properti yang butuh input "berbentuk" (misalnya non-negatif), forAll dan generator custom sangat berguna — kita bedah lengkap di episode 19.

Tip

Pola yang paling produktif: Hspec untuk contoh yang mendokumentasikan perilaku, QuickCheck untuk kontrak yang melindungi refactor. Duo ini biasanya dipasang bersama — Hspec menjalankan QuickCheck di dalam test suite-nya lewat prop.

Coverage dengan hpc

hpc (Haskell Program Coverage) mengukur baris mana yang sudah dieksekusi test:

Coverage dengan hpc
cabal test --enable-coverage

Setelah test selesai, hpc menghasilkan laporan HTML di dist-newstyle/ yang menandai kode ter-covvered (hijau), belum (merah), dan mixes. Target sehat untuk kode penting: 80-100%. Episode 19 akan membahas coverage sebagai bagian dari workflow CI.

Debugging dengan GHCi

GHCi adalah debugger yang luar biasa meski tampak sederhana. Tiga perintah inti:

:t — Cek Tipe

:t - tanya tipe
ghci> :t map
map :: (a -> b) -> [a] -> [b]
ghci> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b

Ketika error tipe membingungkan, mulailah dengan :t pada ekspresi yang bermasalah — sering kali kesalahan terlihat jelas begitu tipe aktual muncul.

:i — Informasi Penuh

:i - informasi definisi
ghci> :i Monad
type Monad :: (* -> *) -> Constraint
class Applicative m => Monad m where
  (>>=) :: m a -> (a -> m b) -> m b
  return :: a -> m a
  ...

:i menampilkan definisi lengkap: method, instance yang ada, dan superclass. Ini cara terbaik memahami apa yang bisa dipakai tipe tertentu.

:set +s — Ukur Waktu dan Alokasi

:set +s - timing
ghci> :set +s
ghci> sum [1..1000000]
500000500000
it :: Int
(0.02 secs, 1,234,567 bytes)

:set +s menampilkan waktu dan alokasi memori tiap ekspresi — alat cepat untuk merasakan kompleksitas sebelum masuk ke profiler di episode 21.

Debug.Trace: Mencetak dari Fungsi Pure

Karena fungsi pure tidak boleh mencetak, kalian butuh backdoor sementara: Debug.Trace.

trace untuk debugging sementara
import Debug.Trace
 
doubleDebug :: Int -> Int
doubleDebug x = trace ("memproses " ++ show x) (x * 2)

trace mencetak pesan ke stderr saat thunk dievaluasi, lalu mengembalikan nilai normal. Perhatikan kata "saat dievaluasi": dengan laziness, trace bisa muncul dalam urutan yang mengejutkan atau tidak muncul sama sekali. Ini alat untuk debug sementara — jangan sampai tertinggal di kode produksi.

Warning

Debug.Trace mengalahkan purity: ia menyelipkan efek samping ke fungsi yang seharusnya murni. Gunakan hanya untuk sesi debugging, dan bersihkan sebelum commit. Untuk pemahaman evaluasi yang lebih dalam, episode 21 akan memakai heap profiling sebagai gantinya.

Alur Debug yang Disarankan

Saat menemukan bug, urutan langkah yang efisien:

  1. Tulis test yang mereproduksi bug (Hspec atau QuickCheck).
  2. Gunakan :t untuk memeriksa tipe ekspresi yang mencurigakan.
  3. Gunakan :i untuk melihat apakah instance/fungsi yang dibutuhkan tersedia.
  4. Jika masih buntu, sematkan trace sementara untuk melihat alur evaluasi.
  5. Setelah bug ditemukan dan diperbaiki, pertahankan testnya — ia sekarang menjadi regresi test.

Penutup

Inti yang harus dibawa pulang:

  • HUnit untuk unit test dasar, Hspec untuk spesifikasi BDD yang terbaca.
  • QuickCheck menguji properti dengan input acak; forAll mengendalikan generator.
  • hpc mengukur coverage; cabal test --enable-coverage.
  • Debugging GHCi: :t (tipe), :i (info), :set +s (timing/alokasi).
  • Debug.Trace adalah backdoor sementara untuk melihat evaluasi; jangan dibawa ke produksi.

Di episode 11 selanjutnya kita akan membahas monads: IO, Maybe, Either, State — memahami main :: IO () dan interaksi dunia luar, >>= dan do notation, monad Maybe untuk null-safety, Either untuk error yang terstruktur, dan Reader/Writer/State untuk konfigurasi dan state lokal. Sampai jumpa di episode 11!

Belajar Haskell - Testing (HUnit/Hspec/QuickCheck) & Debug | Belajar Haskell