محاكاة الأوامر وإنشاء بدائل للأدوات الخارجية
غيّر PATH وعرّف ملفات تنفيذية وهمية لاختبار السكربتات دون لمس الأنظمة الحقيقية
محاكاة الأوامر وإنشاء بدائل للأدوات الخارجية درس مجاني في DevOps Bootcamp على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في DevOps Bootcamp، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة DevOps Bootcamp 4 دروس في المجموع.
لماذا نحاكي الأوامر في اختبارات Bash؟
عند اختبار برنامج Bash يستدعي curl أو aws أو git أو أي أداة خارجية، تواجهون مشكلة: فالاستدعاءات الفعلية تصل إلى الشبكة، أو تعدّل الحالة، أو تكلّف مالًا، أو تفشل ببساطة في بيئة CI التي قد لا تكون هذه الأدوات مثبّتة فيها.
تعني المحاكاة استبدال الأمر الفعلي بآخر زائف تتحكمون فيه. يعيد البديل الزائف (stub) إخراجًا ورموز خروج متوقعة، ولذلك يكون الاختبار سريعًا ومعزولًا وقابلًا لإعادة الإنتاج.
- لا حاجة إلى الوصول إلى الشبكة أو الخدمات السحابية
- تُشغّل الاختبارات خلال أجزاء من الثانية بدلًا من ثوانٍ
- يمكنكم محاكاة أخطاء يصعب إحداثها في الأنظمة الفعلية
- تبقى مسارات CI نظيفة ومن دون تبعيات
يوفّر Bash آلية بسيطة على نحو مفاجئ لتنفيذ ذلك: ما عليكم سوى وضع الملف الثنائي الزائف في موضع يسبق الملف الفعلي في $PATH.
كيف يعمل البحث في PATH
عندما ينفّذ shell أمرًا مثل curl، يبحث في كل مجلدات $PATH من اليسار إلى اليمين، ويشغّل أول تطابق يعثر عليه.
وهذا يعني أنه إذا أضفتم في بداية PATH مجلدًا يحتوي على نص curl خاص بكم، فلن يصل shell أبدًا إلى /usr/bin/curl.
نمط التجاوز:
- أنشئوا مجلدًا مؤقتًا (مجلد stub bin الخاص بكم)
- اكتبوا ملفًا تنفيذيًا زائفًا يحمل الاسم نفسه للأمر الفعلي
- أضيفوا ذلك المجلد في بداية
PATH - شغّلوا البرنامج قيد الاختبار؛ فسيستدعي stub الخاص بكم بدلًا من الملف الثنائي الفعلي
- نظّفوا المجلد المؤقت بعد الاختبار
تعمل هذه الطريقة من دون صلاحيات root، ومن دون تعديل ملفات النظام، ومن دون أي إطار عمل خاص.
إنشاء مجلد stub
يستخدم النمط القياسي mktemp -d لإنشاء مجلد مؤقت معزول للـ stubs. ويحصل كل اختبار أو مجموعة اختبارات على مجلده الخاص، مما يمنع تلوث الاختبارات بعضها ببعض.
بعد اكتمال الاختبار، أزيلوا المجلد باستخدام rm -rf. ويضمن استخدام trap إجراء التنظيف حتى عندما ينتهي الاختبار مبكرًا بسبب خطأ.
#!/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."كتابة stub الأول
الـ stub هو ببساطة ملف تنفيذي يحمل الاسم نفسه للأمر الذي تريدون استبداله. ويطبع أي إخراج يتوقعه البرنامج قيد الاختبار، ثم ينتهي برمز الخروج الذي تختارونه.
القواعد الأساسية للـ stubs:
- يجب أن يكون الملف قابلًا للتنفيذ (
chmod +x) - سطر shebang (
#!/usr/bin/env bash) مطلوب - اطبعوا الإخراج الذي سيحلله برنامجكم الفعلي
- استخدموا
exit 0للنجاح، ورمزًا غير صفري لحالات الفشل المحاكاة
#!/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اختبار برنامج يستدعي curl
لنجمع الآن هذه الأجزاء معًا. افترضوا أن لديكم برنامج نشر يستدعي curl لفحص نقطة نهاية للصحة، ثم ينتهي بخطأ إذا لم تكن الخدمة سليمة. وتريدون اختبار مسار النجاح ومسار الفشل معًا من دون استخدام خادم فعلي.
#!/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"محاكاة حالات فشل الأوامر
من أكثر الاستخدامات قيمة للبدائل الوهمية (stubs) محاكاة حالات الفشل التي يصعب إعادة إنتاجها باستخدام الأدوات الحقيقية، مثل انتهاء مهلة الشبكة، وأخطاء الصلاحيات، وامتلاء القرص، أو إرجاع واجهة API بعيدة للرمز 500.
لمحاكاة الفشل، اجعل البديل الوهمي ينتهي برمز غير صفري. ويمكنكم أيضًا الكتابة إلى stderr بالطريقة نفسها التي يستخدمها الأمر الحقيقي تمامًا، حتى تختبروا معالجة الأخطاء في البرنامج النصي بالكامل.
#!/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تسجيل استدعاءات البديل الوهمي للتحقق
أحيانًا تحتاجون إلى التحقق ليس فقط من ما يخرجه البرنامج النصي، بل أيضًا من كيفية استدعائه لأداة خارجية، مثل الوسائط التي مررها، وعدد مرات استدعائها، أو الترتيب الذي استُدعيت به. يسجل البديل الوهمي المتعقّب (spy stub) استدعاءاته في ملف.
بعد الاختبار، يقرأ إطار الاختبار ملف السجل ويتحقق من محتوياته. ويمنحكم ذلك إمكانية التحقق على مستوى الوسائط من دون أي إطار عمل خاص.
#!/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"إنشاء بدائل وهمية لعدة أوامر دفعة واحدة
غالبًا ما يستدعي البرنامج النصي الحقيقي عدة أدوات خارجية. ويمكنكم إنشاء بدائل وهمية لها جميعًا في دليل STUB_BIN نفسه. يكون كل ملف بديل وهمي مستقلًا، ويمكنه إرجاع مخرجات ورموز خروج مختلفة.
احرصوا على إبقاء البدائل الوهمية بسيطة: أعيدوا فقط ما يحلله البرنامج النصي قيد الاختبار فعليًا. لا تحاولوا محاكاة كل الخيارات، بل مجموعة الخيارات التي يستخدمها البرنامج النصي فقط.
#!/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"استخدام الدوال كبدائل وهمية (من دون الحاجة إلى ملفات)
في الحالات البسيطة، لا تحتاجون إلى كتابة ملفات على الإطلاق. يمكنكم تعريف دالة shell بالاسم نفسه للأمر. وبما أن الدوال يجري حلّها قبل البحث في PATH عن الأوامر الخارجية، فإنها تحظى بالأولوية تلقائيًا.
هذه أسرع طريقة لاختبار البرامج النصية المحمّلة بالمصدر. لكن بدائل الدوال الوهمية لا تعمل إلا ضمن عملية shell نفسها، ولن تكون مرئية للعمليات الفرعية التي تُنشأ باستخدام bash -c صراحةً أو للعمليات التي تعمل في الخلفية. استخدموا النهج القائم على الملفات في هذه الحالات.
#!/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"تصدير الدوال إلى العمليات الفرعية
عندما ينشئ البرنامج النصي قيد الاختبار عملية فرعية، مثل bash script.sh أو خط أنابيب، فإن بدائل الدوال الوهمية المعرّفة في العملية الأصلية لا تُورث افتراضيًا. لديكم خياران:
- استخدموا
export -f function_nameلتصدير الدالة، فتصبح متاحة لعملياتbashالتابعة - أو عودوا إلى البدائل الوهمية القائمة على الملفات في دليل
STUB_BIN، فهي تعمل دائمًا عبر حدود العمليات
يتميز export -f بالأناقة، لكنه يعمل فقط مع bash (وليس مع sh أو غيره من أنظمة shell). فضّلوا البدائل الوهمية القائمة على الملفات في بيئات CI متعددة اللغات.
#!/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)دمج البدائل الوهمية مع إطار اختبار (BATS)
عند استخدام BATS (نظام الاختبار الآلي لـ Bash)، يجب إعداد البدائل الوهمية في الخطاف setup() وتنفيذ التنظيف في teardown(). يعيد BATS ضبط البيئة بين الاختبارات، ولذلك يحصل كل اختبار على دليل بدائل وهمية جديد.
توفر متغيرات BATS مثل $BATS_TEST_TMPDIR دليلًا مؤقتًا خاصًا بكل اختبار تلقائيًا، فاستخدموه بدلًا من mktemp -d للحصول على شيفرة أنظف.
#!/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 ]
}اختبار المعرفة: محاكاة الأوامر في Bash
اختبروا مدى فهمكم لمحاكاة الأوامر وأنماط البدائل الوهمية في Bash.
يكتب مطوّر اختبارًا يعرّف دالة shell باسم aws لتحل محل AWS CLI الحقيقي. يعمل الاختبار جيدًا مباشرةً في الطرفية، لكن عندما يشغّل خط أنابيب CI البرنامج النصي قيد الاختبار باستخدام bash deploy.sh، يجري تجاهل البديل الوهمي واستدعاء الأمر الحقيقي aws.
ما الإصلاح الصحيح؟
مراجعة: محاكاة الأوامر وإنشاء بدائل وهمية للأدوات الخارجية
لقد تعلمتم مجموعة الأدوات الكاملة لاستبدال الأوامر الحقيقية ببدائل وهمية يمكن التحكم فيها أثناء اختبار البرامج النصية في Bash.
التقنيات الأساسية التي تناولناها:
- إضافة دليل إلى بداية PATH — أنشئوا دليل
STUB_BINباستخدامmktemp -d، واكتبوا فيه ملفات بدائل وهمية قابلة للتنفيذ، ثم أضيفوا الدليل إلى بدايةPATH - رموز خروج البدائل الوهمية — أعيدوا
0عند النجاح، وقيمًا غير صفرية لمحاكاة حالات فشل محددة (أخطاء الشبكة، ورفض الإذن، وغير ذلك) - البدائل الوهمية المتعقّبة — أضيفوا الوسائط إلى ملف سجل داخل البديل الوهمي للتحقق من كيفية استدعاء البرنامج النصي للأدوات الخارجية
- البدائل الوهمية المتعددة — ضعوا عدة ملفات بدائل وهمية في
STUB_BINنفسه لمحاكاة منظومة كاملة من التبعيات دفعة واحدة - بدائل الدوال الوهمية — عرّفوا دالة shell بالاسم نفسه للأمر لمحاكاته ضمن العملية نفسها؛ واستخدموا
export -fللوصول إلى عمليات bash التابعة - دمج BATS — استخدموا خطافي
setup()/teardown()والمتغير$BATS_TEST_TMPDIRلعزل كل اختبار بصورة نظيفة
استخدموا دائمًا trap '...' EXIT لضمان تنظيف البدائل الوهمية مهما كانت نتيجة الاختبار. أبقوا البدائل الوهمية بسيطة، وأعيدوا فقط ما يحلله البرنامج النصي فعليًا. وتُعد البدائل الوهمية القائمة على الملفات الخيار الأكثر قابلية للنقل في خطوط أنابيب CI.
الأسئلة الشائعة
هل درس «محاكاة الأوامر وإنشاء بدائل للأدوات الخارجية» مجاني؟
نعم — نص درس «محاكاة الأوامر وإنشاء بدائل للأدوات الخارجية» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة DevOps Bootcamp، انتقل إلى CoddyKit PRO. تتضمن دورة DevOps Bootcamp 4 دروس في المجموع.
ماذا ستتعلم في «محاكاة الأوامر وإنشاء بدائل للأدوات الخارجية»؟
غيّر PATH وعرّف ملفات تنفيذية وهمية لاختبار السكربتات دون لمس الأنظمة الحقيقية تتمرن على DevOps Bootcamp مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ DevOps Bootcamp؟
لا تُشترط خبرة سابقة. DevOps Bootcamp على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.
كم من الوقت يستغرق درس «محاكاة الأوامر وإنشاء بدائل للأدوات الخارجية»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس DevOps Bootcamp هذا؟
نعم. كل درس في DevOps Bootcamp يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- اختبار الدوال بوحدة باستخدام Bats-core
- محاكاة الأوامر وإنشاء بدائل للأدوات الخارجية
- بيانات الاختبار والبيئات المؤقتة وتغطية الاختبارات
- تشغيل اختبارات Shell في مسارات CI