أساسيات التكامل المستمر

استراتيجية الاختبار في التكامل المستمر

18 دقيقة الدرس 4 من 28

استراتيجية الاختبار في التكامل المستمر

خط أنابيب CI بلا استراتيجية اختبار متماسكة ليس سوى طريقة آلية لشحن البرمجيات المعطوبة بشكل أسرع. الهدف من الاختبار في CI ليس تشغيل كل فحص ممكن على كل إيداع — بل هو الحصول على أعلى ثقة بأدنى زمن انتظار في كل مرحلة. يعلّمك هذا الدرس كيف تهيكل الفرق الهندسية في كبرى شركات التقنية اختباراتها، وكيف توازيها على نطاق واسع، وكيف تُزيل الاهتزاز الذي يدمر الثقة في خطوط الأنابيب.

هرم الاختبار في خطوط الأنابيب

يعرّف هرم الاختبار ثلاث طبقات حسب السرعة والتكلفة والنطاق. في خط أنابيب CI، يهم الشكل لأن الاختبارات الأبطأ تستهلك وقت المُشغِّل وتؤخر التغذية الراجعة للمطور.

Test Pyramid in CI — Unit, Integration, E2E layers Unit Tests Fast · Cheap · Many (~70%) Integration Tests Moderate · Medium (~20%) E2E / UI Slow · Expensive (~10%) ~30s–2m ~2–10m ~10–40m Pipeline runs layers bottom-up; fail fast at the cheapest layer first.
هرم الاختبار — الاختبارات الوحدوية تشكّل القاعدة (سريعة وكثيرة)، واختبارات E2E في القمة (بطيئة وقليلة).

في خط أنابيب CI، يُترجم الهرم مباشرةً إلى بوابات متدرجة:

  1. المرحلة 1 — اختبارات الوحدة: تُشغَّل على كل إيداع، ويجب أن تنتهي في أقل من دقيقتين. لا شبكة ولا قاعدة بيانات ولا خدمات خارجية. احتل كل شيء بالنماذج الوهمية.
  2. المرحلة 2 — اختبارات التكامل: تعمل مقابل خدمات حقيقية (قاعدة بيانات، وسيط رسائل) تُشغَّل كحاويات مرافقة. الميزانية المقبولة: 5 إلى 10 دقائق.
  3. المرحلة 3 — اختبارات E2E / العقود / الدخان: تعمل فقط عند الدمج في الفرع الرئيسي أو فروع الإصدار. استخدم بيئة مخصصة. ميزانية الوقت: حتى 30 دقيقة.
مبدأ الفشل السريع: إذا فشلت اختبارات الوحدة، يُوقف خط الأنابيب قبل إهدار وقت المُشغِّل في مجموعات التكامل وE2E. الأساس المعطوب لا يجب أن يصل إلى المراحل المكلفة.

موازاة الاختبارات على نطاق واسع

تشغيل اختبارات متخلة في مُشغِّل واحد لمدة 40 دقيقة أمر غير مقبول. على نطاق كبرى الشركات — Google وMeta وUber — تعدّ موازاة الاختبارات مصدر قلق هندسي من الدرجة الأولى. الاستراتيجيات التالية متاحة لكل فريق اليوم.

1. بناءات المصفوفة — تقسيم ملفات الاختبار أو مجموعاتها عبر مُشغِّلين مُعلَنين في مصفوفة. GitHub Actions يجعل هذا أصليًا:

# .github/workflows/ci.yml jobs: unit-tests: runs-on: ubuntu-24.04 strategy: matrix: shard: [1, 2, 3, 4] steps: - uses: actions/checkout@v4 - name: Run shard ${{ matrix.shard }} of 4 run: | pytest tests/unit/ \ --numprocesses=auto \ --splits 4 \ --group ${{ matrix.shard }} \ --store-durations \ --durations-path .test-durations.json

يسجّل علم --store-durations المدة التي استغرقها كل ملف اختبار. في التشغيل التالي، يستخدم pytest-split تلك المدد لموازنة الشظايا بالوقت لا بعدد الملفات، مما يضمن انتهاء كل مُشغِّل في تقريبًا نفس اللحظة.

2. حاويات الخدمة لاختبارات التكامل — تشغيل بنية تحتية حقيقية كحاويات مرافقة في نفس المهمة. لا تستخدم قاعدة بيانات مرحلة مشتركة لـ CI؛ استخدم حاويات مؤقتة تُدمَّر بعد المهمة:

jobs: integration-tests: runs-on: ubuntu-24.04 services: postgres: image: postgres:16-alpine env: POSTGRES_DB: testdb POSTGRES_USER: ci POSTGRES_PASSWORD: ci_secret ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 5s --health-timeout 3s --health-retries 10 redis: image: redis:7-alpine ports: - 6379:6379 steps: - uses: actions/checkout@v4 - name: Run integration suite env: DATABASE_URL: postgresql://ci:ci_secret@localhost:5432/testdb REDIS_URL: redis://localhost:6379 run: pytest tests/integration/ -v --timeout=60
استخدم فحوصات الصحة على حاويات الخدمة. بدون --health-cmd، قد تحاول خطوة ما الاتصال قبل أن تقبل قاعدة البيانات الاتصالات، مما يتسبب في فشل غير حقيقي. الخيار options في المثال أعلاه يجعل Actions تنتظر حتى تكون Postgres جاهزة فعلًا.

إدارة الاختبارات المتذبذبة

