0Pricing
Linux Command Line & Bash Scripting Mastery · درس

اختبار الدوال بوحدة باستخدام Bats-core

نظّم ملفات الاختبار والتأكيدات وإعداد الاختبارات وإنهائها للتحقّق من دوال Bash الفردية

اختبار الدوال بوحدة باستخدام Bats-core درس مجاني في Linux Command Line & Bash Scripting Mastery على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Linux Command Line & Bash Scripting Mastery، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Linux Command Line & Bash Scripting Mastery 4 دروس في المجموع.

ما هو Bats-core ولماذا نستخدمه؟

Bats-core (Bash Automated Testing System) هو إطار العمل الفعلي لاختبار الوحدات في Bash. يتيح لكم كتابة اختبارات منظّمة وقابلة للتكرار لدوال وبرامج shell، بالطريقة نفسها التي تستخدمون بها JUnit مع Java أو pytest مع Python.

  • كل اختبار عبارة عن كتلة @test تتضمن وصفًا واضحًا باللغة الطبيعية.
  • ينجح الاختبار عندما يعيد كل أمر بداخله رمز الخروج 0.
  • يفشل الاختبار عند أول رمز خروج غير صفري أو عند فشل تأكيد.
  • يكون الإخراج متوافقًا مع TAP، ولذلك تتعرّف عليه أنظمة CI مثل GitHub Actions وJenkins وGitLab CI مباشرةً.

ثبّتوه باستخدام مدير الحزم لديكم أو استنسخوا المستودع:

# Install via git (recommended — always latest)
git clone https://github.com/bats-core/bats-core.git
cd bats-core && sudo ./install.sh /usr/local

# Or on macOS with Homebrew
brew install bats-core

# Verify installation
bats --version
# bats 1.x.y

ملف اختبار Bats الأول

يمتلك ملف اختبار Bats الامتداد .bats ويبدأ بـ shebang خاص. أما لبنة البناء الأساسية فهي التوجيه @test متبوعًا بسلسلة وصفية وكتلة من الأوامر.

  • يُخبر shebang #!/usr/bin/env bats shell بكيفية تنفيذ الملف.
  • كل كتلة @test هي حالة اختبار مستقلة.
  • يمكنكم تشغيل ملف واحد باستخدام bats my_tests.bats أو مجلد كامل باستخدام bats test/.

فيما يلي البنية الأساسية لملف اختبار Bats:

#!/usr/bin/env bats
# File: test/hello.bats

@test "echo outputs the expected string" {
  result=$(echo "hello world")
  [ "$result" = "hello world" ]
}

@test "false command causes test to fail" {
  # Uncommenting the next line would make this test fail:
  # false
  true
}

تحميل الدالة قيد الاختبار باستخدام 'load'

في المشاريع الفعلية، توجد دوال Bash في ملفات مكتبات، وليس داخل ملف الاختبار نفسه. يوفّر Bats المساعد load لتحميل ملفات خارجية مصدريةً بالنسبة إلى مجلد ملف الاختبار.

  • يحمّل load '../lib/math.sh' الملف قبل تشغيل كل اختبار.
  • بعد التحميل، تصبح كل الدوال المعرّفة في ذلك الملف متاحة داخل كتل الاختبار لديكم.
  • احرصوا على وضع دوال المكتبة في مجلد lib/ والاختبارات في مجلد test/ للفصل الواضح بينهما.

مثال على تخطيط المشروع والاختبار المقابل:

# Project layout:
# lib/math.sh       <- functions to test
# test/math.bats    <- test file

# lib/math.sh
add() {
  echo $(( $1 + $2 ))
}

divide() {
  if [ "$2" -eq 0 ]; then
    echo "Error: division by zero" >&2
    return 1
  fi
  echo $(( $1 / $2 ))
}

# test/math.bats
#!/usr/bin/env bats

load '../lib/math.sh'

@test "add returns correct sum" {
  result=$(add 3 4)
  [ "$result" = "7" ]
}

التأكيدات الأساسية: run و$status و$output

يُعد الأمر run جوهر الاختبار باستخدام Bats. فبدلًا من تنفيذ الأمر مباشرةً، يؤدي تغليفه بـ run إلى التقاط رمز الخروج والإخراج الخاصين به من دون التسبب في فشل الاختبار فورًا.

  • $status — يحتوي على رمز خروج آخر أمر run.
  • $output — يحتوي على stdout المدمج لآخر أمر run.
  • $lines — مصفوفة يكون كل عنصر فيها سطرًا واحدًا من الإخراج (${lines[0]} و${lines[1]} وغيرهما).

