0Pricing
DevOps Bootcamp · Lezione

Simulare comandi e creare stub per strumenti esterni

Modifichi PATH e definisca binari fittizi per testare gli script senza intervenire sui sistemi reali.

Simulare comandi e creare stub per strumenti esterni è una lezione DevOps Bootcamp gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento DevOps Bootcamp, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso DevOps Bootcamp include 4 lezioni in totale.

Perché simulare i comandi nei test Bash?

Quando si testa uno script Bash che chiama curl, aws, git o un altro strumento esterno, si presenta un problema: le chiamate reali accedono alla rete, modificano lo stato, hanno un costo o semplicemente falliscono in un ambiente CI in cui tali strumenti non sono installati.

Mocking significa sostituire il comando reale con uno falso sotto il proprio controllo. Il falso (lo stub) restituisce output e codici di uscita prevedibili, rendendo il test rapido, isolato e riproducibile.

  • Non è necessario alcun accesso alla rete o al cloud
  • I test vengono eseguiti in millisecondi anziché in secondi
  • È possibile simulare errori difficili da riprodurre sui sistemi reali
  • Le pipeline CI rimangono pulite e prive di dipendenze

Bash offre un meccanismo sorprendentemente semplice per farlo: è sufficiente inserire il proprio binario falso in una posizione precedente a quello reale in $PATH.

Come funziona la ricerca in PATH

Quando la shell esegue un comando come curl, cerca in ogni directory di $PATH, da sinistra a destra, ed esegue la prima corrispondenza trovata.

Questo significa che, anteponendo a PATH una directory contenente un proprio script curl, la shell non raggiungerà mai /usr/bin/curl.

Pattern di sostituzione:

  1. Creare una directory temporanea (la propria stub bin)
  2. Scrivere un eseguibile falso con lo stesso nome del comando reale
  3. Anteporre quella directory a PATH
  4. Eseguire lo script da testare: chiamerà il proprio stub, non il binario reale
  5. Ripulire la directory temporanea dopo il test

Questa tecnica funziona senza accesso root, senza modificare i file di sistema e senza alcun framework speciale.

Creare una directory per gli stub

Il pattern standard usa mktemp -d per creare una directory temporanea isolata per gli stub. Ogni test o suite di test dispone della propria directory, evitando contaminazioni tra test.

Al termine del test, rimuovere la directory con rm -rf. L'uso di un trap garantisce la pulizia anche quando il test termina anticipatamente a causa di un errore.

#!/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."

Scrivere il primo stub

Uno stub è semplicemente un file eseguibile con lo stesso nome del comando che si desidera sostituire. Stampa l'output previsto dallo script in fase di test e termina con il codice scelto.

Regole fondamentali per gli stub:

  • Il file deve essere eseguibile (chmod +x)
  • La riga shebang (#!/usr/bin/env bash) è obbligatoria
  • Stampare l'output che lo script reale dovrebbe analizzare
  • Usare exit 0 per indicare il successo e un valore diverso da zero per simulare gli errori
#!/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

Testare uno script che chiama curl

Ora mettiamo insieme i concetti. Supponiamo di avere uno script di deployment che chiama curl per controllare un endpoint di stato, quindi termina con un errore se il servizio non è operativo. Si desidera testare sia il percorso di successo sia quello di errore senza usare un server reale.

#!/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"

Simulare i fallimenti dei comandi

Uno degli utilizzi più preziosi degli stub è la simulazione dei fallimenti difficili da riprodurre con strumenti reali: timeout di rete, errori di autorizzazione, disco pieno o un'API remota che restituisce un 500.

Per simulare un fallimento, è sufficiente fare in modo che lo stub termini con un codice diverso da zero. È inoltre possibile scrivere in stderr esattamente come farebbe il comando reale, così da verificare completamente la gestione degli errori dello script.

#!/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

Registrare le chiamate agli stub per la verifica

A volte è necessario verificare non solo che cosa produce lo script, ma anche come chiama uno strumento esterno: gli argomenti passati, il numero di chiamate o il loro ordine. Uno stub spy registra le chiamate in un file.

Dopo il test, il test harness legge il file dei record e ne verifica il contenuto. In questo modo si ottiene una verifica a livello di argomento senza alcun framework speciale.

#!/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"

Creare stub per più comandi contemporaneamente

Uno script reale chiama spesso diversi strumenti esterni. È possibile creare gli stub per tutti questi strumenti nella stessa directory STUB_BIN. Ogni file stub è indipendente e può restituire output e codici di uscita diversi.

Mantenga gli stub essenziali: restituisca solo ciò che lo script sottoposto al test analizza effettivamente. Non cerchi di simulare ogni flag, ma solo il sottoinsieme utilizzato dallo script.

#!/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"

Usare funzioni come stub (senza file)

Per i casi semplici non è necessario scrivere alcun file. È possibile definire una funzione shell con lo stesso nome del comando. Poiché le funzioni vengono risolte prima della ricerca esterna in PATH, hanno automaticamente la precedenza.

Questo è l'approccio più rapido per i test unitari degli script con codice caricato tramite source. Tuttavia, gli stub basati su funzioni funzionano solo all'interno dello stesso processo della shell: non sono visibili alle subshell avviate con bash -c in modo esplicito né ai processi eseguiti in background. In questi casi, utilizzi l'approccio basato sui file.

#!/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"

Esportare le funzioni nelle subshell

Quando lo script sottoposto al test avvia una subshell (ad esempio bash script.sh o una pipeline), gli stub di funzioni shell definiti nel processo padre non vengono ereditati per impostazione predefinita. Sono disponibili due opzioni:

  • Utilizzare export -f function_name per esportare la funzione, rendendola disponibile ai processi bash figli
  • Oppure ricorrere agli stub basati su file in una directory STUB_BIN, che funzionano sempre oltre i confini tra processi

export -f è elegante, ma funziona solo con bash (non con sh o altre shell). Preferisca gli stub basati su file negli ambienti CI poliglotta.

#!/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)

Integrare gli stub con un framework di test (BATS)

Quando utilizza BATS (Bash Automated Testing System), la configurazione degli stub deve trovarsi nell'hook setup() e la pulizia nell'hook teardown(). BATS ripristina l'ambiente tra un test e l'altro, quindi ogni test dispone di una directory per gli stub nuova.

Le variabili di BATS, come $BATS_TEST_TMPDIR, forniscono automaticamente una directory temporanea per ogni test: la utilizzi al posto di mktemp -d per ottenere codice più pulito.

#!/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 ]
}

