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

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.
Skrip shell bisa diuji dengan pendekatan yang sama seperti kode lain:
Tiga lapis ini menangkap kelas bug yang berbeda, dan semuanya bisa dijalankan dalam hitungan detik.
Agar bisa diuji, pisahkan logika (function) dari efek samping (menjalankan perintah, menulis file). Contoh modul yang diuji:
#!/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 adalah framework test ringan untuk Zsh. Install lewat brew atau build manual:
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:
#!/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.zunitRunning 3 tests in calculator
✓ calculate_percentage mengembalikan persen yang benar
✓ calculate_percentage menolak total nol
✓ classify_status memetakan kode HTTPzunit menyediakan beberapa assertion dasar:
| Assertion | Arti |
|---|---|
assert $output = 'value' | string equality |
assert $state = 0 | status exit |
assert $output contains 'text' | substring |
assert $output matches 'regex' | regex |
refute $output = 'x' | negasi |
assert $output_empty | output kosong |
@test 'output mengandung substring' {
run echo "deploy selesai v1.2.3"
assert $output contains 'selesai'
assert $output matches 'v[0-9]+\.[0-9]+'
}Shellcheck mendeteksi bug kelas quoting dan injection (dari episode 17). Untuk skrip Zsh, konfigurasi shell perlu diperjelas:
shellcheck --shell=zsh --severity=warning lib/*.zshKode error yang umum pada skrip Zsh:
| Kode | Masalah |
|---|---|
SC2086 | Double quote untuk mencegah globbing/word splitting |
SC2046 | Quote command substitution |
SC1090 | source file non-konstan — perlu di-resolve manual |
SC2164 | cd tanpa guard |
SC1090 sering muncul saat source "$ZSH/oh-my-zsh.sh" — shellcheck tidak bisa melacak path dinamis. Matikan per baris jika memang disengaja:
# shellcheck disable=SC1090
source "$ZSH/oh-my-zsh.sh"zsh -n memvalidasi sintaks semua skrip tanpa mengeksekusi:
for f in **/*.zsh(N); do
zsh -n "$f" || exit 1
doneGabungkan dengan shellcheck dalam satu script CI:
#!/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"Workflow GitHub Actions contoh (repo ini memakai bun + GitHub Actions — sesuaikan dengan stack kalian):
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.zshTip
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.
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.--shell=zsh wajib agar tidak memakai aturan bash yang tidak berlaku.Inti yang harus dibawa pulang:
@test, run, dan assert $output/assert $state untuk unit test.--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!