يتيح لكم ذلك إجراء تأكيدات على حالات النجاح والفشل معًا:

#!/usr/bin/env bats

load '../lib/math.sh'

@test "divide 10 by 2 returns 5" {
  run divide 10 2
  [ "$status" -eq 0 ]
  [ "$output" = "5" ]
}

@test "divide by zero returns exit code 1" {
  run divide 10 0
  [ "$status" -eq 1 ]
}

@test "divide by zero prints error message" {
  run divide 10 0
  # $output captures stderr too when redirected inside the function
  [[ "$output" == *"division by zero"* ]]
}

استخدام bats-assert لإجراء تأكيدات واضحة

تعمل تأكيدات [ ] المضمّنة، لكنها تعطي رسائل فشل غير مفيدة. توفّر مكتبة المساعد bats-assert دوال تأكيد واضحة تطبع الخطأ الذي حدث تحديدًا.

  • assert_success — تؤكد أن قيمة $status تساوي 0.
  • assert_failure — تؤكد أن قيمة $status غير صفرية.
  • assert_output — تؤكد أن قيمة $output تساوي السلسلة المحددة.
  • assert_output --partial — تؤكد أن الإخراج يحتوي على السلسلة الفرعية.
  • refute_output --partial — تؤكد أن الإخراج لا يحتوي على السلسلة الفرعية.

ثبّتوا المكتبة باستنساخ bats-core/bats-assert داخل مجلد test/helpers/، ثم حمّلوها:

#!/usr/bin/env bats

# Load bats-assert (cloned into test/helpers/bats-assert)
load 'helpers/bats-assert/load'
load '../lib/math.sh'

@test "add 5 and 3 gives 8" {
  run add 5 3
  assert_success
  assert_output "8"
}

@test "divide by zero fails with descriptive message" {
  run divide 9 0
  assert_failure
  assert_output --partial "division by zero"
}

@test "add does not output an error" {
  run add 1 1
  refute_output --partial "Error"
}

setup وteardown: خطافات دورة حياة الاختبار

يوفّر Bats دالتين خاصتين — setup وteardown — تُشغّلان تلقائيًا قبل كل اختبار وبعده. استخدموهما لإعداد الحالة المشتركة وتنظيفها، حتى يبدأ كل اختبار في بيئة معروفة.

  • تُشغّل setup() قبل كل كتلة @test على حدة.
  • تُشغّل teardown() بعد كل كتلة @test على حدة، حتى إذا فشل الاختبار.
  • من الاستخدامات الشائعة: إنشاء مجلدات مؤقتة، وضبط متغيرات البيئة، وإزالة الملفات المؤقتة بعد الاختبار.
#!/usr/bin/env bats

load '../lib/fileutils.sh'

setup() {
  # Create a fresh temp directory before every test
  TEST_DIR=$(mktemp -d)
  export TEST_DIR
}

teardown() {
  # Always clean up, even on test failure
  rm -rf "$TEST_DIR"
}

@test "write_file creates a file with correct content" {
  run write_file "$TEST_DIR/hello.txt" "hello world"
  assert_success
  [ -f "$TEST_DIR/hello.txt" ]
  [ "$(cat "$TEST_DIR/hello.txt")" = "hello world" ]
}

@test "write_file fails when directory does not exist" {
  run write_file "/nonexistent/dir/file.txt" "data"
  assert_failure
}

setup_file وteardown_file: خطافات على مستوى مجموعة الاختبارات

أحيانًا تحتاجون إلى إعداد موارد مكلفة مرة واحدة لكل ملف، وليس قبل كل اختبار. يوفّر Bats setup_file وteardown_file لهذا الغرض.

  • تُشغّل setup_file() مرة واحدة قبل جميع الاختبارات في الملف.
  • تُشغّل teardown_file() مرة واحدة بعد جميع الاختبارات في الملف.
  • استخدموا BATS_FILE_TMPDIR (المتاح تلقائيًا) لمشاركة البيانات بين setup_file والاختبارات؛ إذ لا تستمر المتغيرات العادية عبر subshells.

حالة الاستخدام المعتادة هي تشغيل خادم وهمي أو بناء ملف ثنائي مرة واحدة، ثم إيقافه في النهاية:

#!/usr/bin/env bats

setup_file() {
  # Build the project binary once for all tests in this file
  make build --silent
  export BINARY="$PWD/bin/myapp"
  echo "Binary built: $BINARY"
}

teardown_file() {
  # Remove the binary after all tests complete
  rm -f "$BINARY"
  echo "Cleaned up binary"
}

setup() {
  # Still runs before each individual test
  TEST_TMP=$(mktemp -d)
}

teardown() {
  rm -rf "$TEST_TMP"
}

