التفريع وطلبات الدمج

طلب الدمج (Pull Request) اقتراحٌ بدمج إيداعات (Commits) فرعٍ في فرع آخر، وغالباً في main. فبدلاً من دفع (Push) التغيير مباشرةً إلى الشيفرة المشتركة، يفتح المطور طلب دمج: يراجع الزملاء الفروقات (Diff)، وتعمل الاختبارات الآلية عليه بوصفها فحوصاً (Checks)، ولا يُدمج الفرع إلا عندما يتحقق الأمران. وتسمّيه منصة GitLab «Merge Request».

مثال ذلك: يودِع مطورٌ قاعدة خصم مع اختبارات الوحدة الخاصة بها في الفرع feature/CHK-231-bulk-discount، ثم يفتح طلب دمج إلى main. وخلال دقائق تعرض صفحته الأسطر المتغيرة، والمراجعين، والفحوص — البناء، والتدقيق (Lint)، واختبارات الوحدة، واختبارات الدخان للواجهة البرمجية — وكلٌّ منها ناجح أو فاشل.

لماذا يهم

يحدد التفريع متى يلتقي التغيير بعمل بقية الفريق، ومن ثمّ أيّ مزيج من التغييرات يختبره المختبِر فعلاً. والفروع التي تعيش أسابيع تراكم تعارضات (Conflicts) لا تظهر إلا عند الدمج، وغالباً قُبيل الإصدار.

وطلب الدمج آخر نقطة يكون فيها التغيير صغيراً ومعزولاً ورخيص الإصلاح. فقراءته اختبارٌ ساكن (Static Testing)، وفحوصه تشغّل التحليل الساكن واختبارات الانحدار الآلية مع كل دفع؛ لذا فالمختبِر الذي يشارك فيه يساعد على إبقاء العيوب خارج main.

استراتيجيات التفريع: التطوير المعتمد على الجذع وفروع الميزات

استراتيجية التفريع (Branching Strategy) اتفاق الفريق على الفروع الموجودة، ومدة بقائها، وطريقة وصول العمل إلى الفرع الرئيسي، الذي يُسمّى أيضاً الجذع (Trunk) أو الخط الرئيسي (Mainline).

في التطوير المعتمد على الجذع (Trunk-Based Development) يدمج الجميع عملهم في main مرة يومياً على الأقل، مباشرةً أو عبر فروع قصيرة العمر تُدمج خلال يوم تقريباً. وتُدمج الميزات غير المكتملة لكنها تُخفى خلف أعلام الميزات (Feature Flags)، وهي مفاتيح تُبقي الشيفرة الجديدة معطّلة حتى تجهز. وهذا هو النموذج الذي يقوم عليه التكامل المستمر الذي تناوله درس سابق: تبقى التغييرات صغيرة ويبقى main قابلاً للإصدار.

أما مع فروع الميزات (Feature Branches) فلكل قصة أو إصلاح فرعه الخاص، يُنشأ من main ويعود إليه عبر طلب دمج. وكلما قصر عمر الفرع بقي الفريق أقرب إلى التكامل المستمر، الذي يتوقع أن يصل العمل إلى main مرة يومياً على الأقل؛ أما الفروع المفتوحة أسابيع فتبتعد عن main، ويصبح الدمج حدثاً محفوفاً بالمخاطر تلتقي فيه عدة تغييرات غير مختبَرة لأول مرة.

  • فروع الإصدار (Release Branches) مثل release/2.4 تثبّت إصداراً بعينه؛ والإصلاح الذي يُجرى فيها يجب أن يصل إلى main أيضاً، وإلا عاد العيب في الإصدار التالي.
  • النماذج الأثقل مثل GitFlow تضيف فرعاً طويل العمر باسم develop وفروعاً للإصلاحات العاجلة (Hotfix). وفي أي نموذج، اسأل: من أي فرع ومن أي إيداع صُنع هذا البناء؟

طلبات الدمج ومراجعة الشيفرة

