Belajar Zsh - Testing Skrip (zunit & Shellcheck)
Series/Belajar Zsh/Episode 19
Episode 19 of 23

Belajar Zsh - Testing Skrip (zunit & Shellcheck)

Membawa skrip Zsh ke standar engineering: menulis unit test dengan zunit beserta assertion helpers, menjalankan shellcheck sebagai linter, memvalidasi sintaks dengan zsh -n, dan menyusun CI job untuk skrip shell agar setiap perubahan terverifikasi secara otomatis

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

Pendahuluan

Setelah di episode 18 kalian membangun toolkit remote yang lengkap, pada episode ini kita membahas sesuatu yang jarang dilakukan developer shell: testing. Skrip shell sering dianggap "cukup dijalankan saja" — padahal di production, skrip yang sama dijalankan ratusan kali oleh cron dan pipeline, dan sekali rusak bisa menghabiskan waktu berjam-jam debugging.

Zsh punya ekosistem testing yang solid: zunit untuk unit test dengan assertion helpers, shellcheck untuk lint, dan zsh -n untuk validasi sintaks. Episode ini membangun pipeline kualitas dari nol: test function-function inti, lalu otomatisasi di CI.

Mengapa Test Skrip Shell?

Skrip shell bisa diuji dengan pendekatan yang sama seperti kode lain:

  • Unit test — uji function secara terisolasi dengan input/output yang diketahui.
  • Lint — deteksi bug pola otomatis (quoting, injection).
  • Sintaks check — pastikan tidak ada syntax error sebelum eksekusi.
100%

Tiga lapis ini menangkap kelas bug yang berbeda, dan semuanya bisa dijalankan dalam hitungan detik.

Struktur Proyek yang Dapat Diuji

Agar bisa diuji, pisahkan logika (function) dari efek samping (menjalankan perintah, menulis file). Contoh modul yang diuji:

lib/calculator.zsh
#!/usr/bin/env zsh
 
calculate_percentage() {
    local part=$1 total=$2
    if (( total <= 0 )); then
        return 1
    fi
    print -l -- $(( part * 100.0 / total ))
}
 
classify_status() {
    local code=$1
    case "$code" in
        2*) print "success" ;;
        4*) print "client_error" ;;
        5*) print "server_error" ;;
        *)  print "unknown" ;;
    esac
}

Perhatikan: tidak ada I/O, tidak ada perintah eksternal — murni transformasi input ke output. Inilah yang membuatnya mudah diuji.

zunit: Unit Test untuk Zsh

zunit adalah framework test ringan untuk Zsh. Install lewat brew atau build manual:

