EduFlow
اختبار تطبيقات JavaScript
⁦0%⁩

الوحدة 5 · استراتيجية الاختبار

الاختبار في CI واختيار ما لا تختبره

18 دقراءة 3 داختر بحكمة

الدرس في لمحة

تشغّل مجموعة اختبارات متعدّدة الطبقات في CI باستخدام GitHub Actions كي يحصل كل pull request على تغذية راجعة سريعة وموثوقة، وتقرّر أيّ الاختبارات تتخطّاها أو تحذفها لأن تكلفتها أكبر مما تلتقطه.

  1. 1pipeline من الأسرع إلى الأبطأ
  2. 2اختيار ما لا تختبره
  3. 3الصورة الكاملة

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

الاختبارات على حاسوبك تحميك أنت. أما الاختبارات في CI (التكامل المستمر) فتحمي الفريق كله، مع كل pull request، سواء تذكّر أحدهم تشغيلها أم لا. يجمع هذا الدرس الأخير القطع معًا: pipeline يشغّل الطبقات التي بنيتها بالترتيب الصحيح، وحسن التقدير في ما يستحق اختبارًا من الأساس.

pipeline من الأسرع إلى الأبطأ

الترتيب مهم بقدر المحتوى. الفحوص الرخيصة التي تفشل كثيرًا تأتي أولًا، فيكلّفك الخطأ المطبعي 30 ثانية من الانتظار بدل 12 دقيقة:

أربع مراحل من اليسار إلى اليمين: الـ lint والأنواع في ثوانٍ، واختبارات unit وintegration في دقيقة تقريبًا، والبناء، ثم اختبارات end-to-end موزّعة في بضع دقائق. كل مرحلة لا تعمل إلا إن نجحت التي قبلها. Lint + أنواع ~30 s Vitest ~1 min البناء ~30 s Playwright 3 shards، ~4 min الفشل يوقف التشغيل مبكرًا
pipeline لـ pull request، الفحوص الأرخص أولًا

وإليك ذلك على شكل workflow في GitHub Actions:

.github/workflows/test.yml
name: Test
on:
  pull_request:
  push:
    branches: [main]

