Free

تشغيل الاختبارات تلقائياً بالخطّافات: Claude يعرف فوراً إذا كسر شيئاً

استخدم PostToolUse Hooks على أدوات Write/Edit لتشغيل الاختبارات بعد كل تغيير — النتائج الفاشلة تُعاد إلى Claude ليصلحها في الحال.


Claude Code يعدّل الكود بسرعة — لكنه لا يُشغّل الاختبارات من تلقاء نفسه. افتراضياً، بمجرد كتابة ملف يعتبر المهمة منتهية. دون تعليمات صريحة، لن يعرف إذا كان التغيير قد كسر شيئاً.

خطّاف PostToolUse يحل هذه المشكلة: في كل مرة يكتب فيها Claude ملفاً، تعمل الاختبارات تلقائياً. عند الفشل، تُعاد النتيجة مباشرةً إلى Claude ليصلحها في الحال.


كيف يعمل

يعدّل Claude Code الملفات بأداتين: Write (إنشاء أو استبدال) وEdit (تغييرات جزئية). ربط خطّاف بمرحلة PostToolUse لهاتين الأداتين يُطلق تشغيل الاختبارات في كل مرة يُحفظ فيها ملف على القرص.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/run-tests.sh"
          }
        ]
      }
    ]
  }
}

يستقبل الخطّاف JSON كامل لاستدعاء الأداة عبر stdin، بما فيه مسار الملف المعدَّل. استخدام هذا المسار لتحديد الاختبارات التي ستُشغَّل هو مفتاح هذا النهج.

قواعد رمز الخروج (PostToolUse):
- exit 0: الاختبارات نجحت — يكمل Claude دون انقطاع
- exit 2: الاختبارات فشلت — محتوى stderr يُحقن إلى Claude الذي يرى الخطأ ويحاول التصحيح


الإصدار الأساسي: اختيار أمر الاختبار حسب نوع الملف

أبسط نهج: فحص امتداد الملف وتشغيل مجموعة الاختبارات المناسبة.

#!/bin/bash
input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // empty')

[[ -z "$file" ]] && exit 0

# لا تُطلق الاختبارات عند تعديل ملف الاختبار نفسه
echo "$file" | grep -qE '(test|spec)\.' && exit 0

case "$file" in
  *.py)
    cd "$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
    result=$(python -m pytest --tb=short -q 2>&1)
    ;;
  *.ts|*.js|*.tsx|*.jsx)
    cd "$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
    result=$(npm test -- --passWithNoTests 2>&1)
    ;;
  *.go)
    dir=$(dirname "$file")
    result=$(go test "./$dir/..." 2>&1)
    ;;
  *.rb)
    cd "$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
    result=$(bundle exec rspec --format progress 2>&1)
    ;;
  *)
    exit 0
    ;;
esac

exit_code=$?

if [[ $exit_code -ne 0 ]]; then
  echo "فشلت الاختبارات (الملف المعدَّل: $file):" >&2
  echo "$result" >&2
  exit 2
fi

الإصدار المتقدم: استهداف ملف الاختبار المحدد

تشغيل المجموعة كاملة في كل تغيير بطيء جداً. استخدم مسار الملف المعدَّل لإيجاد الاختبارات ذات الصلة فقط.

في مشروع Python، يقابل src/auth/login.py عادةً tests/auth/test_login.py:

#!/bin/bash
input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // empty')

[[ -z "$file" || "$file" != *.py ]] && exit 0
echo "$file" | grep -qE '(test_|_test\.py)' && exit 0

root=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
cd "$root"

basename=$(basename "$file" .py)
test_file=$(find tests -name "test_${basename}.py" 2>/dev/null | head -1)

if [[ -n "$test_file" ]]; then
  result=$(python -m pytest "$test_file" --tb=short -q 2>&1)
else
  result=$(python -m pytest --tb=short -q 2>&1)
fi

if [[ $? -ne 0 ]]; then
  echo "فشلت الاختبارات:" >&2
  echo "$result" >&2
  exit 2
fi