@test "myapp --version outputs version string" {
  run "$BINARY" --version
  assert_output --partial "1.0"
}

اختبار الدوال التي تعدّل الملفات

من الأنماط الشائعة جدًا اختبار دوال Bash التي تقرأ من نظام الملفات أو تكتب فيه. تتمثل التقنية الأساسية في استخدام مجلدات مؤقتة (عبر mktemp -d داخل setup) حتى لا تلمس الاختبارات الملفات الفعلية ولا تتداخل مع بعضها.

  • اعملوا دائمًا داخل $TEST_DIR (أو $BATS_TEST_TMPDIR — المتاح تلقائيًا في إصدارات Bats الحديثة).
  • استخدموا مكتبة المساعد bats-file لإجراء تأكيدات واضحة على الملفات، مثل assert_file_exists وassert_file_contains.
  • لا تضعوا مسارات ثابتة مثل /tmp/myfile؛ إذ ستتعارض عمليات تشغيل الاختبارات بالتوازي.
#!/usr/bin/env bats

load 'helpers/bats-assert/load'
load 'helpers/bats-file/load'
load '../lib/fileutils.sh'

setup() {
  TEST_DIR="$BATS_TEST_TMPDIR"
}

# lib/fileutils.sh defines:
# append_line() { echo "$2" >> "$1"; }

@test "append_line adds a line to an existing file" {
  echo "first line" > "$TEST_DIR/log.txt"

  run append_line "$TEST_DIR/log.txt" "second line"
  assert_success

  assert_file_contains "$TEST_DIR/log.txt" "second line"
}

@test "append_line creates file if it does not exist" {
  run append_line "$TEST_DIR/new.txt" "hello"
  assert_success
  assert_file_exists "$TEST_DIR/new.txt"
}

محاكاة الأوامر الخارجية

غالبًا ما تستدعي الدوال برامج خارجية مثل curl أو aws أو git. في اختبارات الوحدات، تريدون اختبار منطقكم أنتم، لا الأمر الخارجي الفعلي. وتتمثل أنظف تقنية للمحاكاة في Bats في تعريف دالة shell بالاسم نفسه للأمر داخل setup؛ إذ تكون لها الأولوية على الملف الثنائي الفعلي.

  • عرّفوا دالة مثل curl() { echo 'mocked response'; return 0; } داخل setup وصدّروها.
  • استخدموا export -f curl حتى تكون الدالة مرئية في subshells التي ينشئها run.
  • يمكنكم أيضًا كتابة المحاكاة في ملف مؤقت موجود على PATH للسيناريوهات الأكثر تعقيدًا.
#!/usr/bin/env bats

load 'helpers/bats-assert/load'
load '../lib/network.sh'

# lib/network.sh defines:
# fetch_status() {
#   local url="$1"
#   local code
#   code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
#   echo "$code"
# }

setup() {
  # Override 'curl' with a mock function
  curl() {
    # Simulate a 200 OK response
    echo "200"
    return 0
  }
  export -f curl
}

@test "fetch_status returns 200 when curl reports 200" {
  run fetch_status "https://example.com"
  assert_success
  assert_output "200"
}

تخطي الاختبارات ووضع الوسوم

لا يمكن دائمًا تشغيل كل اختبار؛ فقد تحتاجون أحيانًا إلى اتصال حقيقي بالشبكة أو أداة محددة أو نظام تشغيل معيّن. يوفّر Bats الأمر skip لتجاوز اختبار بشكل مشروط مع عرض رسالة توضّح السبب، بدلًا من تعليق الاختبار أو تعطيل مجموعة الاختبارات.

  • استدعوا skip "reason" في أي موضع داخل كتلة @test لتخطي ذلك الاختبار.
  • تظهر الاختبارات المتخطاة في الإخراج بالحرف S ولا تُحتسب كاختبارات فاشلة.
  • يدعم Bats 1.5 والإصدارات الأحدث الوسوم: أضيفوا وسمًا إلى الاختبارات باستخدام # bats test_tags=slow,network، ثم صفّوا النتائج باستخدام bats --filter-tags network test/.
#!/usr/bin/env bats

# bats test_tags=network
@test "API returns valid JSON" {
  # Skip if no internet connectivity
  if ! ping -c1 -W1 8.8.8.8 &>/dev/null; then
    skip "No network connection available"
  fi

  run curl -s "https://api.example.com/health"
  assert_success
  assert_output --partial '"status"'
}

# bats test_tags=unit
@test "slug function lowercases and replaces spaces" {
  # Always runs — pure function, no external deps
  slug() { echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' ' '-'; }
  run slug "Hello World"
  assert_output "hello-world"
}

