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 DevOps Bootcamp 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 DevOps Bootcamp, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs DevOps Bootcamp 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:
- Utwórz katalog tymczasowy (swój katalog plików binarnych ze stubami)
- Zapisz w nim fikcyjny plik wykonywalny o takiej samej nazwie jak rzeczywiste polecenie
- Dodaj ten katalog na początku zmiennej
PATH - Uruchom testowany skrypt — wywoła on stub, a nie rzeczywisty plik binarny
- 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 0w 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/versionTestowanie 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"
fiRejestrowanie 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ówbash - 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_BINza pomocąmktemp -d, umieść w nim wykonywalne pliki atrap i dodaj ten katalog na początkuPATH - Kody zakończenia atrap — zwracaj
0w 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 DevOps Bootcamp, przejdź na CoddyKit PRO. Kurs DevOps Bootcamp 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 DevOps Bootcamp 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ąć DevOps Bootcamp?
Nie wymagamy żadnego doświadczenia. DevOps Bootcamp 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 DevOps Bootcamp?
Tak. Każda lekcja DevOps Bootcamp 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
- Testowanie jednostkowe funkcji za pomocą Bats-core
- Mockowanie poleceń i zastępowanie narzędzi zewnętrznych
- Fixtures, środowiska tymczasowe i pokrycie kodu
- Uruchamianie testów powłoki w potokach CI