Fixtures, tijdelijke omgevingen en coverage
Bouw geïsoleerde testfixtures en meet welke takken van uw scripts uw tests daadwerkelijk uitvoeren.
Fixtures, tijdelijke omgevingen en coverage is een gratis De Linux-opdrachtregel en Bash-scripting beheersen-les op CoddyKit. Dit is les 3 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject De Linux-opdrachtregel en Bash-scripting beheersen. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus De Linux-opdrachtregel en Bash-scripting beheersen bevat in totaal 4 lessen.
Waarom fixtures en isolatie belangrijk zijn
Bij het testen van Bash-scripts vormen bijwerkingen het grootste risico: je tests wijzigen per ongeluk echte bestanden, echte databases of de echte systeemstatus. Een test die op jouw computer slaagt maar productiegegevens beschadigt, is erger dan helemaal geen test.
De oplossing bestaat uit testfixtures — gecontroleerde, wegwerpbare omgevingen die echte omstandigheden nabootsen zonder iets echts aan te raken. Goede fixtures bieden:
- Reproduceerbaarheid — tests leveren bij elke uitvoering hetzelfde resultaat op
- Isolatie — tests beïnvloeden elkaar of het hostsysteem niet
- Veiligheid — destructieve bewerkingen raken alleen wegwerpgegevens
- Snelheid — geen netwerkoproepen en geen zware I/O tenzij dat absoluut nodig is
Bij Bash-tests zijn fixtures meestal tijdelijke mappen met bekende bestanden, nepuitvoerbare bestanden die vooraan in $PATH staan, en omgevingsvariabelen waarvan het bereik beperkt is tot het testproces.
Tijdelijke mappen maken en opruimen
Het standaardpatroon voor een tijdelijke map per test gebruikt mktemp -d. Daarmee wordt een unieke map in /tmp gemaakt en het pad ervan afgedrukt. Je slaat het pad op en registreert een trap om de map automatisch te verwijderen wanneer de shell wordt afgesloten — ook bij een fout.
Dit idioom van twee regels hoort in elk testbestand dat het bestandssysteem aanraakt:
#!/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'Een boomstructuur voor fixtures opzetten
Een goed gestructureerde fixture weerspiegelt de mappenindeling die je te testen script daadwerkelijk verwacht. Zie het als een kleine nep-hoofdmap van een project. De functie voor het instellen van de fixture maakt deze indeling vóór elke test, en de opruimactie verwijdert haar daarna.
Belangrijke werkwijzen:
- Gebruik een functie
setup()die je testraamwerk vóór elke test aanroept - Gebruik een
teardown()ofcleanup()die na elke test wordt uitgevoerd, ook bij een fout - Houd fixturebestanden minimaal — alleen wat het script daadwerkelijk leest
- Geef fixturebestanden beschrijvende namen, zodat fouten eenvoudig te diagnosticeren zijn
#!/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_fixtureUitvoerbare bestanden mocken met een nep-<code>PATH</code>
Veel Bash-scripts roepen externe hulpmiddelen aan, zoals curl, aws, docker of git. Tijdens tests wil je geen echte diensten aanspreken, dus vervang je die hulpmiddelen door nepuitvoerbare bestanden.
De techniek is eenvoudig:
- Maak een tijdelijke map
bin/in je fixture - Schrijf daar kleine shellscripts met dezelfde namen als de echte hulpmiddelen
- Voeg die map vooraan toe aan
$PATHvoordat je je te testen script aanroept
Omdat er van links naar rechts in $PATH wordt gezocht, krijgt de nepversie voorrang. Het echte binaire bestand wordt nooit aangeroepen.
#!/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"Uitvoer van opdrachten vastleggen en controleren
Een fixture is alleen nuttig als je kunt controleren wat je script heeft gedaan. De standaardpatronen zijn:
- Leg stdout/stderr vast in variabelen met
$()of procesvervanging - Bekijk logbestanden die door nep-binaire bestanden zijn geschreven
- Controleer afsluitcodes expliciet met
$?of voorwaardelijke logica - Controleer of bepaalde bestanden zijn gemaakt, gewijzigd of ongemoeid zijn gelaten
Met kleine, gerichte hulpfuncties voor controles blijven je tests leesbaar en krijg je nauwkeurige foutmeldingen wanneer er iets misgaat.
#!/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"Bereik van omgevingsvariabelen in tests
Scripts lezen vaak omgevingsvariabelen zoals $HOME, $CONFIG_PATH of $DATABASE_URL. Tijdens tests moet je deze overschrijven zonder de echte omgeving te vervuilen.
De veiligste aanpak is om het te testen script uit te voeren in een subproces met alleen de variabelen die je expliciet instelt. Met de opdracht env kun je de omgeving opschonen en alleen toevoegen wat je nodig hebt:
env -i VAR=val ./script.sh— volledig schone omgeving(export VAR=val; ./script.sh)— subprocess neemt de omgeving van het bovenliggende proces over, plus jouw overschrijvingen
Het gebruik van subprocessen betekent ook dat wijzigingen van het script aan $IFS, $PWD of een andere globale status niet teruglekken naar je testuitvoerder.
#!/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"Inleiding tot kcov voor Bash-dekking
Dekking beantwoordt de vraag: welke regels (en vertakkingen) van mijn script hebben de tests daadwerkelijk uitgevoerd? Een hoog dekkingspercentage garandeert geen correctheid, maar een laag percentage onthult niet-geteste paden waarin waarschijnlijk fouten zitten.
Het belangrijkste hulpmiddel voor Bash-dekking is kcov. Het instrumenteert het script op besturingssysteemniveau met PTRACE (Linux) of dtrace (macOS), zodat je geen wijzigingen in de broncode hoeft aan te brengen. Het maakt een HTML-rapport met rode (niet-uitgevoerde) en groene (uitgevoerde) regels.
Basisgebruik:
kcov --include-path=./src coverage-out/ ./src/myscript.sh- Open
coverage-out/index.htmlin een browser om de resultaten te bekijken - Verwerk in CI
coverage-out/myscript.sh/coverage.jsonvoor een machinaal leesbaar percentage
Let op: kcov moet afzonderlijk worden geïnstalleerd (brew install kcov op macOS, apt install kcov op Ubuntu 20.04+).
kcov uitvoeren op een echt script
Hier volgt een volledig voorbeeld met een inzetbaar script, een test die het uitvoert en de kcov-aanroep die de dekking meet. Let erop dat de uitvoermap per test wordt gemaakt, zodat je de resultaten van meerdere testruns later kunt samenvoegen.
#!/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"Dekking van meerdere testruns samenvoegen
Een enkele test dekt zelden alle vertakkingen. Je voert meerdere tests uit — elk in een eigen uitvoermap voor de dekking — en voegt ze daarna samen. De vlag --merge van kcov combineert meerdere uitvoeringen tot één uniform rapport.
Het gebruikelijke patroon in een CI-pijplijn:
- Voer test A uit → uitvoer naar
cov/test_a/ - Voer test B uit → uitvoer naar
cov/test_b/ - Voeg samen →
kcov --merge cov/all/ cov/test_a/ cov/test_b/ - Verwerk
cov/all/<script>/coverage.jsonom het uiteindelijke percentage te verkrijgen
Je kunt ook een minimumdrempel afdwingen en de CI-build laten mislukken als de dekking daaronder zakt:
#!/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"Vertakkingsdekking versus regeldekking
Er zijn twee belangrijke dekkingsmetingen die je zult tegenkomen:
- Regeldekking — is deze regel überhaupt uitgevoerd? Dit is eenvoudig te manipuleren: één test kan veel regels aanraken en toch belangrijke voorwaardelijke paden missen.
- Vertakkingsdekking — is elke vertakking van elke
if,caseen&&/||doorlopen? Dit geeft een veel sterker signaal. Je hebt tests nodig voor zowel de ware als de onware kant van elke beslissing.
kcov rapporteert beide. Het belangrijkste inzicht: 100% regeldekking betekent niet automatisch 100% vertakkingsdekking. Bekijk dit script — één test met een niet-leeg bestand dekt elke regel, maar de vertakking voor een leeg bestand (regel 9 hieronder) wordt nooit bereikt:
#!/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")Fixtures en dekking integreren in CI
Alles samenvoegen: een robuuste CI-pijplijn voor Bash-projecten combineert het instellen van fixtures, het uitvoeren van tests met kcov, het samenvoegen en een drempelcontrole in één script. Dit script wordt het startpunt van CI: één opdracht om alles uit te voeren.
Ontwerpprincipes voor de testrunner van CI:
- Elke testcase roept
setup_fixtureaan en registreertteardown_fixtureviatrap - Het script dat wordt getest, wordt aangeroepen met een neppe
$PATHen variabelen met een beperkte scope - kcov omwikkelt elke aanroep en schrijft naar een genummerde submap
- Na alle tests voegt kcov de resultaten samen en bepaalt het drempelscript of de build mag doorgaan
- De CI-runner wordt afgesloten met een niet-nulstatus als een test of de dekkingscontrole mislukt
#!/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 ))Kenniscontrole: techniek met een neppe PATH
Test uw begrip van de techniek met een neppe PATH die in Bash-testfixtures wordt gebruikt.
Samenvatting: fixtures, tijdelijke omgevingen en dekking
In deze les hebt u de volledige gereedschapskist behandeld voor betrouwbare, geïsoleerde Bash-tests:
- Tijdelijke mappen —
mktemp -dplus eentrap ... EXITgarandeert automatisch opruimen, ongeacht hoe de test eindigt. - Fixturestructuur — een paar van
setup_fixtureenteardown_fixturevult een minimale mappenstructuur die echte scriptinvoer nabootst en ruimt die weer op. - Neppe PATH — plaats stub-uitvoerbare bestanden in
$FIXTURE/bin/en zet die map vooraan in$PATHom aanroepen naarcurl,aws,dockerof andere externe hulpmiddelen te onderscheppen zonder systeembinaries aan te raken. - Scope van de omgeving — voer het script dat wordt getest uit in een subshell (
()ofenv -i), zodat gewijzigde variabelen nooit teruglekken naar de testrunner. - Asserties — kleine helperfuncties (
assert_eq,assert_file_exists,assert_contains) produceren duidelijke geslaagd/mislukt-uitvoer en betekenisvolle foutmeldingen. - kcov-dekking — omwikkelt de uitvoering van scripts zonder wijzigingen aan de broncode en produceert HTML- en JSON-rapporten voor regel- en vertakkingsdekking.
- Samenvoegen en drempel — combineer meerdere kcov-uitvoeringen met
--merge, verwerk de JSON en laat CI mislukken als de dekking onder uw minimum komt. - Vertakkingsdekking versus regeldekking — richt u altijd op vertakkingsdekking; alleen regeldekking kan volledige voorwaardelijke paden missen en een vals gevoel van zekerheid geven.
Met deze technieken worden uw Bash-tests net zo grondig als tests voor elke gecompileerde taal.
Leer Bash met een AI-tutor — gratis
Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.
- Cursussen
- 22
- Lessen
- 88
Veelgestelde vragen
Is de les “Fixtures, tijdelijke omgevingen en coverage” gratis?
Ja — de volledige tekst van “Fixtures, tijdelijke omgevingen en coverage” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus De Linux-opdrachtregel en Bash-scripting beheersen wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus De Linux-opdrachtregel en Bash-scripting beheersen bevat in totaal 4 lessen.
Wat leer ik in “Fixtures, tijdelijke omgevingen en coverage”?
Bouw geïsoleerde testfixtures en meet welke takken van uw scripts uw tests daadwerkelijk uitvoeren. Je oefent met De Linux-opdrachtregel en Bash-scripting beheersen door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.
Heb ik ervaring nodig om met De Linux-opdrachtregel en Bash-scripting beheersen te beginnen?
Ervaring vooraf is niet nodig. De Linux-opdrachtregel en Bash-scripting beheersen op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 3 van 4.
Hoe lang duurt de les “Fixtures, tijdelijke omgevingen en coverage”?
De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.
Kan ik code schrijven en uitvoeren in deze les over De Linux-opdrachtregel en Bash-scripting beheersen?
Ja. Elke les over De Linux-opdrachtregel en Bash-scripting beheersen bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.
Alle lessen in deze cursus
- Functies testen met Bats-core
- Commando's mocken en externe tools stubben
- Fixtures, tijdelijke omgevingen en coverage
- Shelltests uitvoeren in CI-pipelines