Intensiv DevOps-uddannelse · Lektion

Mocking af kommandoer og stubbing af eksterne værktøjer

Overskriv PATH, og definér falske binærfiler for at teste scripts uden at påvirke rigtige systemer.

Lektion 2 af 413 trin

Mocking af kommandoer og stubbing af eksterne værktøjer er en gratis Intensiv DevOps-uddannelse-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Intensiv DevOps-uddannelse, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Intensiv DevOps-uddannelse-kurset indeholder 4 lektioner i alt.

Hvorfor simulere kommandoer i Bash-test?

Når du tester et Bash-script, der kalder curl, aws, git eller et andet eksternt værktøj, står du over for et problem: Rigtige kald tilgår netværket, ændrer tilstand, koster penge eller mislykkes ganske enkelt i et CI-miljø, hvor værktøjerne ikke er installeret.

Simulering betyder, at du erstatter den rigtige kommando med en falsk kommando, som du selv kontrollerer. Din falske kommando (din stub) returnerer forudsigeligt output og afslutningskoder, så din test er hurtig, isoleret og reproducerbar.

  • Ingen adgang til netværk eller cloud er nødvendig
  • Test køres på millisekunder i stedet for sekunder
  • Du kan simulere fejl, der er svære at fremkalde på rigtige systemer
  • CI-arbejdsgange forbliver enkle og uden afhængigheder

Bash giver dig en overraskende enkel mekanisme til dette: Placér blot din falske binærfil tidligere i $PATH end den rigtige.

Sådan fungerer opslag i PATH

Når shellen kører en kommando som curl, søger den i hver mappe i $PATH fra venstre mod højre og kører det første match, den finder.

Det betyder, at hvis du sætter en mappe med dit eget curl-script forrest, når shellen aldrig frem til /usr/bin/curl.

Mønsteret til tilsidesættelse:

  1. Opret en midlertidig mappe (din stub-binærmappe)
  2. Skriv en falsk kørbar fil med samme navn som den rigtige kommando
  3. Sæt mappen forrest i PATH
  4. Kør scriptet, der skal testes — det kalder din stub og ikke den rigtige binærfil
  5. Ryd den midlertidige mappe efter testen

Dette fungerer uden root-adgang, uden at ændre systemfiler og uden noget særligt rammeværk.

Oprettelse af en stubmappe

Standardmønsteret bruger mktemp -d til at oprette en isoleret midlertidig mappe til dine stubs. Hver test eller testsuite får sin egen mappe, så test ikke forurener hinanden.

Fjern mappen med rm -rf, når testen er afsluttet. En trap sikrer oprydning, også når testen afsluttes tidligt på grund af en fejl.

#!/usr/bin/env bash
# Setup a stub bin directory for testing

# Create the temp dir
STUB_BIN=$(mktemp -d)

# Always clean up on exit (success, error, or signal)
trap 'rm -rf "$STUB_BIN"' EXIT

# Prepend it to PATH so our stubs take priority
export PATH="$STUB_BIN:$PATH"

echo "Stub bin: $STUB_BIN"
echo "PATH starts with: ${PATH%%:*}"

# Your tests would go here...
echo "Tests complete."

Skriv din første stub

En stub er blot en kørbar fil med samme navn som den kommando, du vil erstatte. Den udskriver det output, som scriptet, der testes, forventer, og afslutter med den kode, du vælger.