يعرض طلب الدمج، ملفاً ملفاً، فروقات ما سيُدخله الفرع المصدر إلى الفرع الهدف، ويجمع حولها كل ما يخص التغيير: الوصف، والتذكرة المرتبطة، والإيداعات، وتعليقات المراجعين على أسطر بعينها، ونتائج الفحوص. وطلب الدمج المسودة (Draft) لم يجهز للمراجعة بعد، لكن فحوصه يمكن أن تعمل منذ الآن.

يستطيع كل مراجع أن يعلّق، أو يوافق (Approve)، أو يطلب تغييرات (Request Changes)؛ ويرد صاحب التغيير بدفع إيداعات جديدة إلى الفرع نفسه، فتعمل الفحوص من جديد. وحين تكتمل الموافقات والفحوص المطلوبة يُدمج الفرع بإيداع دمج (Merge Commit)، أو بإيداع واحد مضغوط (Squash) يجمع كل إيداعاته، أو بإعادة التأسيس (Rebase) التي تعيد تطبيق إيداعاته فوق الفرع الهدف، بحسب إعدادات المستودع.

بمصطلحات ISTQB، مراجعة طلب الدمج اختبارٌ ساكن، وغالباً في صورة مراجعة غير رسمية (Informal Review). ونقطة ضعفها المعروفة ختم الموافقة الشكلي: موافقة تُعطى دون قراءة. وما يجعل المراجعة الحقيقية ممكنة طلباتُ دمج صغيرة ووصفٌ، يُكتب عادةً بصيغة Markdown، يجيب عن أربعة أسئلة:

  • ما الذي تغيّر ولماذا، مع رابط التذكرة.
  • أي الاختبارات أُضيفت أو عُدّلت أو حُذفت، وما الذي فُحص يدوياً.
  • المخاطر: ترحيلات قاعدة البيانات (Migrations)، والإعدادات، وأعلام الميزات، والشيفرة المشتركة.
  • كيف يُجرَّب: بيانات الاختبار، والأعلام، وبيئة معاينة (Preview).

جرّبها: مقارنة النصوص (Diff) جرّبها: منسّق Markdown

الاختبارات بوصفها فحوصاً لطلب الدمج

يمكن لكل دفع إلى طلب الدمج أن يشغّل خط CI، وكل مهمة (Job) تُبلغ عن نتيجتها في فحص حالة (Status Check): قيد الانتظار، أو ناجح، أو فاشل. وتستطيع قواعد المستودع — حماية الفروع (Branch Protection) في GitHub، وفحوص الدمج (Merge Checks) في GitLab وBitbucket — أن تجعل الفحوص والموافقات إلزامية، فتصبح بوابة جودة على الطريق إلى main.

ينتقل التغيير من فرع قصير العمر إلى طلب دمج تعمل فيه الفحوص الإلزامية ومراجعة الشيفرة جنباً إلى جنب، ولا يصل إلى الفرع الرئيسي إلا عندما يتحقق الأمران.
  1. 1إنشاء فرع من mainفرع قصير العمر لكل قصة أو إصلاحfeature/CHK-231-bulk-discount
  2. 2الإيداع والدفعالتغيير مع اختباراته
  3. 3فتح طلب دمجالفروقات مقابل main، والوصف، والتذكرة المرتبطة، والمراجعون
  4. 4الفحوص الإلزاميةالبناء والتدقيق واختبارات الوحدة واختبارات الدخان لـAPI، على التغيير مدموجاً مع main
    في الوقت نفسه
    • 5مراجعة الشيفرةيعلّق المراجعون، ومنهم المختبِر، أو يوافقون أو يطلبون تغييرات

    يتكرر: فشل فحص أو طُلبت تغييرات: ادفع إصلاحاً إلى الفرع نفسه، فتعمل الفحوص من جديد

    بوابة: نجحت كل الفحوص الإلزامية واكتملت الموافقات

  5. 6الدمج في mainإيداع دمج أو ضغط أو إعادة تأسيس، بحسب إعداد المستودع
  6. 7الاختبار على mainعلى بناء يحتوي التغيير المدموج، مع تسجيل إيداعه

توضيحي: الفحوص الإلزامية وعدد الموافقات المطلوبة يحددها كل مستودع.