Verifica delle conoscenze: simulare i comandi in Bash

Verifichi la propria comprensione della simulazione dei comandi e dei modelli di stub in Bash.

Uno sviluppatore scrive un test che definisce una funzione shell chiamata aws per simulare la vera AWS CLI. Il test funziona correttamente se eseguito direttamente nel terminale, ma quando la pipeline CI esegue lo script sottoposto al test come bash deploy.sh, lo stub viene ignorato e viene chiamato il comando reale aws.

Qual è la correzione corretta?

Riepilogo: simulare i comandi e creare stub per gli strumenti esterni

Ha appreso il kit completo di strumenti per sostituire i comandi reali con simulazioni controllate durante i test degli script Bash.

Tecniche fondamentali trattate:

  • Prependere una directory a PATH — creare una directory STUB_BIN con mktemp -d, scrivervi file stub eseguibili e anteporre la directory a PATH
  • Codici di uscita degli stub — restituire 0 per il successo e valori diversi da zero per simulare fallimenti specifici (errori di rete, autorizzazione negata e così via)
  • Stub spy — aggiungere gli argomenti a un file di log all'interno dello stub per verificare come lo script ha chiamato gli strumenti esterni
  • Più stub — inserire diversi file stub nella stessa directory STUB_BIN per simulare contemporaneamente un intero ecosistema di dipendenze
  • Stub di funzione — definire una funzione shell con lo stesso nome di un comando per la simulazione nello stesso processo; utilizzare export -f per raggiungere i processi bash figli
  • Integrazione con BATS — utilizzare gli hook setup()/teardown() e $BATS_TEST_TMPDIR per un isolamento pulito tra i test

Utilizzi sempre un trap '...' EXIT per garantire la pulizia degli stub indipendentemente dall'esito del test. Mantenga gli stub essenziali: restituisca solo ciò che lo script analizza effettivamente. Gli stub basati su file sono la scelta più portabile per le pipeline CI.

Domande Frequenti

La lezione «Simulare comandi e creare stub per strumenti esterni» è gratuita?

Sì — il testo completo di «Simulare comandi e creare stub per strumenti esterni» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso DevOps Bootcamp, passa a CoddyKit PRO. Il corso DevOps Bootcamp include 4 lezioni in totale.

Cosa imparerò in «Simulare comandi e creare stub per strumenti esterni»?

Modifichi PATH e definisca binari fittizi per testare gli script senza intervenire sui sistemi reali. Eserciti DevOps Bootcamp con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare DevOps Bootcamp?

Non è richiesta alcuna esperienza precedente. DevOps Bootcamp su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Simulare comandi e creare stub per strumenti esterni»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione DevOps Bootcamp?

Sì. Ogni lezione DevOps Bootcamp include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Test unitari delle funzioni con Bats-core
  2. Simulare comandi e creare stub per strumenti esterni
  3. Fixture, ambienti temporanei e coverage
  4. Eseguire test shell nelle pipeline CI
← Torna a DevOps Bootcamp