لمشاريع JavaScript/TypeScript، يدعم Jest التصفية بنمط اسم الملف:

#!/bin/bash
input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // empty')

[[ -z "$file" ]] && exit 0
echo "$file" | grep -qE '\.(test|spec)\.' && exit 0
echo "$file" | grep -qE '\.(ts|tsx|js|jsx)$' || exit 0

root=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
cd "$root"

basename=$(basename "$file" | sed 's/\.[^.]*$//')
result=$(npx jest --testPathPattern="$basename" --passWithNoTests 2>&1)

if [[ $? -ne 0 ]]; then
  echo "فشلت الاختبارات:" >&2
  echo "$result" >&2
  exit 2
fi

الإعداد الكامل

تغطية Write وEdit مع حماية بمهلة زمنية لمنع الاختبارات المعلّقة من تجميد Claude:

.claude/hooks/run-tests.sh:

#!/bin/bash
set -euo pipefail

input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // empty')

[[ -z "$file" ]] && exit 0

# تخطي ملفات الاختبار نفسها والملفات غير البرمجية
echo "$file" | grep -qE '(test_|_test\.|\.test\.|\.spec\.)' && exit 0
echo "$file" | grep -qE '\.(py|ts|js|tsx|jsx|go|rb)$' || exit 0

root=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
cd "$root"

run_test() {
  timeout 60 "$@" 2>&1
}

case "$file" in
  *.py)
    result=$(run_test python -m pytest --tb=short -q)
    ;;
  *.ts|*.tsx|*.js|*.jsx)
    basename=$(basename "$file" | sed 's/\.[^.]*$//')
    result=$(run_test npx jest --testPathPattern="$basename" --passWithNoTests)
    ;;
  *.go)
    dir=$(dirname "$file")
    result=$(run_test go test "./$dir/...")
    ;;
  *.rb)
    result=$(run_test bundle exec rspec --format progress)
    ;;
  *)
    exit 0
    ;;
esac

if [[ $? -ne 0 ]]; then
  echo "⚠ فشلت الاختبارات (الملف المحرِّك: $(basename "$file"))" >&2
  echo "" >&2
  echo "$result" >&2
  exit 2
fi

.claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/run-tests.sh"
          }
        ]
      }
    ]
  }
}

ملاحظات مهمة

لا تُطلق الاختبارات عند تعديل ملفات الاختبار

عندما يعدّل Claude ملف اختبار، الاختبار نفسه لم يكتمل بعد. تشغيله الآن سابق لأوانه. هذا السطر التصفوي ضروري:

echo "$file" | grep -qE '(test_|_test\.|\.test\.|\.spec\.)' && exit 0

Write وEdit يستخدمان أسماء حقول مختلفة في tool_input

Write يستخدم file_path، وEdit يستخدم path. التعامل معهما بقيمة احتياطية:

file=$(echo "$input" | jq -r '.tool_input.file_path // .tool_input.path // empty')

التحكم في مهلة الاختبارات

مجموعات الاختبار البطيئة مع تعديلات Claude المتكررة قد تُجمّد الجلسة. timeout 60 قيمة معقولة؛ اضبطها وفق مدة الاختبار الفعلية للمشروع.

مستوى المشروع، لا العالمي

هذا الخطّاف ينتمي إلى إعداد مستوى المشروع (.claude/settings.json). تتفاوت أوامر الاختبار كثيراً بين المشاريع. للإصدار العالمي، يحتاج البرنامج النصي إلى اكتشاف نوع المشروع ديناميكياً.


النتيجة

مع هذا الإعداد، يصبح إيقاع عمل Claude:

  1. تعديل ملف مصدري
  2. الخطّاف يُشغّل الاختبارات المرتبطة تلقائياً
  3. فشل الاختبارات ← Claude يرى مخرجات الخطأ ويواصل التعديل حتى تنجح
  4. نجاح الاختبارات ← صمت، Claude ينتقل إلى الخطوة التالية

لا حاجة للتكرار المستمر "شغّل الاختبارات". لكل تغيير يُجريه Claude شبكة أمان من الاختبارات.