jobs:
  unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: lts/*
          cache: npm
      - run: npm ci
      - run: npm run lint
      - run: npx tsc --noEmit
      - run: npx vitest run --coverage

  e2e:
    needs: unit
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3]
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: lts/*
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test --shard=${{ matrix.shard }}/3
      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: playwright-report-${{ matrix.shard }}
          path: playwright-report/
          retention-days: 14

بضعة تفاصيل تؤدّي عملًا حقيقيًا:

  • needs: unit يعني أن مهمّة الـ end-to-end لا تبدأ إلا حين تنجح اختبارات الوحدات. لا فائدة من تشغيل المتصفّحات لكود يفشل في tsc.
  • --shard=1/3 يقسّم مجموعة Playwright على ثلاثة أجهزة تعمل بالتوازي. (وللحصول على تقرير واحد مدمج، انتقل إلى المُبلّغ blob وشغّل npx playwright merge-reports في مهمّة أخيرة.)
  • if: ${{ !cancelled() }} يرفع التقرير حتى حين تفشل الاختبارات، وهذا بالضبط حين تحتاج إلى الـ traces من درس تتبّع الأخطاء.
  • CI يُضبط تلقائيًا على خوادم GitHub، فتعمل Vitest مرة واحدة بدل المراقبة، ويفشل أي .only شارد، ويبدأ عمل retries: process.env.CI ? 2 : 0 في إعدادات Playwright.

وأخيرًا، في إعدادات المستودع، اجعل هذه المهامّ فحوص حالة إلزامية (required status checks) على main، كي يمنع الـ pipeline الأحمر الـ merge فعلًا. فالـ pipeline الذي لا يُلزَم أحد بالنجاح فيه مجرّد اقتراح.

اختيار ما لا تختبره

كل اختبار يكلّف وقت كتابة، ودقائق CI، وصيانة. وبعضها يكلّف أكثر مما سيلتقطه في أي وقت:

  • الكود التافه: دالة getter تعيد حقلًا، أو ثابت (expect(CURRENCY).toBe('USD') يعيد قول ما في الكود).
  • سلوك أطر العمل والمكتبات: أن useState يحدّث الـ state، أو أن Array.prototype.sort يرتّب، أو أن مكتبة التواريخ لديك تنسّق التواريخ. هذا يختبره القائمون على صيانتها.
  • التفاصيل الداخلية: أيّ دالة مساعدة خاصة استُدعيت، وكم مرة أُعيد render مكوّن ما، والحالة الداخلية. هذه تفشل مع إعادة الهيكلة وتنجح مع الأخطاء.
  • التنسيق: أسماء الأصناف وقيم البكسل في الـ unit tests. وإن كانت الانتكاسات البصرية مهمّة، فاستخدم اختبارات لقطات الشاشة في متصفّح حقيقي لبضع شاشات أساسية.
  • الكود المؤقّت: التجارب والنماذج الأولية التي توشك على حذفها.

وكن مستعدًا للحذف. الاختبار الذي لم يفشل قط لسبب حقيقي خلال عامين، أو الذي ينكسر مع كل إعادة هيكلة، أو المتخطّى بشكل دائم، عبء. وحذفه مساهمة.

الصورة الكاملة

لديك الآن كل الطبقات: الفحوص الساكنة، والـ unit tests التي تثبّت السلوك والحدود، واختبارات الواجهة التي تتصرّف كالمستخدمين، واختبارات الـ end-to-end التي تنتظر بشكل صحيح وتبقى معزولة، والأدوات للحكم عليها: الـ coverage للفجوات، والـ mutation testing للقوة. والسؤال الذي يقف خلف كل ذلك هو نفسه سؤال الدرس الأول: هل سيفشل هذا الاختبار بسبب خطأ قد يصل فعلًا إلى المستخدمين، بسرعة، وبرسالة تقول ما الذي انكسر؟ احتفظ بالاختبارات التي تجيب بنعم، وأصلح التي يمكن أن تجيب بها، واحذف البقية.

يطلب منك التمرين اتخاذ هذه القرارات على قائمة اختبارات تبدو حقيقية.

أهم النقاط

  • رتّب CI من الأسرع إلى الأبطأ (الـ lint والأنواع، ثم اختبارات unit وintegration، ثم end-to-end) كي توقف حالات الفشل الرخيصة التشغيل مبكرًا.
  • اجعل مهامّ الاختبار فحوصًا إلزامية (required checks) على الفرع الرئيسي، وارفع تقارير Playwright كـ artifacts، ووزّع مجموعات الـ end-to-end البطيئة على عدة أجهزة بالـ sharding.
  • أبقِ الـ pipeline كله سريعًا بما يكفي كي ينتظره الناس؛ فالـ pipeline الذي يستغرق 40 دقيقة يُتجاوَز.
  • لا تختبر الكود التافه، ولا سلوك أطر العمل أو المكتبات، ولا التنسيق، ولا التفاصيل الداخلية؛ واحذف الاختبارات التي لا تفشل أبدًا لسبب حقيقي.
  • يستحق الاختبار مكانه حين يفشل بسبب خطأ قد يصل فعلًا إلى المستخدمين، بسرعة، وبرسالة واضحة.

قراءات إضافية

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

يشغّل الـ pipeline لديك Playwright أولًا (12 دقيقة)، ثم الـ lint (20 ثانية). خطأ مطبعي في import يُفشل الـ lint في معظم الـ pull requests المعطوبة. ما الترتيب الأفضل؟

السؤال 2 من 3

أيّ من هذه الاختبارات هو أفضل مرشّح للحذف؟

السؤال 3 من 3

كبرت مجموعة الـ end-to-end حتى صارت تستغرق 30 دقيقة على جهاز واحد. ما الخطوة الأولى الأكثر فاعلية؟

تدريب

اختر بحكمة

تحتفظ به أم تحذفه؟

ورثت مجموعة اختبارات Splitwise-lite. يستغرق CI خمسًا وعشرين دقيقة، والفريق يريده أسرع دون أن يفقد الثقة. اختر كل اختبار ستحذفه (أو لن تكتبه من الأساس). واترك دون اختيار الاختبارات التي تستحق مكانها.

تحتفظ به أم تحذفه؟