# Run only unit tests:
# bats --filter-tags unit test/

تنظيم مجموعة اختبارات متكاملة

يتبع مشروع Bats المنظّم جيدًا تخطيطًا متوقعًا للمجلدات، مما يسهّل ضم المساهمين الجدد ودمجه مع مسارات CI.

البنية الموصى بها:

  • lib/ — دوال Bash الخاصة بالإنتاج، بمعدل ملف واحد لكل مجال: math.sh وfileutils.sh.
  • test/ — ملف .bats واحد لكل ملف مكتبة: math.bats وfileutils.bats.
  • test/helpers/ — ‏bats-assert وbats-file وbats-support بوصفها وحدات فرعية في git.
  • Makefile — هدف test حتى يتمكن المساهمون من تشغيل make test فقط.

شغّلوا مجموعة الاختبارات كاملةً بأمر واحد:

# Makefile
.PHONY: test
test:
	bats test/

# Run all tests recursively (Bats 1.5+)
# bats --recursive test/

# Run a specific file
# bats test/math.bats

# Run with verbose (TAP) output for CI
# bats --tap test/

# Example directory tree:
# .
# |-- lib/
# |   |-- math.sh
# |   `-- fileutils.sh
# |-- test/
# |   |-- helpers/
# |   |   |-- bats-assert/
# |   |   `-- bats-file/
# |   |-- math.bats
# |   `-- fileutils.bats
# `-- Makefile

اختبروا معارفكم: تأكيدات Bats-core

اختبروا مدى فهمكم لآلية الاختبار الأساسية في Bats-core.

مراجعة: اختبار وحدات Bash باستخدام Bats-core

تعلّمتم في هذا الدرس كيفية تنظيم اختبارات الوحدات وكتابتها لدوال Bash الفردية باستخدام Bats-core. فيما يلي أهم النقاط:

  • بنية ملف الاختبار — استخدموا shebang ‏#!/usr/bin/env bats وكتل @test ذات أسماء وصفية.
  • load — حمّلوا ملفات المكتبة حتى تصبح الدوال متاحة في الاختبارات من دون نسخها ولصقها.
  • run + $status + $output — الثلاثي الأساسي؛ استخدموا run دائمًا لالتقاط النتائج من دون التسبب في فشل الاختبار فورًا.
  • bats-assert — فضّلوا assert_success وassert_failure وassert_output على [ ] الخام للحصول على رسائل فشل سهلة القراءة.
  • setup / teardown — تُشغّلان قبل كل اختبار وبعده؛ أما setup_file / teardown_file فتُشغّلان مرة واحدة لكل ملف.
  • المحاكاة — احجبوا الأوامر الخارجية بدوال shell تحمل الاسم نفسه وصدّروها باستخدام export -f.
  • skip — تجاوزوا الاختبارات التي تعتمد على موارد غير متاحة بشكل مشروط.
  • تخطيط المشروع — افصلوا بين lib/ وtest/ وtest/helpers/ لتسهيل الصيانة والدمج مع CI.

تمنح هذه الأنماط مشاريع Bash لديكم الانضباط نفسه في الاختبار الذي تطبقونه على أي مشروع برمجي حديث.

الأسئلة الشائعة

هل درس «اختبار الدوال بوحدة باستخدام Bats-core» مجاني؟

نعم — نص درس «اختبار الدوال بوحدة باستخدام Bats-core» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Linux Command Line & Bash Scripting Mastery، انتقل إلى CoddyKit PRO. تتضمن دورة Linux Command Line & Bash Scripting Mastery 4 دروس في المجموع.

ماذا ستتعلم في «اختبار الدوال بوحدة باستخدام Bats-core»؟

نظّم ملفات الاختبار والتأكيدات وإعداد الاختبارات وإنهائها للتحقّق من دوال Bash الفردية تتمرن على Linux Command Line & Bash Scripting Mastery مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Linux Command Line & Bash Scripting Mastery؟

لا تُشترط خبرة سابقة. Linux Command Line & Bash Scripting Mastery على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «اختبار الدوال بوحدة باستخدام Bats-core»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Linux Command Line & Bash Scripting Mastery هذا؟

نعم. كل درس في Linux Command Line & Bash Scripting Mastery يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. اختبار الدوال بوحدة باستخدام Bats-core
  2. محاكاة الأوامر وإنشاء بدائل للأدوات الخارجية
  3. بيانات الاختبار والبيئات المؤقتة وتغطية الاختبارات
  4. تشغيل اختبارات Shell في مسارات CI
← العودة إلى Linux Command Line & Bash Scripting Mastery