Fixture, Environment Sementara, dan Cakupan
Bangun fixture pengujian yang terisolasi dan ukur cabang skrip mana yang benar-benar dijalankan oleh pengujian Anda.
Fixture, Environment Sementara, dan Cakupan adalah pelajaran DevOps Bootcamp gratis di CoddyKit. Ini adalah pelajaran 3 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar DevOps Bootcamp, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus DevOps Bootcamp mencakup 4 pelajaran total.
Mengapa Fixture dan Isolasi Itu Penting
Saat menguji skrip Bash, risiko terbesar adalah efek samping: pengujian Anda tanpa sengaja mengubah file nyata, basis data nyata, atau keadaan sistem nyata. Pengujian yang berhasil di komputer Anda tetapi merusak data produksi lebih buruk daripada tidak memiliki pengujian sama sekali.
Solusinya adalah fixture pengujian—lingkungan terkendali dan sementara yang mencerminkan kondisi nyata tanpa menyentuh apa pun yang sebenarnya. Fixture yang baik memberi Anda:
- Dapat direproduksi—pengujian menghasilkan hasil yang sama setiap kali dijalankan
- Isolasi—pengujian tidak saling mengganggu atau mengganggu host
- Keamanan—operasi destruktif hanya menyentuh data yang boleh dibuang
- Kecepatan—tanpa pemanggilan jaringan atau I/O berat kecuali benar-benar diperlukan
Dalam pengujian Bash, fixture biasanya berupa direktori sementara yang diisi file-file yang telah ditentukan, berkas eksekusi tiruan yang ditempatkan di awal $PATH, serta variabel lingkungan yang cakupannya dibatasi pada proses pengujian.
Membuat dan Membersihkan Direktori Sementara
Pola standar untuk direktori sementara per pengujian menggunakan mktemp -d, yang membuat direktori unik di /tmp dan mencetak jalurnya. Anda menyimpan jalur tersebut dan mendaftarkan trap untuk menghapusnya secara otomatis saat shell keluar—bahkan ketika terjadi kegagalan.
Idiom dua baris ini seharusnya ada di setiap file pengujian yang menyentuh sistem berkas:
#!/usr/bin/env bash
set -euo pipefail
# Create an isolated temp directory
TMPDIR=$(mktemp -d)
# Always clean up, even if the script exits early or errors out
trap 'rm -rf "$TMPDIR"' EXIT
echo "Working in: $TMPDIR"
# Simulate creating fixture files
mkdir -p "$TMPDIR/project/{src,tests,logs}"
echo 'version=1.2.3' > "$TMPDIR/project/.env"
echo 'Hello fixture' > "$TMPDIR/project/src/main.sh"
ls -R "$TMPDIR/project"
echo 'Temp dir will be removed automatically on exit'Menyusun Struktur Pohon Direktori Fixture
Fixture yang tersusun baik mencerminkan tata letak direktori yang benar-benar diharapkan oleh skrip yang diuji. Anggaplah fixture sebagai akar proyek tiruan berukuran kecil. Fungsi penyiapan fixture membuat tata letak ini sebelum setiap pengujian, dan pembersihan menghapusnya.
Praktik utama:
- Gunakan fungsi
setup()yang dipanggil oleh kerangka kerja pengujian sebelum setiap pengujian - Gunakan
teardown()ataucleanup()yang berjalan setelah setiap pengujian, bahkan saat terjadi kegagalan - Jaga agar file fixture tetap minimal—hanya yang benar-benar dibaca oleh skrip
- Beri nama file fixture secara deskriptif agar kegagalan mudah didiagnosis
#!/usr/bin/env bash
# fixture_helpers.bash — source this from your test files
FIXTURE_ROOT=''
setup_fixture() {
FIXTURE_ROOT=$(mktemp -d)
# Build the directory tree the deploy script expects
mkdir -p "$FIXTURE_ROOT"/{dist,config,logs}
echo '{"version":"2.0"}' > "$FIXTURE_ROOT/config/app.json"
echo 'console.log("app")' > "$FIXTURE_ROOT/dist/index.js"
touch "$FIXTURE_ROOT/logs/.gitkeep"
export FIXTURE_ROOT
echo "[setup] Fixture ready at $FIXTURE_ROOT"
}
teardown_fixture() {
if [[ -n "$FIXTURE_ROOT" && -d "$FIXTURE_ROOT" ]]; then
rm -rf "$FIXTURE_ROOT"
echo '[teardown] Fixture removed'
fi
}
# Self-test
setup_fixture
ls "$FIXTURE_ROOT"
teardown_fixtureMeniru Berkas Eksekusi dengan PATH Tiruan
Banyak skrip Bash memanggil alat eksternal seperti curl, aws, docker, atau git. Dalam pengujian, Anda tidak ingin mengakses layanan nyata, jadi Anda mengganti alat tersebut dengan berkas eksekusi tiruan.
Tekniknya sederhana:
- Buat direktori sementara
bin/di dalam fixture - Tulis skrip shell kecil di sana dengan nama yang sama seperti alat nyata
- Tambahkan direktori tersebut di awal
$PATHsebelum memanggil skrip yang diuji
Karena $PATH dicari dari kiri ke kanan, berkas tiruan akan dipilih. Berkas biner nyata tidak pernah dipanggil.
#!/usr/bin/env bash
set -euo pipefail
FIXTURE=$(mktemp -d)
trap 'rm -rf "$FIXTURE"' EXIT
# Create a fake 'curl' that records calls and returns canned data
FAKE_BIN="$FIXTURE/bin"
mkdir -p "$FAKE_BIN"
cat > "$FAKE_BIN/curl" << 'SCRIPT'
#!/usr/bin/env bash
# Log every argument for later inspection
echo "curl $*" >> "$FIXTURE_BIN_LOG"
# Return a canned HTTP 200 response body
echo '{"status":"ok","id":42}'
SCRIPT
chmod +x "$FAKE_BIN/curl"
# Export the log path so the fake can find it
export FIXTURE_BIN_LOG="$FIXTURE/curl_calls.log"
# Prepend fake bin directory to PATH
export PATH="$FAKE_BIN:$PATH"
# Now any call to 'curl' hits our fake
curl -s https://api.example.com/health
curl -X POST https://api.example.com/deploy
echo '--- Recorded curl calls ---'
cat "$FIXTURE_BIN_LOG"Menangkap dan Memastikan Keluaran Perintah
Fixture hanya berguna jika Anda dapat memastikan tindakan skrip Anda. Pola standarnya adalah:
- Tangkap stdout/stderr ke dalam variabel dengan
$()atau substitusi proses - Periksa file log yang ditulis oleh berkas biner tiruan
- Periksa kode keluar secara eksplisit dengan
$?atau logika kondisional - Pastikan file tertentu dibuat, diubah, atau dibiarkan tanpa perubahan
Menulis pembantu pemeriksaan yang kecil dan terfokus membuat pengujian Anda mudah dibaca serta memberikan pesan kegagalan yang tepat saat terjadi kesalahan.
#!/usr/bin/env bash
set -euo pipefail
# Minimal assertion helpers
assert_eq() {
local desc="$1" expected="$2" actual="$3"
if [[ "$expected" == "$actual" ]]; then
echo "PASS: $desc"
else
echo "FAIL: $desc"
echo " expected: $expected"
echo " actual: $actual"
return 1
fi
}
assert_file_exists() {
local desc="$1" file="$2"
if [[ -f "$file" ]]; then
echo "PASS: $desc"
else
echo "FAIL: $desc — file not found: $file"
return 1
fi
}
assert_contains() {
local desc="$1" needle="$2" haystack="$3"
if [[ "$haystack" == *"$needle"* ]]; then
echo "PASS: $desc"
else
echo "FAIL: $desc — '$needle' not found in output"
return 1
fi
}
# Demo usage
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
echo 'hello world' > "$TMPDIR/greeting.txt"
OUT=$(cat "$TMPDIR/greeting.txt")
assert_eq 'file content matches' 'hello world' "$OUT"
assert_file_exists 'greeting file created' "$TMPDIR/greeting.txt"
assert_contains 'output has hello' 'hello' "$OUT"Membatasi Cakupan Variabel Lingkungan dalam Pengujian
Skrip sering membaca variabel lingkungan seperti $HOME, $CONFIG_PATH, atau $DATABASE_URL. Dalam pengujian, Anda harus mengganti nilainya tanpa mencemari lingkungan nyata.
Pendekatan yang paling aman adalah menjalankan skrip yang diuji dalam subshell yang hanya berisi variabel yang Anda tetapkan secara eksplisit. Perintah env memungkinkan Anda menghapus seluruh lingkungan lalu menambahkan kembali hanya yang diperlukan:
env -i VAR=val ./script.sh—lingkungan yang sepenuhnya bersih(export VAR=val; ./script.sh)—subshell mewarisi lingkungan proses induk dan menambahkan penggantian Anda
Menggunakan subshell juga berarti jika skrip mengubah $IFS, $PWD, atau keadaan global lainnya, perubahan tersebut tidak pernah keluar kembali ke pelaksana pengujian.
#!/usr/bin/env bash
set -euo pipefail
FIXTURE=$(mktemp -d)
trap 'rm -rf "$FIXTURE"' EXIT
# Write a tiny script under test that reads env vars
cat > "$FIXTURE/deploy.sh" << 'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
ENV_NAME=${DEPLOY_ENV:-unknown}
CFG=${CONFIG_DIR:-/etc/app}
echo "Deploying to $ENV_NAME using config from $CFG"
SCRIPT
chmod +x "$FIXTURE/deploy.sh"
echo '--- Test 1: staging env ---'
# Run in a clean subshell with explicit vars
(
export DEPLOY_ENV=staging
export CONFIG_DIR="$FIXTURE/config"
"$FIXTURE/deploy.sh"
)
echo '--- Test 2: production env ---'
(
export DEPLOY_ENV=production
export CONFIG_DIR=/etc/prod-config
"$FIXTURE/deploy.sh"
)
echo '--- Test 3: defaults ---'
# No env vars set — script should use its own defaults
env -i PATH="$PATH" "$FIXTURE/deploy.sh"Pengenalan kcov untuk Cakupan Bash
Cakupan menjawab pertanyaan: baris (dan cabang) mana dari skrip saya yang benar-benar dijalankan oleh pengujian? Angka cakupan yang tinggi tidak menjamin kebenaran, tetapi angka yang rendah menunjukkan jalur yang belum diuji dan kemungkinan menyimpan bug.
Alat utama untuk cakupan Bash adalah kcov. Alat ini bekerja dengan menginstrumentasikan skrip pada tingkat OS menggunakan PTRACE (Linux) atau dtrace (macOS), sehingga tidak memerlukan perubahan pada kode sumber Anda. Alat ini menghasilkan laporan HTML yang menampilkan baris merah (belum tercakup) dan hijau (tercakup).
Penggunaan dasar:
kcov --include-path=./src coverage-out/ ./src/myscript.sh- Buka
coverage-out/index.htmldi peramban untuk memeriksa hasil - Dalam CI, uraikan
coverage-out/myscript.sh/coverage.jsonuntuk mendapatkan persentase yang dapat dibaca mesin
Catatan: kcov harus dipasang secara terpisah (brew install kcov di macOS, apt install kcov di Ubuntu 20.04+).
Menjalankan kcov pada Skrip Nyata
Berikut contoh menyeluruh yang menunjukkan skrip yang siap diterapkan, pengujian yang menjalankannya, serta pemanggilan kcov untuk mengukur cakupan. Perhatikan bahwa direktori keluaran dibuat untuk setiap pengujian, sehingga hasil dari beberapa pelaksanaan pengujian dapat digabungkan nanti.
#!/usr/bin/env bash
# This demo shows the *structure* of a kcov workflow.
# It will not run kcov itself (not guaranteed to be installed),
# but the script under test and test runner are fully runnable.
set -euo pipefail
FIXTURE=$(mktemp -d)
trap 'rm -rf "$FIXTURE"' EXIT
# 1. Script under test
cat > "$FIXTURE/process.sh" << 'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
INPUT="$1"
if [[ ! -f "$INPUT" ]]; then
echo "ERROR: file not found" >&2
exit 1
fi
LINE_COUNT=$(wc -l < "$INPUT")
if (( LINE_COUNT == 0 )); then
echo "WARNING: file is empty"
else
echo "Processed $LINE_COUNT lines"
fi
SCRIPT
chmod +x "$FIXTURE/process.sh"
# 2. Happy path test (exercises line 6 and 11)
echo -e 'one\ntwo\nthree' > "$FIXTURE/data.txt"
OUT=$("$FIXTURE/process.sh" "$FIXTURE/data.txt")
echo "Happy path: $OUT"
# 3. Empty file test (exercises line 9)
touch "$FIXTURE/empty.txt"
OUT=$("$FIXTURE/process.sh" "$FIXTURE/empty.txt")
echo "Empty file: $OUT"
# 4. Missing file test (exercises line 5-6)
if ! OUT=$("$FIXTURE/process.sh" "$FIXTURE/missing.txt" 2>&1); then
echo "Missing file (expected error): $OUT"
fi
# To measure coverage, wrap each call with kcov:
# kcov --include-path="$FIXTURE" "$FIXTURE/cov/happy" "$FIXTURE/process.sh" ...
# kcov --merge "$FIXTURE/cov/all" "$FIXTURE/cov/happy" "$FIXTURE/cov/empty"Menggabungkan Cakupan dari Beberapa Pelaksanaan Pengujian
Satu pengujian jarang mencakup semua cabang. Anda menjalankan beberapa pengujian—masing-masing di direktori keluaran cakupannya sendiri—lalu menggabungkannya. Flag --merge milik kcov menggabungkan beberapa pelaksanaan menjadi satu laporan terpadu.
Pola umum dalam pipeline CI:
- Jalankan pengujian A → keluaran ke
cov/test_a/ - Jalankan pengujian B → keluaran ke
cov/test_b/ - Gabungkan →
kcov --merge cov/all/ cov/test_a/ cov/test_b/ - Uraikan
cov/all/<script>/coverage.jsonuntuk mendapatkan persentase akhir
Anda juga dapat menetapkan ambang minimum dan menggagalkan build CI jika cakupan turun di bawahnya:
#!/usr/bin/env bash
# extract_coverage.sh — parse kcov JSON and fail below threshold
set -euo pipefail
MIN_COVERAGE=80 # percent
COV_JSON="${1:-coverage-out/myscript.sh/coverage.json}"
if [[ ! -f "$COV_JSON" ]]; then
echo "ERROR: coverage JSON not found at $COV_JSON" >&2
exit 1
fi
# kcov JSON contains a key like: "percent_covered": "87.50"
PERCENT=$(grep -oP '"percent_covered":\s*"\K[0-9.]+' "$COV_JSON")
PERCENT_INT=${PERCENT%%.*} # truncate decimal
echo "Coverage: ${PERCENT}% (minimum: ${MIN_COVERAGE}%)"
if (( PERCENT_INT < MIN_COVERAGE )); then
echo "FAIL: coverage ${PERCENT}% is below threshold ${MIN_COVERAGE}%" >&2
exit 1
fi
echo "PASS: coverage threshold met"Cakupan Cabang vs Cakupan Baris
Ada dua metrik cakupan utama yang akan Anda temui:
- Cakupan baris—apakah baris ini dijalankan sama sekali? Metrik ini mudah dimanipulasi: satu pengujian dapat menyentuh banyak baris, tetapi melewatkan jalur kondisional yang penting.
- Cakupan cabang—apakah setiap cabang dari setiap
if,case, dan&&/||diambil? Sinyalnya jauh lebih kuat. Diperlukan pengujian untuk sisi benar dan salah dari setiap keputusan.
kcov melaporkan keduanya. Wawasan utamanya: cakupan baris 100% tidak berarti cakupan cabang 100%. Perhatikan skrip ini—satu pengujian dengan file yang tidak kosong akan mencakup setiap baris, tetapi cabang file kosong (baris 9 di bawah) tidak pernah tercapai:
#!/usr/bin/env bash
# Illustrates line vs branch coverage gap
set -euo pipefail
check_file() {
local f="$1"
if [[ -f "$f" ]]; then # branch A (true) OR branch B (false)
local lines
lines=$(wc -l < "$f")
if (( lines > 0 )); then # branch C (true) OR branch D (false)
echo "File has $lines lines"
else
echo "File is empty" # branch D — unreached if only tested with non-empty file
fi
else
echo "File missing" # branch B — unreached if only tested with existing file
fi
}
# Only one test: covers lines 5-10 (4 of 6 branches)
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
echo 'data' > "$TMPDIR/sample.txt"
check_file "$TMPDIR/sample.txt"
# To reach 100% branch coverage you also need:
# check_file "/nonexistent/path"
# check_file "$TMPDIR/empty.txt" (after: touch "$TMPDIR/empty.txt")Mengintegrasikan Fixture dan Cakupan dalam CI
Menyatukan semuanya: pipeline CI yang tangguh untuk proyek Bash menggabungkan penyiapan fixture, pelaksanaan pengujian dengan kcov, penggabungan, dan pemeriksaan ambang batas dalam satu skrip. Skrip ini menjadi titik masuk CI—satu perintah untuk menjalankan semuanya.
Prinsip desain untuk pelaksana pengujian CI:
- Setiap kasus pengujian memanggil
setup_fixturedan mendaftarkanteardown_fixturemelaluitrap - Skrip yang diuji dipanggil dengan
$PATHpalsu dan variabel lingkungan dengan cakupan terbatas - kcov membungkus setiap pemanggilan dan menulis hasilnya ke subdirektori bernomor
- Setelah semua pengujian selesai, kcov menggabungkan hasilnya dan skrip ambang batas menentukan apakah build boleh dilanjutkan
- Pelaksana CI keluar dengan kode selain nol jika ada pengujian atau pemeriksaan cakupan yang gagal
#!/usr/bin/env bash
# ci_runner.sh — full fixture + coverage pipeline entry point
set -euo pipefail
SRC="./src/deploy.sh"
COV_ROOT="$(mktemp -d)/coverage"
trap 'rm -rf "$COV_ROOT"' EXIT
mkdir -p "$COV_ROOT"
PASS=0
FAIL=0
RUN_NUM=0
run_test() {
local name="$1" test_fn="$2"
local fixture
fixture=$(mktemp -d)
local cov_out="$COV_ROOT/run_$((++RUN_NUM))"
if (
trap 'rm -rf "$fixture"' EXIT
export FIXTURE="$fixture"
# Fake bin directory shadowing real tools
mkdir -p "$fixture/bin"
export PATH="$fixture/bin:$PATH"
"$test_fn" "$fixture"
); then
echo "PASS: $name"
(( PASS++ )) || true
else
echo "FAIL: $name"
(( FAIL++ )) || true
fi
}
# Example test function
test_happy_path() {
local fx="$1"
mkdir -p "$fx/dist"
echo 'app.js' > "$fx/dist/index.js"
# Would normally run: kcov "$cov_out" "$SRC" --env=staging "$fx"
echo "[test] happy path executed in $fx"
}
run_test 'happy_path' test_happy_path
echo "Results: $PASS passed, $FAIL failed"
(( FAIL == 0 ))Pemeriksaan Pengetahuan: Teknik PATH Palsu
Uji pemahaman Anda tentang teknik PATH palsu yang digunakan dalam fixture pengujian Bash.
Ringkasan: Fixture, Lingkungan Sementara, dan Cakupan
Pelajaran ini membahas seluruh perangkat untuk pengujian Bash yang tepercaya dan terisolasi:
- Direktori sementara—
mktemp -dbersamatrap ... EXITmenjamin pembersihan otomatis, apa pun cara pengujian berakhir. - Struktur fixture—pasangan
setup_fixture/teardown_fixturemengisi dan menghapus struktur direktori minimal yang mencerminkan input skrip nyata. - PATH palsu—tempatkan program pelaksana tiruan di
$FIXTURE/bin/dan tambahkan di awal$PATHuntuk mencegat pemanggilan kecurl,aws,docker, atau alat eksternal apa pun tanpa menyentuh biner sistem. - Pembatasan cakupan lingkungan—jalankan skrip yang diuji dalam subshell (
()atauenv -i) agar variabel yang diubah tidak pernah merembes kembali ke pelaksana pengujian. - Asersi—fungsi pembantu kecil (
assert_eq,assert_file_exists,assert_contains) menghasilkan keluaran lulus/gagal yang jelas dan pesan kesalahan yang bermakna. - Cakupan kcov—membungkus eksekusi skrip tanpa perubahan pada sumber dan menghasilkan laporan HTML serta JSON untuk cakupan baris dan percabangan.
- Penggabungan dan ambang batas—gabungkan beberapa proses kcov dengan
--merge, uraikan JSON, lalu gagalkan CI jika cakupan turun di bawah batas minimum Anda. - Cakupan percabangan dibandingkan cakupan baris—selalu targetkan cakupan percabangan; cakupan baris saja dapat melewatkan seluruh jalur kondisional dan memberikan rasa percaya diri yang keliru.
Dengan teknik ini, pengujian Bash Anda menjadi sama ketatnya dengan pengujian untuk bahasa terkompilasi apa pun.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Fixture, Environment Sementara, dan Cakupan” gratis?
Ya — teks lengkap “Fixture, Environment Sementara, dan Cakupan” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus DevOps Bootcamp, upgrade ke CoddyKit PRO. Kursus DevOps Bootcamp mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Fixture, Environment Sementara, dan Cakupan”?
Bangun fixture pengujian yang terisolasi dan ukur cabang skrip mana yang benar-benar dijalankan oleh pengujian Anda. Kamu berlatih DevOps Bootcamp dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai DevOps Bootcamp?
Tidak diperlukan pengalaman sebelumnya. DevOps Bootcamp di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 3 dari 4.
Berapa lama pelajaran “Fixture, Environment Sementara, dan Cakupan” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran DevOps Bootcamp ini?
Ya. Setiap pelajaran DevOps Bootcamp menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Menguji Fungsi Unit dengan Bats-core
- Membuat Mock Perintah dan Stub Alat Eksternal
- Fixture, Environment Sementara, dan Cakupan
- Menjalankan Pengujian Shell dalam Pipeline CI