De Linux-opdrachtregel en Bash-scripting beheersen · Les

Fixtures, tijdelijke omgevingen en coverage

Bouw geïsoleerde testfixtures en meet welke takken van uw scripts uw tests daadwerkelijk uitvoeren.

Les 3 van 413 stappen

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() of cleanup() 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_fixture

Uitvoerbare 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:

  1. Maak een tijdelijke map bin/ in je fixture
  2. Schrijf daar kleine shellscripts met dezelfde namen als de echte hulpmiddelen
  3. Voeg die map vooraan toe aan $PATH voordat 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.html in een browser om de resultaten te bekijken
  • Verwerk in CI coverage-out/myscript.sh/coverage.json voor 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.json om 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, case en &&/|| 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_fixture aan en registreert teardown_fixture via trap
  • Het script dat wordt getest, wordt aangeroepen met een neppe $PATH en 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 -d plus een trap ... EXIT garandeert automatisch opruimen, ongeacht hoe de test eindigt.
  • Fixturestructuur — een paar van setup_fixture en teardown_fixture vult 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 $PATH om aanroepen naar curl, aws, docker of andere externe hulpmiddelen te onderscheppen zonder systeembinaries aan te raken.
  • Scope van de omgeving — voer het script dat wordt getest uit in een subshell (() of env -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.

Gratis beginnen

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

  1. Functies testen met Bats-core
  2. Commando's mocken en externe tools stubben
  3. Fixtures, tijdelijke omgevingen en coverage
  4. Shelltests uitvoeren in CI-pipelines
← Terug naar De Linux-opdrachtregel en Bash-scripting beheersen