0Pricing
Linux Command Line & Bash Scripting Mastery · Lekcja

Mockowanie poleceń i zastępowanie narzędzi zewnętrznych

Nadpisuj PATH i definiuj fałszywe pliki wykonywalne, aby testować skrypty bez ingerowania w rzeczywiste systemy.

Mockowanie poleceń i zastępowanie narzędzi zewnętrznych to bezpłatna lekcja Linux Command Line & Bash Scripting Mastery na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Linux Command Line & Bash Scripting Mastery, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Linux Command Line & Bash Scripting Mastery zawiera 4 lekcji w sumie.

Dlaczego mockować polecenia w testach Bash?

Podczas testowania skryptu Bash, który wywołuje curl, aws, git lub inne zewnętrzne narzędzie, pojawia się problem: rzeczywiste wywołania korzystają z sieci, modyfikują stan, generują koszty albo po prostu kończą się niepowodzeniem w środowisku CI, w którym tych narzędzi nie zainstalowano.

Mockowanie oznacza zastąpienie rzeczywistego polecenia kontrolowaną imitacją. Taka imitacja (czyli stub) zwraca przewidywalne dane wyjściowe i kody wyjścia, dzięki czemu test jest szybki, izolowany i powtarzalny.

  • Nie jest potrzebny dostęp do sieci ani chmury
  • Testy wykonują się w milisekundach zamiast w sekundach
  • Można symulować błędy, które trudno wywołać w rzeczywistych systemach
  • Potoki CI pozostają uporządkowane i wolne od zależności

Bash udostępnia zaskakująco prosty mechanizm: wystarczy umieścić fikcyjny plik binarny w katalogu znajdującym się w $PATH wcześniej niż katalog z rzeczywistym plikiem.

Jak działa wyszukiwanie w PATH

Gdy powłoka wykonuje polecenie takie jak curl, przeszukuje każdy katalog w zmiennej $PATH od lewej do prawej i uruchamia pierwsze znalezione dopasowanie.

Oznacza to, że jeśli na początku zmiennej dodadzą Państwo katalog zawierający własny skrypt curl, powłoka nigdy nie dotrze do /usr/bin/curl.

Wzorzec zastępowania:

  1. Utwórz katalog tymczasowy (swój katalog plików binarnych ze stubami)
  2. Zapisz w nim fikcyjny plik wykonywalny o takiej samej nazwie jak rzeczywiste polecenie
  3. Dodaj ten katalog na początku zmiennej PATH
  4. Uruchom testowany skrypt — wywoła on stub, a nie rzeczywisty plik binarny
  5. Po teście usuń katalog tymczasowy

Rozwiązanie to działa bez uprawnień administratora, bez modyfikowania plików systemowych i bez specjalnego frameworka.

Tworzenie katalogu stubów

Standardowy wzorzec wykorzystuje mktemp -d do utworzenia odizolowanego katalogu tymczasowego na stuby. Każdy test lub zestaw testów otrzymuje własny katalog, co zapobiega zanieczyszczaniu środowiska przez inne testy.

Po zakończeniu testu usuń katalog za pomocą rm -rf. Użycie trap gwarantuje posprzątanie nawet wtedy, gdy test zakończy się wcześniej z powodu błędu.

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

Pisanie pierwszego stuba

Stub to po prostu plik wykonywalny o takiej samej nazwie jak polecenie, które chcesz zastąpić. Wypisuje on dane wyjściowe oczekiwane przez testowany skrypt i kończy działanie wybranym kodem.