ينبغي أن تنتهي فحوص طلب الدمج خلال دقائق: البناء، والتدقيق، واختبارات الوحدة والمكوّنات، وبضعة اختبارات دخان للواجهة البرمجية أو من البداية إلى النهاية (End-to-End). أما حزمة الانحدار الكاملة فتعمل عادةً بعد الدمج أو وفق جدول زمني؛ ويتناول درس لاحق هذا التقسيم.

وتهمّ المختبِرَ تفصيلتان. كثيراً ما تعمل الفحوص على طلب الدمج مدموجاً مع الفرع الهدف الحالي (فتشغيل pull_request في GitHub Actions يسحب إيداع دمج مؤقتاً)؛ وإذا تقدّم main فقد تصبح النتيجة قديمة، ولهذا تستخدم بعض الفرق طابور دمج (Merge Queue) يختبر كل طلب دمج مع الطلبات التي تسبقه في الطابور قبل دمجه. والفحص الأخضر لا يثبت إلا ما تتحقق منه الاختبارات: فالاختبار المتخطّى، أو القيمة المتوقعة المعدّلة لتطابق المخرَج الجديد، يبقى أخضر.

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

Pull request #482   feature/CHK-231-bulk-discount -> main
Head commit 9f3c2e1, first run

Required checks
  passed   build                  1m 12s
  passed   lint and type check    48s
  failed   unit tests             211 passed, 1 failed
  passed   API smoke tests        18 passed

Reviews    0 of 2 required approvals

Merging is blocked: 1 required check failed, 2 approvals missing.

مثال تطبيقي: طلب دمج أخضر للمراجعة

القصة ⁦CHK-231⁩: «الطلبات التي يبلغ مجموعها الفرعي ⁦500.00⁩ ريال أو أكثر تحصل على خصم ⁦10%⁩ من المجموع الفرعي». بعد الإصلاح أصبحت الفحوص الإلزامية الأربعة في طلب الدمج ⁦#482⁩ خضراء، ووافق عليه مطور واحد، والمختبِر هو المراجع الثاني.

Pull request #482   feature/CHK-231-bulk-discount -> main
Required checks: 4 of 4 passed     Approvals: 1 of 2

الوصف
  ما الذي تغيّر: خصم 10% عندما يتجاوز المجموع الفرعي 500.00 ريال
  الاختبارات: اختبارات وحدة للقيمتين 600.00 و400.00، واختبارات API دون تغيير
  المخاطر: لا شيء

src/pricing/discount.ts
+  if (subtotal > 500) {
+    return round2(subtotal * 0.9);
+  }

tests/api/checkout.api.test.ts
-  expect(body.total).toBe(540.00);
+  expect(body.total).toBe(486.00);

تعليقات المختبِر في المراجعة

يقرأ المختبِر الوصف والاختبارات والفروقات، ويعلّق على الأسطر المعنية، ويختار «طلب تغييرات» (Request Changes):

  • على discount.ts: القصة تقول «⁦500.00⁩ أو أكثر»، لكن الوصف يقول «يتجاوز» والشيفرة تقول subtotal > 500، فالطلب الذي مجموعه ⁦500.00⁩ بالضبط لا يحصل على خصم. عيبٌ اكتُشف بالقراءة.
  • على اختبارات الوحدة: القيمتان ⁦600.00⁩ و⁦400.00⁩ تنجحان سواء كان العامل > أو >=. اطلب القيمتين الحدّيتين ⁦499.99⁩ (بلا خصم) و⁦500.00⁩ (⁦450.00⁩ بعد الخصم)، كما في درس تحليل القيم الحدّية.
  • على اختبار الواجهة البرمجية: تغيّر الإجمالي المتوقع من ⁦540.00⁩ إلى ⁦486.00⁩، لكن الوصف يقول إن اختبارات API لم تتغير. القيمة تناسب القاعدة الجديدة (⁦540.00⁩ ناقص ⁦10%⁩)، لكن كل قيمة متوقعة تتغير يجب أن تُشرح.
  • على «المخاطر: لا شيء»: الأسعار تمسّ الإجماليات والفواتير والمبالغ المستردة. اسأل: هل يُطبَّق الخصم قبل رسوم التوصيل وضريبة القيمة المضافة أم بعدها؟ وكيف تُقرَّب ⁦10%⁩ من ⁦500.05⁩؟
  • بعد الدمج، اختبر القصة على بناء من main يحتوي التغيير المدموج، وسجّل إيداعه.

