Testfiksturer, midlertidige miljøer og dekningsgrad
Bygg isolerte testfiksturer og mål hvilke grener i skriptene testene faktisk dekker.
Testfiksturer, midlertidige miljøer og dekningsgrad er en gratis leksjon i Mestring av Linux-kommandolinjen og Bash-skripting på CoddyKit. Dette er leksjon 3 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Mestring av Linux-kommandolinjen og Bash-skripting, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Mestring av Linux-kommandolinjen og Bash-skripting inneholder totalt 4 leksjoner.
Hvorfor test-fixtures og isolasjon er viktige
Ved testing av Bash-skript er den største risikoen bivirkninger: Testene endrer ved et uhell ekte filer, ekte databaser eller den faktiske systemtilstanden. En test som består på maskinen Deres, men ødelegger produksjonsdata, er verre enn ingen test i det hele tatt.
Løsningen er test-fixtures – kontrollerte, kortvarige miljøer som gjenspeiler virkelige forhold uten å berøre noe ekte. Gode fixtures gir Dem:
- Reproduserbarhet – testene gir samme resultat hver gang
- Isolasjon – testene forstyrrer ikke hverandre eller vertsmaskinen
- Sikkerhet – destruktive operasjoner berører bare data som kan kastes
- Hastighet – ingen nettverkskall og ingen omfattende I/O med mindre det er helt nødvendig
Ved Bash-testing er fixtures vanligvis midlertidige kataloger fylt med kjente filer, falske kjørbare filer som plasseres tidlig i $PATH, og miljøvariabler som er begrenset til testprosessen.
Opprette og rydde opp i midlertidige kataloger
Det standardiserte mønsteret for en midlertidig katalog per test bruker mktemp -d, som oppretter en unik katalog i /tmp og skriver ut banen til den. De lagrer banen og registrerer en trap for å fjerne katalogen automatisk når skallet avsluttes – også ved feil.
Dette tolinjers idiomet bør finnes i alle testfiler som berører filsystemet:
#!/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'Strukturere et katalogtre for fixtures
En godt strukturert fixture gjenspeiler katalogoppsettet som skriptet som testes, faktisk forventer. Tenk på den som en miniatyrisert falsk prosjektrot. Fixture-oppsettfunksjonen oppretter dette oppsettet før hver test, og teardown fjerner det.
Viktig praksis:
- Bruk en
setup()-funksjon som testrammeverket kaller før hver test - Bruk en
teardown()ellercleanup()som kjøres etter hver test, også ved feil - Hold fixture-filene minimale – bare det skriptet faktisk leser
- Gi fixture-filene beskrivende navn, slik at feil blir enkle å diagnostisere
#!/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_fixtureMocke kjørbare filer med en falsk PATH
Mange Bash-skript kaller eksterne verktøy som curl, aws, docker eller git. I tester ønsker De ikke å kontakte ekte tjenester, så De erstatter verktøyene med falske kjørbare filer.
Teknikken er enkel:
- Opprett en midlertidig
bin/-katalog i fixturen - Skriv små shell-skript der med samme navn som de ekte verktøyene
- Legg katalogen først i
$PATHfør De kaller skriptet som testes
Ettersom $PATH søkes gjennom fra venstre mot høyre, vinner den falske filen. Den ekte binærfilen blir aldri kalt.
#!/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"Fange opp og kontrollere kommandoutdata
En fixture er bare nyttig hvis De kan kontrollere hva skriptet gjorde. Standardmønstrene er:
- Fang stdout/stderr i variabler med
$()eller prosessubstitusjon - Undersøk loggfiler som er skrevet av falske binærfiler
- Kontroller avslutningskoder eksplisitt med
$?eller betinget logikk - Bekreft at bestemte filer ble opprettet, endret eller ikke berørt
Hvis De skriver små, fokuserte hjelpefunksjoner for kontroll, blir testene lettere å lese, og De får presise feilmeldinger når noe går galt.
#!/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"Omfang for miljøvariabler i tester
Skript leser ofte fra miljøvariabler som $HOME, $CONFIG_PATH eller $DATABASE_URL. I tester må De overstyre disse uten å forurense det virkelige miljøet.
Den tryggeste tilnærmingen er å kjøre skriptet som testes, i et underskall med bare variablene De angir eksplisitt. Kommandoen env lar Dem redusere miljøet og legge tilbake bare det De trenger:
env -i VAR=val ./script.sh– helt rent miljø(export VAR=val; ./script.sh)– underskallet arver det overordnede miljøet og får i tillegg overstyringene Deres
Bruk av underskall innebærer også at hvis skriptet endrer $IFS, $PWD eller annen global tilstand, slipper disse endringene aldri tilbake til testkjøringen.
#!/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"Introduksjon til kcov for Bash-dekning
Dekningsgrad svarer på spørsmålet: Hvilke linjer (og grener) i skriptet ble faktisk kjørt av testene? En høy dekningsgrad garanterer ikke at koden er riktig, men en lav dekningsgrad avdekker uprøvde kjørebaner som sannsynligvis kan inneholde feil.
Hovedverktøyet for Bash-dekning er kcov. Det fungerer ved å instrumentere skriptet på operativsystemnivå ved hjelp av PTRACE (Linux) eller dtrace (macOS), og krever derfor ingen endringer i kildekoden. Det produserer en HTML-rapport som viser røde linjer (ikke dekket) og grønne linjer (dekket).
Grunnleggende bruk:
kcov --include-path=./src coverage-out/ ./src/myscript.sh- Åpne
coverage-out/index.htmli en nettleser for å undersøke resultatene - Parse
coverage-out/myscript.sh/coverage.jsoni CI for å få en maskinlesbar prosentverdi
Merk: kcov må installeres separat (brew install kcov på macOS, apt install kcov på Ubuntu 20.04+).
Kjøre kcov mot et ekte skript
Her er et komplett eksempel som viser et distribuerbart skript, en test som kjører det, og kcov-kallet som måler dekningen. Legg merke til at utdatakatalogen er separat for hver test, slik at De kan slå sammen resultater fra flere testkjøringer senere.
#!/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"Slå sammen dekning fra flere testkjøringer
En enkelt test dekker sjelden alle grener. De kjører flere tester – hver i sin egen katalog for dekningsutdata – og slår dem deretter sammen. kcovs --merge-flagg kombinerer flere kjøringer til én samlet rapport.
Det typiske mønsteret i en CI-pipeline:
- Kjør test A → skriv utdata til
cov/test_a/ - Kjør test B → skriv utdata til
cov/test_b/ - Slå sammen →
kcov --merge cov/all/ cov/test_a/ cov/test_b/ - Parse
cov/all/<script>/coverage.jsonfor å hente den endelige prosentverdien
De kan også håndheve en minimumsgrense og la CI-byggingen mislykkes hvis dekningen faller under den:
#!/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"Gren-dekning kontra linjedekning
Det finnes to hovedmål for dekningsgrad som De vil møte:
- Linjedekning – ble denne linjen kjørt i det hele tatt? Den er enkel å manipulere: Én enkelt test kan berøre mange linjer uten å dekke viktige betingede kjørebaner.
- Gren-dekning – ble hver gren i hver
if,caseog&&/||fulgt? Dette gir et langt sterkere signal. Det krever tester for både den sanne og den usanne siden av hver avgjørelse.
kcov rapporterer begge deler. Den viktige innsikten er: 100 % linjedekning innebærer ikke 100 % gren-dekning. Se på dette skriptet – én enkelt test med en fil som ikke er tom, vil dekke hver linje, men grenen for tom fil (linje 9 nedenfor) blir aldri nådd:
#!/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")Integrering av fixtures og dekning i CI
La oss sette alt sammen: En robust CI-pipeline for Bash-prosjekter kombinerer oppsett av fixtures, testkjøring med kcov, sammenslåing og en terskelsjekk i ett enkelt skript. Dette skriptet blir inngangspunktet for CI – én kommando for å kjøre alt.
Designprinsipper for CI-testkjøreren:
- Hver test case kaller
setup_fixtureog registrererteardown_fixtureviatrap - Skriptet som testes, startes med en falsk
$PATHog avgrensede miljøvariabler - kcov pakker inn hver kjøring og skriver resultatet til en nummerert undermappe
- Etter alle testene slår kcov sammen resultatene, og terskelskriptet avgjør om byggingen får passere
- CI-kjøreren avsluttes med en status ulik null hvis en test eller dekning-sjekken feiler
#!/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 ))Kunnskapssjekk: Teknikk med falsk PATH
Test forståelsen Deres av teknikken med falsk PATH som brukes i Bash-test-fixtures.
Oppsummering: Fixtures, midlertidige miljøer og dekning
Denne leksjonen dekket hele verktøykassen for pålitelig og isolert testing av Bash:
- Midlertidige mapper –
mktemp -dsammen med entrap ... EXITgaranterer automatisk opprydding, uansett hvordan testen avsluttes. - Fixture-struktur – Et par med
setup_fixtureogteardown_fixtureoppretter og sletter et minimalt mappetre som gjenspeiler virkelige inndata til skriptet. - Falsk PATH – Plasser stub-kjørbare filer i
$FIXTURE/bin/, og sett denne mappen foran$PATHfor å fange opp kall tilcurl,aws,dockereller andre eksterne verktøy uten å berøre systemets binærfiler. - Avgrensning av miljø – Kjør skriptet som testes i et subshell (
()ellerenv -i) slik at endrede variabler aldri lekker tilbake til testkjøreren. - Påstander – Små hjelpefunksjoner (
assert_eq,assert_file_exists,assert_contains) gir tydelig bestått/ikke bestått-utdata og meningsfulle feilmeldinger. - kcov-dekning – Pakker inn kjøringen av skriptet uten endringer i kildekoden og produserer HTML- og JSON-rapporter for linje- og grendekning.
- Sammenslåing og terskel – Kombiner flere kcov-kjøringer med
--merge, analyser JSON-filen og la CI feile hvis dekningen faller under minimumsgrensen. - Grendekning kontra linjedekning – Sikt alltid mot grendekning. Linjedekning alene kan overse hele betingede kjørebaner og gi falsk trygghet.
Med disse teknikkene blir Bash-testene Deres like grundige som tester for ethvert kompilert språk.
Lær deg Bash med en AI-veileder – gratis
Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.
- Kurs
- 22
- Leksjoner
- 88
Ofte stilte spørsmål
Er leksjonen «Testfiksturer, midlertidige miljøer og dekningsgrad» gratis?
Ja – hele teksten i «Testfiksturer, midlertidige miljøer og dekningsgrad» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av Mestring av Linux-kommandolinjen og Bash-skripting-kurset, kan du oppgradere til CoddyKit PRO. Kurset i Mestring av Linux-kommandolinjen og Bash-skripting inneholder totalt 4 leksjoner.
Hva lærer jeg i «Testfiksturer, midlertidige miljøer og dekningsgrad»?
Bygg isolerte testfiksturer og mål hvilke grener i skriptene testene faktisk dekker. Du øver på Mestring av Linux-kommandolinjen og Bash-skripting med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.
Trenger jeg erfaring for å begynne med Mestring av Linux-kommandolinjen og Bash-skripting?
Ingen tidligere erfaring er nødvendig. Mestring av Linux-kommandolinjen og Bash-skripting på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 3 av 4.
Hvor lang tid tar leksjonen «Testfiksturer, midlertidige miljøer og dekningsgrad»?
De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.
Kan jeg skrive og kjøre kode i denne Mestring av Linux-kommandolinjen og Bash-skripting-leksjonen?
Ja. Alle Mestring av Linux-kommandolinjen og Bash-skripting-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.
Alle leksjonene i dette kurset
- Enhetstesting av funksjoner med Bats-core
- Mocking av kommandoer og stubbing av eksterne verktøy
- Testfiksturer, midlertidige miljøer og dekningsgrad
- Kjøring av skalltester i CI-pipelines