الاختبار المتذبذب هو الذي يجتاز ويفشل بشكل غير حتمي على نفس الكود. التذبذب هو أكبر مدمّر للثقة في CI. عندما يتوقف المهندسون عن الاعتقاد بأن البناء الأحمر يعني فشلًا حقيقيًا، يبدؤون في دمج كود معطوب. حدّد مدونة هندسة Google التذبذب باعتباره من أكبر خمسة مصادر لإهدار الإنتاجية عبر جميع الفرق الهندسية.

الأسباب الجذرية (بترتيب التكرار على نطاق واسع):

  • الاعتمادية الزمنيةsleep(1) بدلًا من الاستطلاع عن حالة معينة؛ سباقات الظروف في الكود غير المتزامن.
  • الحالة القابلة للتحول المشتركة — اختبارات تُسرِّب صفوف قاعدة البيانات أو الذاكرة المؤقتة أو المتغيرات العامة للاختبار التالي.
  • الاعتمادية على الشبكة — اختبارات تستدعي واجهات برمجية خارجية حقيقية قد تُقيِّد أو تنتهي مهلتها.
  • الاعتمادية على الترتيب — اختبارات تنجح فقط عند تشغيلها بتسلسل معين.
  • استنفاد الموارد — اختبارات تفشل عندما يكون المُشغِّل تحت ضغط المعالج أو الذاكرة.

استراتيجية الكشف: شغّل كل اختبار من 10 إلى 20 مرة بشكل معزول في بيئة نظيفة. pytest يمتلك إضافة لهذا:

# Detect flakes: run each test 10 times in random order pip install pytest-repeat pytest-randomly pytest tests/ \ --count=10 \ --randomly-seed=last \ -x \ --tb=short 2>&1 | tee flake-report.txt

نمط العزل — لا تحذف اختبارًا متذبذبًا ولا تتركه يعترض خط الأنابيب. ضع علامة عليه، وعزله، وأصلحه بموعد محدد:

# Mark a flaky test with a custom marker import pytest @pytest.mark.flaky(reruns=3, reruns_delay=2) def test_payment_webhook_idempotency(): # This test is flaky due to async webhook delivery timing # Quarantine ticket: INFRA-4821 — owner: @payment-team # Deadline: 2025-08-01 ...

أضف -m "not flaky" إلى استدعاء خط الأنابيب الرئيسي حتى لا تعترض الاختبارات المعزولة عمليات الدمج. شغّل العلامة flaky منفصلةً في مهمة cron ليلية حتى يرى الفريق الفشل دون أن يُعاق:

العزل ليس مقبرة. كل اختبار معزول يجب أن يكون له مالك وتذكرة وموعد نهائي. تُطبّق فرق في Spotify وAirbnb سياسة: الاختبار المتذبذب الذي لم يُصلح خلال دورتين يُحذف. مسار كود غير مختبر أفضل من اختبار تعلّم المهندسون تجاهله.

إعداد التقارير والرؤية

يجب أن تكون نتائج الاختبار قابلة للاستهلاك دون قراءة السجلات الخام. أصدر JUnit XML من كل مُشغِّل اختبار — كل منصة CI كبرى (GitHub Actions، وGitLab CI، وJenkins، وCircleCI) يمكنها تحليله أصليًا وعرض نجاح/فشل كل اختبار مع سجله التاريخي:

# pytest — emit JUnit XML for CI test reporting pytest tests/ \ --junitxml=reports/junit.xml \ --cov=src \ --cov-report=xml:reports/coverage.xml \ --cov-fail-under=80 # In GitHub Actions, upload reports as artifacts and annotations - name: Upload test results uses: actions/upload-artifact@v4 if: always() with: name: test-reports path: reports/

تتبّع معدل تذبذب الاختبار واتجاه متوسط مدة الاختبار وتغيّر التغطية لكل طلب سحب بمرور الوقت. عندما ينمو متوسط وقت الاختبار أكثر من 20% أسبوعيًا، لدى الفريق مشكلة موازاة يجب حلها قبل أن تصبح مشكلة ثقافة.

بوابات التغطية تحمي حدود الجودة الدنيا، لا السقوف. اضبط --cov-fail-under على خط الأساس الحالي مطروحًا منه 2%. طلب السحب الذي يُخفّض التغطية بنسبة 15% يجب أن يفشل. الذي لا يُضيف اختبارات لكود جديد يجب أن يفشل. لكن مطاردة تغطية 100% تحفّز اختبار تفاصيل التنفيذ لا السلوك.

تجميع كل شيء معًا

تبدو استراتيجية اختبار CI الجاهزة للإنتاج لخدمة Python متوسطة الحجم هكذا من البداية للنهاية: تعمل اختبارات الوحدة أولًا على 4 شظايا متوازية (هدف: أقل من 90 ثانية إجمالًا)، وتعمل اختبارات التكامل في مهمة واحدة مع Postgres وRedis كحاويات مرافقة (هدف: أقل من 8 دقائق)، وتعمل اختبارات دخان E2E فقط على الدفع إلى main مقابل بيئة مرحلة (هدف: أقل من 20 دقيقة). الاختبارات المتذبذبة تُعزَل فورًا وتُتابَع في اجتماع مراجعة التذبذب الأسبوعي. التغطية مُقيَّدة عند 78% (خط أساس الفريق). تُنشر جميع التقارير كقطع أثرية JUnit XML وتُعرض في لوحة CI. هذا هو المعيار الذي تعمل به كل فرق جادة — والأساس الذي يجب أن تبني نحوه.

ES
Edrees Salih
منذ ساعة

We are still cooking the magic in the way!