Vigtige regler for stubs:

  • Filen skal være kørbar (chmod +x)
  • Shebang-linjen (#!/usr/bin/env bash) er påkrævet
  • Udskriv det output, som dit rigtige script ville fortolke
  • Brug exit 0 ved succes og en værdi, der ikke er nul, ved simulerede fejl
#!/usr/bin/env bash
# Create a stub for 'curl' that returns a fake HTTP response

STUB_BIN=$(mktemp -d)
trap 'rm -rf "$STUB_BIN"' EXIT
export PATH="$STUB_BIN:$PATH"

# Write the stub
cat > "$STUB_BIN/curl" << 'EOF'
#!/usr/bin/env bash
# Fake curl: always returns a 200 OK with a JSON body
echo '{"status": "ok", "version": "1.2.3"}'
exit 0
EOF
chmod +x "$STUB_BIN/curl"

# Verify the stub is found before the real curl
which curl
curl https://example.com/api/version

Test af et script, der kalder curl

Lad os nu samle det hele. Antag, at du har et implementeringsscript, der kalder curl for at kontrollere et sundhedsendepunkt og derefter afslutter med en fejl, hvis tjenesten ikke er sund. Du vil teste både den normale og den fejlslagne sti uden en rigtig server.

#!/usr/bin/env bash
# Script under test: check_health.sh
# It calls curl and checks the returned JSON

check_health() {
  local url="$1"
  local response
  response=$(curl -sf "$url")
  if [[ "$response" == *'"healthy":true'* ]]; then
    echo "Service is UP"
    return 0
  else
    echo "Service is DOWN" >&2
    return 1
  fi
}

# ---- Test harness ----
STUB_BIN=$(mktemp -d)
trap 'rm -rf "$STUB_BIN"' EXIT
export PATH="$STUB_BIN:$PATH"

# Happy path stub
cat > "$STUB_BIN/curl" << 'EOF'
#!/usr/bin/env bash
echo '{"healthy":true}'
EOF
chmod +x "$STUB_BIN/curl"

check_health "http://fake-host/health" && echo "PASS: healthy response"

Simulering af kommandofejl

En af de mest værdifulde anvendelser af stubs er at simulere fejl, som er vanskelige at genskabe med rigtige værktøjer — netværkstimeouts, adgangsfejl, fuld disk eller en ekstern API, der returnerer en 500-fejl.

Du simulerer en fejl ved ganske enkelt at lade din stub afslutte med en kode, der ikke er nul. Du kan også skrive til stderr præcis som den rigtige kommando ville gøre, så dit scripts fejlhåndtering bliver gennemtestet.

#!/usr/bin/env bash
# Test that check_health handles a curl failure gracefully

check_health() {
  local url="$1"
  local response
  # -f makes curl exit non-zero on HTTP error; -s silences progress
  if ! response=$(curl -sf "$url" 2>/dev/null); then
    echo "ERROR: could not reach $url" >&2
    return 1
  fi
  echo "OK: $response"
}

STUB_BIN=$(mktemp -d)
trap 'rm -rf "$STUB_BIN"' EXIT
export PATH="$STUB_BIN:$PATH"

# Failure stub — simulates a network error (curl exit code 6 = could not resolve host)
cat > "$STUB_BIN/curl" << 'EOF'
#!/usr/bin/env bash
echo 'curl: (6) Could not resolve host: fake-host' >&2
exit 6
EOF
chmod +x "$STUB_BIN/curl"

if ! check_health "http://fake-host/health"; then
  echo "PASS: failure path handled correctly"
fi

Registrering af stub-kald til verifikation

Nogle gange har du brug for at kontrollere ikke kun hvad dit script skriver som output, men også hvordan det kaldte et eksternt værktøj — argumenterne, det sendte, hvor mange gange det blev kaldt, eller i hvilken rækkefølge. En spion-stub registrerer sine kald i en fil.

Efter testen læser dit testværktøj registreringsfilen og kontrollerer dens indhold. Det giver dig verifikation på argumentniveau uden et særligt framework.

#!/usr/bin/env bash
# Spy stub: record every invocation of 'aws' to a log file

STUB_BIN=$(mktemp -d)
CALL_LOG=$(mktemp)
trap 'rm -rf "$STUB_BIN" "$CALL_LOG"' EXIT
export PATH="$STUB_BIN:$PATH"
export CALL_LOG   # make it available inside the stub

cat > "$STUB_BIN/aws" << 'EOF'
#!/usr/bin/env bash
# Append all arguments to the call log
echo "aws $*" >> "$CALL_LOG"
# Return fake S3 output
echo "upload: ./report.pdf to s3://my-bucket/report.pdf"
exit 0
EOF
chmod +x "$STUB_BIN/aws"

# Simulate the script under test calling aws s3 cp
aws s3 cp report.pdf s3://my-bucket/report.pdf
aws s3 cp logs.tar.gz s3://my-bucket/logs.tar.gz

# Verify calls were made with expected arguments
echo "--- Recorded calls ---"
cat "$CALL_LOG"
grep -q 's3://my-bucket/report.pdf' "$CALL_LOG" && echo "PASS: S3 upload verified"

Stubning af flere kommandoer på én gang

Et rigtigt script kalder ofte flere eksterne værktøjer. Du kan stubbe dem alle i den samme STUB_BIN-mappe. Hver stub-fil er uafhængig og kan returnere forskelligt output og forskellige afslutningskoder.

Hold stubs minimale: Returnér kun det, som scriptet under test rent faktisk fortolker. Forsøg ikke at simulere hvert eneste flag — brug kun det udsnit, dit script anvender.

#!/usr/bin/env bash
# Stub both 'git' and 'docker' for a release script test

STUB_BIN=$(mktemp -d)
trap 'rm -rf "$STUB_BIN"' EXIT
export PATH="$STUB_BIN:$PATH"

# Stub git: pretend we are on tag v2.1.0
cat > "$STUB_BIN/git" << 'EOF'
#!/usr/bin/env bash
case "$*" in
  *"describe --tags"*) echo "v2.1.0" ;;
  *"rev-parse HEAD"*)  echo "abc1234" ;;
  *) echo "[git stub] unhandled: $*" >&2 ; exit 1 ;;