جرّبها: تحليل القيم الحدّية جرّبها: مولّد سيناريوهات الانحدار

أخطاء شائعة

  • معاملة طلب الدمج الأخضر كأنه مختبَر: الاختبار المتخطّى أو القيمة المتوقعة المعدّلة يبقيان أخضرين.
  • الموافقة على طلب دمج دون قراءة الفروقات والاختبارات، فتتحول المراجعة إلى ختم موافقة شكلي.
  • اختبار بناءٍ دون معرفة الفرع والإيداع اللذين صُنع منهما، ثم الإبلاغ عن عيب أُصلح بالفعل في main.
  • إعادة تشغيل الفحص الفاشل حتى ينجح بدلاً من معرفة السبب؛ فالنتيجة المتذبذبة (Flaky) قد يكون سببها الاختبار نفسه، أو البيئة، أو عيباً حقيقياً في المنتج يتعلق بالتوقيت.
  • إبقاء فروع الميزات مفتوحة أسابيع، فيجمع الدمجُ عدة تغييرات غير مختبَرة دفعة واحدة قُبيل الإصدار.

من منظور ضمان الجودة

اطلب أن تُضاف مراجعاً لطلبات الدمج الخاصة بالقصص التي ستختبرها. اقرأ الوصف، ثم الاختبارات، ثم الشيفرة: هل يغطي اختبارٌ كلَّ معيار قبول؟ هل الحدود والحالات السلبية موجودة؟ هل هناك اختبار متخطّى أو محذوف أو معدّل؟ هل يطابق الوصف الفروقات؟ واطلب التغييرات للمشكلات الحقيقية فقط، كي تحتفظ مراجعاتك بوزنها.

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

تمرين عملي

راجع طلب دمج وقرّر هل يمكن دمجه

المطلوب

الفحوص الإلزامية هي البناء واختبارات الوحدة واختبارات الدخان للواجهة البرمجية. تقول القصة ⁦CHK-245⁩: «يستطيع المستخدم إلغاء الطلب ما دامت حالته Placed أو Paid. وعند إلغاء طلب في حالة Paid يُردّ المبلغ كاملاً». وقد اجتاز طلب الدمج التالي كل فحوصه الإلزامية.

اكتب تعليقات مراجعتك، وقرّر هل توافق، وبيّن ما الذي ستختبره بعد الدمج.

المادة
Pull request #517   fix/CHK-245-cancel-order -> main
Required checks: 3 of 3 passed

الوصف
  ما الذي تغيّر: السماح بإلغاء طلبات Placed وPaid، واسترداد مبلغ طلبات Paid
  الاختبارات: اختبار وحدة لإلغاء طلب Placed

src/orders/cancel.ts
-  if (order.status !== 'Placed') throw new CancelNotAllowed();
+  if (!['Placed', 'Paid', 'Shipped'].includes(order.status)) {
+    throw new CancelNotAllowed();
+  }
+  if (order.status === 'Paid') refund(order, order.total - order.deliveryFee);

tests/orders/cancel.test.ts
+  it('cancels a Placed order', ...);
-  it('rejects cancelling a Shipped order', ...);
الخطوات
  1. قارن الوصف بالقصة وبالفروقات.
  2. اكتب ما تتطلبه القصة ولا تغطيه الاختبارات.
  3. افحص كل اختبار عُدّل أو حُذف.
  4. قرّر، واكتب تعليقاتك، ودوّن ما ستختبره على main.
إظهار الإجابة النموذجية
الإجابة النموذجية

اطلب تغييرات: الفحوص خضراء فقط لأن الاختبارات لا تغطي ما تغيّر. والوصف يطابق القصة لا الفروقات: فهو لا يذكر Shipped، ولا الاختبار المحذوف، ولا رسوم التوصيل المستبعدة من المبلغ المسترد.

العيب الأول: أُضيفت الحالة Shipped إلى الحالات المسموح بها والقصة لا تسمح بها، وحُذف الاختبار rejects cancelling a Shipped order في طلب الدمج نفسه، ولهذا ما زالت الفحوص ناجحة. ولأن الاسترداد لا يجري إلا في حالة Paid، فالطلب الملغى في حالة Shipped لن يُردّ مبلغه إطلاقاً. اطلب إزالة Shipped وإعادة الاختبار.