Najważniejsze zasady dotyczące stubów:

  • Plik musi być wykonywalny (chmod +x)
  • Wiersz shebangu (#!/usr/bin/env bash) jest wymagany
  • Wypisz dane wyjściowe, które rzeczywisty skrypt miałby przetworzyć
  • Użyj exit 0 w przypadku powodzenia, a wartości niezerowej w przypadku symulowanych błędów
#!/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

Testowanie skryptu wywołującego curl

Połączmy teraz wszystkie elementy. Załóżmy, że mają Państwo skrypt wdrożeniowy, który wywołuje curl w celu sprawdzenia punktu końcowego stanu usługi, a następnie kończy działanie błędem, jeśli usługa nie działa prawidłowo. Należy przetestować zarówno ścieżkę powodzenia, jak i ścieżkę niepowodzenia — bez korzystania z rzeczywistego serwera.

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

Symulowanie awarii poleceń

Jednym z najbardziej wartościowych zastosowań atrap jest symulowanie awarii, które trudno odtworzyć przy użyciu rzeczywistych narzędzi — przekroczeń czasu oczekiwania sieci, błędów uprawnień, braku miejsca na dysku lub zdalnego interfejsu API zwracającego kod 500.

Aby zasymulować awarię, wystarczy sprawić, aby atrapa zakończyła działanie z kodem różnym od zera. Można również zapisywać dane do stderr dokładnie tak, jak zrobiłoby to rzeczywiste polecenie, dzięki czemu obsługa błędów w skrypcie zostanie w pełni przetestowana.

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

Rejestrowanie wywołań atrap w celu weryfikacji

Czasami trzeba sprawdzić nie tylko co zwraca skrypt, lecz także jak wywołuje zewnętrzne narzędzie — jakie przekazuje argumenty, ile razy je wywołuje lub w jakiej kolejności. Atrapa typu spy zapisuje swoje wywołania do pliku.

Po zakończeniu testu mechanizm testowy odczytuje plik z rejestrem i sprawdza jego zawartość. Pozwala to weryfikować argumenty bez używania specjalnego frameworka.

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

Stubowanie wielu poleceń jednocześnie

Rzeczywisty skrypt często wywołuje kilka zewnętrznych narzędzi. Wszystkie można zastąpić atrapami w tym samym katalogu STUB_BIN. Każdy plik atrapy jest niezależny i może zwracać inne dane wyjściowe oraz kody zakończenia.

Atrapy powinny być minimalne: zwracaj tylko to, co testowany skrypt rzeczywiście parsuje. Nie próbuj symulować każdej opcji — wystarczy podzbiór używany przez skrypt.

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

Używanie funkcji jako atrap (bez plików)

W prostych przypadkach nie trzeba w ogóle tworzyć plików. Można zdefiniować funkcję powłoki o tej samej nazwie co polecenie. Ponieważ funkcje są wyszukiwane przed zewnętrznym wyszukiwaniem w PATH, automatycznie uzyskują priorytet.

To najszybsze podejście do testowania jednostkowego skryptów wczytywanych za pomocą source. Atrapy będące funkcjami działają jednak tylko w tym samym procesie powłoki — nie będą widoczne dla podpowłok uruchamianych jawnie za pomocą bash -c ani dla procesów działających w tle. W takich przypadkach należy użyć podejścia opartego na plikach.

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

Eksportowanie funkcji do podpowłok

Gdy testowany skrypt uruchamia podpowłokę (np. bash script.sh lub potok), funkcje powłoki zdefiniowane w procesie nadrzędnym nie są domyślnie dziedziczone. Dostępne są dwie możliwości:

  • Użyć export -f function_name, aby wyeksportować funkcję — stanie się dostępna dla potomnych procesów bash
  • Albo użyć atrap opartych na plikach w katalogu STUB_BIN, które zawsze działają między granicami procesów

export -f jest eleganckim rozwiązaniem, ale działa tylko z bash (nie z sh ani innymi powłokami). W środowiskach CI wykorzystujących różne języki i powłoki preferuj atrapy oparte na plikach.

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

Integracja atrap z frameworkiem testowym (BATS)

W przypadku używania BATS (Bash Automated Testing System) konfiguracja atrap powinna znajdować się w hooku setup(), a sprzątanie — w teardown(). BATS resetuje środowisko między testami, więc każdy test otrzymuje nowy katalog atrap.

Zmienne BATS, takie jak $BATS_TEST_TMPDIR, automatycznie udostępniają katalog tymczasowy dla danego testu — używaj go zamiast mktemp -d, aby kod był czytelniejszy.

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

Sprawdzenie wiedzy: mockowanie poleceń w Bash

Sprawdź, czy rozumiesz mockowanie poleceń i wzorce atrap w Bash.

Programista tworzy test definiujący funkcję powłoki o nazwie aws, aby zastąpić rzeczywiste AWS CLI. Test działa poprawnie uruchomiony bezpośrednio w terminalu, ale gdy potok CI uruchamia testowany skrypt jako bash deploy.sh, atrapa jest ignorowana i wywoływane jest rzeczywiste polecenie aws.

Jakie jest prawidłowe rozwiązanie?

Podsumowanie: mockowanie poleceń i stubowanie zewnętrznych narzędzi

Poznali Państwo kompletny zestaw technik zastępowania rzeczywistych poleceń kontrolowanymi atrapami podczas testowania skryptów Bash.

Omówione podstawowe techniki:

  • Dodawanie katalogu na początku PATH — utwórz katalog STUB_BIN za pomocą mktemp -d, umieść w nim wykonywalne pliki atrap i dodaj ten katalog na początku PATH
  • Kody zakończenia atrap — zwracaj 0 w przypadku powodzenia i wartości różne od zera, aby symulować określone awarie (błędy sieci, odmowę dostępu itp.)
  • Atrapy typu spy — dopisuj argumenty do pliku dziennika wewnątrz atrapy, aby sprawdzić, jak skrypt wywołał zewnętrzne narzędzia
  • Wiele atrap — umieść kilka plików atrap w tym samym katalogu STUB_BIN, aby jednocześnie zamockować cały ekosystem zależności
  • Atrapy będące funkcjami — zdefiniuj funkcję powłoki o tej samej nazwie co polecenie, aby mockować je w tym samym procesie; użyj export -f, aby udostępnić ją potomnym procesom bash
  • Integracja z BATS — używaj hooków setup()/teardown() oraz $BATS_TEST_TMPDIR, aby zapewnić czystą izolację każdego testu

Zawsze używaj trap '...' EXIT, aby zagwarantować usunięcie atrap niezależnie od wyniku testu. Atrapy powinny być minimalne — zwracaj tylko to, co skrypt rzeczywiście parsuje. Atrapy oparte na plikach są najbardziej przenośnym rozwiązaniem dla potoków CI.

Często zadawane pytania

Czy lekcja „Mockowanie poleceń i zastępowanie narzędzi zewnętrznych” jest bezpłatna?

Tak — pełny tekst „Mockowanie poleceń i zastępowanie narzędzi zewnętrznych” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Linux Command Line & Bash Scripting Mastery, przejdź na CoddyKit PRO. Kurs Linux Command Line & Bash Scripting Mastery zawiera 4 lekcji w sumie.

Co nauczysz się w „Mockowanie poleceń i zastępowanie narzędzi zewnętrznych”?

Nadpisuj PATH i definiuj fałszywe pliki wykonywalne, aby testować skrypty bez ingerowania w rzeczywiste systemy. Ćwiczysz Linux Command Line & Bash Scripting Mastery z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Linux Command Line & Bash Scripting Mastery?

Nie wymagamy żadnego doświadczenia. Linux Command Line & Bash Scripting Mastery w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Mockowanie poleceń i zastępowanie narzędzi zewnętrznych”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Linux Command Line & Bash Scripting Mastery?

Tak. Każda lekcja Linux Command Line & Bash Scripting Mastery zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Testowanie jednostkowe funkcji za pomocą Bats-core
  2. Mockowanie poleceń i zastępowanie narzędzi zewnętrznych
  3. Fixtures, środowiska tymczasowe i pokrycie kodu
  4. Uruchamianie testów powłoki w potokach CI
← Powrót do Linux Command Line & Bash Scripting Mastery