esac
EOF
chmod +x "$STUB_BIN/git"

# Stub docker: pretend build and push succeed
cat > "$STUB_BIN/docker" << 'EOF'
#!/usr/bin/env bash
echo "[docker stub] $*"
exit 0
EOF
chmod +x "$STUB_BIN/docker"

# Simulate the release logic
VERSION=$(git describe --tags)
SHA=$(git rev-parse HEAD)
echo "Building image for version=$VERSION sha=$SHA"
docker build -t "myapp:$VERSION" .
docker push "myapp:$VERSION"

Brug af funktioner som stubs (ingen filer nødvendige)

I enkle tilfælde behøver du slet ikke skrive filer. Du kan definere en shell-funktion med samme navn som kommandoen. Da funktioner slås op før eksternt PATH-opslag, får de automatisk prioritet.

Dette er den hurtigste metode til enhedstest af sourcede scripts. Funktionsstubs virker dog kun inden for den samme shell-proces — de er ikke synlige for subshells, der startes med eksplicit bash -c, eller for baggrundsprocesser. Brug den filbaserede metode til disse tilfælde.

#!/usr/bin/env bash
# Source the script under test (a small helper library)
source_under_test() {
  # Inline the logic we want to test
  get_instance_id() {
    # Would normally call: curl http://169.254.169.254/latest/meta-data/instance-id
    curl -sf http://169.254.169.254/latest/meta-data/instance-id
  }
}
source_under_test

# Override curl with a shell function stub
curl() {
  echo "i-0abc123def456"
  return 0
}
# Export is NOT needed — function is visible in same shell

# Run the function under test
result=$(get_instance_id)
[[ "$result" == "i-0abc123def456" ]] && echo "PASS: instance ID returned" || echo "FAIL"

Eksport af funktioner til subshells

Når dit script under test starter en subshell (f.eks. bash script.sh eller en pipeline), nedarves shell-funktionsstubs, der er defineret i den overordnede proces, ikke automatisk. Du har to muligheder:

  • Brug export -f function_name til at eksportere funktionen — så bliver den tilgængelig for underordnede bash-processer
  • Eller brug filbaserede stubs i en STUB_BIN-mappe, som altid virker på tværs af procesgrænser

export -f er elegant, men virker kun med bash (ikke sh eller andre shells). Foretræk filbaserede stubs i CI-miljøer med flere shell-sprog.

#!/usr/bin/env bash
# Demonstrate export -f for subshell-visible function stubs

# Define the stub in the current shell
curl() {
  echo '{"status":"ok"}'
  return 0
}
# Export the function so child bash processes inherit it
export -f curl