Install zunit
brew install molovo/tap/zunit
# atau
zsh <(curl -s https://raw.githubusercontent.com/molovo/zunit/master/install.zsh)

Konvensi zunit: file test dinamai *.zunit, berisi @test blocks dengan assertions:

test/calculator.zunit
#!/usr/bin/env zunit
 
@setup {
    source ../lib/calculator.zsh
}
 
@test 'calculate_percentage mengembalikan persen yang benar' {
    run calculate_percentage 25 200
    assert $state = 0
    assert $output = 12.5
}
 
@test 'calculate_percentage menolak total nol' {
    run calculate_percentage 10 0
    assert $state = 1
}
 
@test 'classify_status memetakan kode HTTP' {
    assert $(classify_status 200) = 'success'
    assert $(classify_status 404) = 'client_error'
    assert $(classify_status 500) = 'server_error'
}
  • @setup dijalankan sebelum setiap test — tempat source modul.
  • run <command> menangkap output ke $output dan status ke $state.
  • assert <ekspresi> adalah assertion helper utama.

Menjalankan

Jalankan zunit
zunit
Output zunit
Running 3 tests in calculator
✓ calculate_percentage mengembalikan persen yang benar
✓ calculate_percentage menolak total nol
✓ classify_status memetakan kode HTTP

Assertion Helpers

zunit menyediakan beberapa assertion dasar:

AssertionArti
assert $output = 'value'string equality
assert $state = 0status exit
assert $output contains 'text'substring
assert $output matches 'regex'regex
refute $output = 'x'negasi
assert $output_emptyoutput kosong
Contoh assertion lain
@test 'output mengandung substring' {
    run echo "deploy selesai v1.2.3"
    assert $output contains 'selesai'
    assert $output matches 'v[0-9]+\.[0-9]+'
}

Shellcheck sebagai Linter

Shellcheck mendeteksi bug kelas quoting dan injection (dari episode 17). Untuk skrip Zsh, konfigurasi shell perlu diperjelas:

Shellcheck untuk zsh
shellcheck --shell=zsh --severity=warning lib/*.zsh

Kode error yang umum pada skrip Zsh:

KodeMasalah
SC2086Double quote untuk mencegah globbing/word splitting
SC2046Quote command substitution
SC1090source file non-konstan — perlu di-resolve manual
SC2164cd tanpa guard

SC1090 sering muncul saat source "$ZSH/oh-my-zsh.sh" — shellcheck tidak bisa melacak path dinamis. Matikan per baris jika memang disengaja:

Suppress SC1090
# shellcheck disable=SC1090
source "$ZSH/oh-my-zsh.sh"

zsh -n di CI

zsh -n memvalidasi sintaks semua skrip tanpa mengeksekusi:

Sintaks check semua skrip
for f in **/*.zsh(N); do
    zsh -n "$f" || exit 1
done

Gabungkan dengan shellcheck dalam satu script CI:

scripts/check.zsh
#!/usr/bin/env zsh
set -euo pipefail
 
print "==> Sintaks check"
for f in **/*.zsh(N); do
    zsh -n "$f"
done
 
print "==> Shellcheck"
shellcheck --shell=zsh **/*.zsh
 
print "==> Unit test"
zunit
 
print "Semua check lulus"

CI Job untuk Skrip Shell

Workflow GitHub Actions contoh (repo ini memakai bun + GitHub Actions — sesuaikan dengan stack kalian):

.github/workflows/shell.yml
name: shell
on: [push, pull_request]
 
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install tools
        run: |
          sudo apt-get update
          sudo apt-get install -y zsh shellcheck
          brew install molovo/tap/zunit
      - name: Lint & test
        run: ./scripts/check.zsh

Tip

Untuk CI container, pertimbangkan menjalankan zunit di dalam container resmi (misal zshbrew/zsh atau image Alpine + zsh) agar environment deterministik. Poinnya: jangan test skrip hanya di mesin lokal — CI menjamin konsistensi di setiap perubahan.

Common Pitfalls

  • Test mengeksekusi perintah eksternal — modul dengan rm/git sulit diuji; mock dengan function override di @setup.
  • source path relatif — gunakan source "${0:h}/../lib/..." berbasis path file, bukan cd.
  • assert vs run — panggil function lewat run untuk menangkap status; assertion langsung pada command substitution tidak menangkap $state.
  • Shellcheck pada skrip Zsh spesifik — flag --shell=zsh wajib agar tidak memakai aturan bash yang tidak berlaku.

Penutup

Inti yang harus dibawa pulang:

  • Pisahkan logika (function murni) dari efek samping agar mudah diuji.
  • zunit: @test, run, dan assert $output/assert $state untuk unit test.
  • Shellcheck dengan --shell=zsh menangkap bug quoting/injection.
  • zsh -n memvalidasi sintaks; gabungkan semua di CI job.

Di episode 20 selanjutnya kita akan membahas Zsh 5.9.2 & fitur terbaru — apa saja yang berubah di rilis 5.9 (Mei 2022), 5.9.1 (Mei 2026), dan 5.9.2 (Juli 2026), filosofi development yang konservatif demi stabilitas, serta cara mengakses fitur baru via plugin dan modul. Sampai jumpa di episode 20!

Belajar Zsh - Testing Skrip (zunit & Shellcheck) | Belajar Zsh