العيب الثاني: المبلغ المسترد هو order.total - order.deliveryFee لا المبلغ كاملاً. اسأل مالك المنتج هل رسوم التوصيل قابلة للاسترداد، ثم أصلح الشيفرة أو القصة.

الاختبارات الناقصة: إلغاء طلب في حالة Paid ومقدار المبلغ المسترد؛ ورفض الإلغاء في حالة Shipped وأي حالة لاحقة (لا ينتقل إلى Cancelled إلا Placed وPaid)؛ وإلغاء طلب ملغى أصلاً.

بعد الدمج، على بناء من main يحتوي هذا التغيير: ألغِ طلباً في حالة Placed وآخر في حالة Paid، وتحقق من المبلغ المسترد، وجرّب طلباً في حالة Shipped، وسجّل الإيداع ورقم طلب الدمج ⁦#517⁩.

أسئلة شائعة

هل طلب الدمج ميزة من ميزات Git؟

لا. يوفر Git الفروع والإيداعات والدمج، أما طلب الدمج فميزة في منصات الاستضافة المبنية على Git، مثل GitHub وGitLab (حيث يُسمّى Merge Request) وBitbucket وAzure DevOps. وفي Git نفسه أمر git request-pull، لكنه لا يفعل أكثر من طباعة طلب نصي يلخّص التغييرات ويذكر من أين تُسحب، ويُرسل عادةً بالبريد الإلكتروني.

هل يوافق المختبِر على طلبات الدمج؟

إذا أضاف الفريق المختبِرين مراجعين، فنعم، فيما تستطيع الحكم عليه: مدى تغطية الاختبارات لمعايير القبول، وجودة الاختبارات، وقابلية الاختبار، والمخاطر. ولست مضطراً إلى الحكم على كل سطر من شيفرة الإنتاج. اذكر في مراجعتك ما الذي فحصته، كي لا تُفهم موافقتك على أنها أكثر مما هي.

أيهما أفضل للاختبار: التطوير المعتمد على الجذع أم فروع الميزات؟

كلاهما ينجح حين تكون التغييرات صغيرة وتُدمج خلال يوم تقريباً، ويُختبر كل دمج آلياً. والتطوير المعتمد على الجذع يعتمد على فحوص آلية قوية وعلى أعلام الميزات، فيختبر المختبِرون الميزات والعلم مفعّل ومعطّل. والفروع طويلة العمر هي الحالة الخطرة في النموذجين، لأن التكامل واختباره يأتيان متأخرين.

المراجع

  • About pull requestsGitHubما طلب السحب (Pull Request): اقتراح لدمج تغييرات من فرع في آخر، مع مساحة للنقاش والمراجعة وتشغيل الفحوص قبل الدمج.
  • Pro Git, 2nd Edition (Scott Chacon and Ben Straub)Apress · 2014كتاب Pro Git المتاح للقراءة مجاناً على الإنترنت: أساسيات Git، والتفريع والدمج، وسير العمل الموزّع، وكيف يخزّن Git بياناته.
  • Continuous Integration (Martin Fowler)martinfowler.com · 2024الوصف الأكثر استشهاداً للتكامل المستمر: ادمج كثيراً، واجعل البناء يختبر نفسه وسريعاً، وأصلح البناء المكسور فوراً.
  • Code Review Developer GuideGoogle (eng-practices)ممارسات Google المنشورة لمراجعة الكود: ما الذي يبحث عنه المراجعون، ومعيار الموافقة على التغيير، وكيفية كتابة تعليقات المراجعة.

تدرّب بأدوات متقن

نفّذ تحليلاً كاملاً للقيم الحدّية (ثنائي القيم وثلاثي القيم) مع فئات التكافؤ والنتائج المتوقّعة.

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

توحيد تنسيق Markdown (العناوين والقوائم والجداول والتأكيد) ومعاينة النتيجة المعروضة.

التنسيق

للتعمّق أكثر

تقدّمك في هذا الموضوع

جزء من مسار تعلم ضمان الجودة