# Verify the stub works in a subshell
bash -c '
  response=$(curl -sf http://api.example.com/status)
  echo "Subshell got: $response"
'

# Without export -f, the subshell would call the real curl
# (or fail if curl is not installed)

Integration af stubs med et testframework (BATS)

Når du bruger BATS (Bash Automated Testing System), hører opsætning af stubs hjemme i setup()-hooket, mens oprydning hører hjemme i teardown(). BATS nulstiller miljøet mellem tests, så hver test får en ny stub-mappe.

BATS-variabler som $BATS_TEST_TMPDIR giver dig automatisk en midlertidig mappe pr. test — brug den i stedet for mktemp -d for at få renere kode.

#!/usr/bin/env bats
# File: test_deploy.bats
# Run with: bats test_deploy.bats

setup() {
  # BATS provides a unique tmpdir per test
  export STUB_BIN="$BATS_TEST_TMPDIR/stub_bin"
  mkdir -p "$STUB_BIN"
  export PATH="$STUB_BIN:$PATH"

  # Default stub: healthy service
  cat > "$STUB_BIN/curl" << 'EOF'
#!/usr/bin/env bash
echo '{"healthy":true}'
EOF
  chmod +x "$STUB_BIN/curl"
}

teardown() {
  # BATS auto-removes BATS_TEST_TMPDIR, but explicit is safer
  rm -rf "$STUB_BIN"
}

@test "deploy succeeds when service is healthy" {
  run bash deploy.sh
  [ "$status" -eq 0 ]
  [[ "$output" == *"Deploy complete"* ]]
}

@test "deploy aborts when service is down" {
  # Override the stub for this specific test
  echo -e '#!/usr/bin/env bash\nexit 1' > "$STUB_BIN/curl"
  chmod +x "$STUB_BIN/curl"

  run bash deploy.sh
  [ "$status" -ne 0 ]
}

Videnstjek: Mocking af kommandoer i Bash

Test din forståelse af mocking af kommandoer og stub-mønstre i Bash.

En udvikler skriver en test, der definerer en shell-funktion med navnet aws for at stubbe den rigtige AWS CLI. Testen kører fint direkte i terminalen, men når CI-pipelinen kører scriptet under test som bash deploy.sh, ignoreres stubben, og den rigtige aws-kommando kaldes.

Hvad er den korrekte løsning?

Opsummering: Mocking af kommandoer og stubning af eksterne værktøjer

Du har lært det komplette værktøjssæt til at erstatte rigtige kommandoer med kontrollerede efterligninger under test af Bash-scripts.

Gennemgåede kerneteknikker:

  • Foranstilling af PATH — opret en STUB_BIN-mappe med mktemp -d, skriv eksekverbare stub-filer der, og sæt mappen forrest i PATH
  • Stub-afslutningskoder — returnér 0 ved succes og værdier, der ikke er nul, for at simulere bestemte fejl (netværksfejl, adgang nægtet osv.)
  • Spion-stubs — føj argumenter til en logfil inde i stubben for at kontrollere, hvordan dit script kaldte eksterne værktøjer
  • Flere stubs — placér flere stub-filer i den samme STUB_BIN for at mocke et helt økosystem af afhængigheder på én gang
  • Funktionsstubs — definér en shell-funktion med samme navn som en kommando for mocking i den samme proces; brug export -f for at nå underordnede bash-processer
  • BATS-integration — brug setup()/teardown()-hooks og $BATS_TEST_TMPDIR for ren isolation pr. test

Brug altid en trap '...' EXIT for at sikre oprydning af stubs uanset testens resultat. Hold stubs minimale — returnér kun det, som dit script rent faktisk fortolker. Filbaserede stubs er det mest portable valg til CI-pipelines.

Gratis at komme i gang

Lær Intensiv DevOps-uddannelse med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
142
Lektioner
568

Ofte stillede spørgsmål

Er lektionen “Mocking af kommandoer og stubbing af eksterne værktøjer” gratis?

Ja — alle 3 lektioner i læringssporet Intensiv DevOps-uddannelse, inklusive “Mocking af kommandoer og stubbing af eksterne værktøjer”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Intensiv DevOps-uddannelse-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Mocking af kommandoer og stubbing af eksterne værktøjer”?

Overskriv PATH, og definér falske binærfiler for at teste scripts uden at påvirke rigtige systemer. Du øver dig i Intensiv DevOps-uddannelse med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Intensiv DevOps-uddannelse?

Der kræves ingen tidligere erfaring. Intensiv DevOps-uddannelse på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.

Hvor lang tid tager lektionen “Mocking af kommandoer og stubbing af eksterne værktøjer”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Intensiv DevOps-uddannelse-lektion?

Ja. Alle Intensiv DevOps-uddannelse-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Enhedstest af funktioner med Bats-core
  2. Mocking af kommandoer og stubbing af eksterne værktøjer
  3. Test-fixtures, midlertidige miljøer og dækning
  4. Kørsel af shelltests i CI-pipelines
← Tilbage til Intensiv DevOps-uddannelse