التطوير الموجَّه بالسلوك وGherkin

التطوير الموجَّه بالسلوك (Behaviour-Driven Development) طريقة عمل يتفق فيها منظور الأعمال ومنظور التطوير ومنظور الاختبار على الطريقة التي ينبغي أن تتصرف بها الميزة، عبر مناقشة أمثلة محددة، ثم تُكتب الأمثلة المتفق عليها بلغة نصية منظَّمة تُسمّى Gherkin، ثم تُؤتمت لتتحقق من النظام. ويصف منهج ISTQB للمستوى التأسيسي هذا التطوير بأنه يعبّر عن السلوك المطلوب للتطبيق بحالات اختبار مكتوبة بصيغة بسيطة من اللغة الطبيعية يفهمها أصحاب المصلحة، غالباً بصيغة Given/When/Then، ثم تُترجم حالات الاختبار هذه آلياً إلى اختبارات قابلة للتنفيذ.

خذ مثلاً رمز خصم عند إتمام الشراء. في محادثة قصيرة يتفق الفريق على القاعدة «الرمز SAVE10 يخصم ⁦10%⁩ من مجموع فرعي قدره ⁦200.00⁩ ريال فأكثر» وعلى مثالين: مجموع فرعي قدره ⁦200.00⁩ ريال يدفع صاحبه ⁦180.00⁩، ومجموع قدره ⁦199.99⁩ ريال يبقى سعره كما هو. يصبح كل مثال سيناريو Gherkin في ملف ميزة (Feature File)، وتربط تعريفات الخطوات (Step Definitions) كل سطر في السيناريو بشيفرة الأتمتة. فيصير النص نفسه المواصفةَ التي وافقت عليها جهة الأعمال، واختباراً يعمل مع كل بناء، وتوثيقاً للقاعدة.

لماذا يهم

كثير من العيوب المكلفة في ميزات الأعمال ليست زلات برمجة بل سوء فهم: قرأ المطور «⁦200.00⁩ فأكثر» على أنها «أكثر من ⁦200.00⁩»، أو لم يقرر أحد ما الذي يحدث لرمز منتهي الصلاحية. ويعالج هذا التطوير تلك الكلفة من منبعها: فالحوار حول أمثلة محددة قبل كتابة أي شيفرة يكشف القراءات المختلفة والقواعد الناقصة وهي ما زالت رخيصة الإصلاح، ولذلك فهو من أبكر صور الاختبار الساكن (Static Testing) لدى الفريق.

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

يبدأ BDD بحوار: «الأصدقاء الثلاثة»

تصف Cucumber، وهي من أشهر أدوات BDD، العمل اليومي بهذا الأسلوب بثلاث ممارسات تتكرر مع كل تغيير صغير. الاكتشاف (Discovery): يناقش الفريق أمثلة محددة للسلوك الجديد ويتفق على تفاصيله. والصياغة (Formulation): تُكتب الأمثلة المتفق عليها سيناريوهاتِ Gherkin، ويتحقق من اتفقوا عليها من أن الصياغة ما زالت تقول ما قصدوه. والأتمتة (Automation): تُربط السيناريوهات بالشيفرة لتعمل على النظام، ويُفضَّل أن يحدث ذلك قبل بناء الميزة ليوجّه التطوير. باختصار: ما يمكن أن يفعله النظام، وما ينبغي أن يفعله، وما يفعله فعلاً.

يحدث الاكتشاف عادةً في اجتماع قصير لكل قصة أثناء تحسين قائمة العمل (Backlog Refinement)، قبل بدء التطوير. وتسميه فرق كثيرة «الأصدقاء الثلاثة» (Three Amigos) لأنه يجمع ثلاثة منظورات: الأعمال (مالك المنتج أو محلل الأعمال، صاحب المشكلة والقواعد)، والتطوير (من يعرف كيف يمكن البناء وما الصعب تقنياً)، والاختبار (من يسأل ما الذي قد يسوء وأي الأمثلة تُظهر ذلك). والمهم هو المنظورات الثلاثة لا عدد الحاضرين، فقد ينضم مصمم أو مطور ثانٍ.

ومن الطرق الشائعة لإدارة هذا الاجتماع رسم خريطة الأمثلة (Example Mapping) الموصوف في توثيق Cucumber عن BDD. تُبنى الخريطة على الطاولة ببطاقات ملونة: بطاقة صفراء للقصة، وبطاقة زرقاء لكل قاعدة، وبطاقات خضراء تحت كل قاعدة للأمثلة التي توضّحها، وبطاقات حمراء للأسئلة التي لا يستطيع أحد في الغرفة الإجابة عنها. وتكشف الخريطة جاهزية القصة بنظرة واحدة: كثرة البطاقات الحمراء تعني أن القصة غير جاهزة للبناء، وكثرة البطاقات الزرقاء تعني غالباً أنها كبيرة ويجب تقسيمها. وهذه خريطة القصة SHOP-31 «تطبيق رمز خصم عند إتمام الشراء».

  • القصة (صفراء): SHOP-31، تطبيق رمز خصم عند إتمام الشراء.
  • القاعدة 1 (زرقاء): الرمز SAVE10 يخصم ⁦10%⁩ من مجموع فرعي قدره ⁦200.00⁩ ريال فأكثر. الأمثلة (خضراء): ⁦250.00⁩ ← يدفع ⁦225.00⁩؛ ⁦200.00⁩ ← يدفع ⁦180.00⁩؛ ⁦199.99⁩ ← يُرفض الرمز ويدفع ⁦199.99⁩.
  • القاعدة 2 (زرقاء): الرمز المنتهي الصلاحية يُرفض. مثال (أخضر): انتهى الرمز في ⁦31⁩ مارس ⁦2027⁩ وطُبّق في ⁦1⁩ أبريل ⁦2027⁩، فتظهر الرسالة «انتهت صلاحية هذا الرمز».
  • القاعدة 3 (زرقاء): رمز واحد لكل طلب. مثال (أخضر): طُبّق SAVE10 ثم أُدخل WELCOME5؛ والنتيجة معلّقة على بطاقة حمراء.
  • الأسئلة (حمراء): هل يُحسب الحد الأدنى قبل رسوم التوصيل أم بعدها؟ هل يحلّ الرمز الثاني محل الأول أم يُرفض؟ ماذا يحدث إذا حذف العميل منتجات فنزل المجموع الفرعي تحت ⁦200.00⁩ بعد تطبيق الرمز؟

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

صياغة Gherkin: أجزاء ملف الميزة

لغة Gherkin هي الصيغة النصية التي تقرؤها أدوات BDD. تعيش السيناريوهات في ملفات نصية امتدادها .feature، ويبدأ كل سطر غير فارغ بكلمة مفتاحية، باستثناء الأوصاف الحرة. وأنت تعرف كلمات الخطوات Given وWhen وThen وAnd وBut، وScenario Outline مع جدول Examples الخاص به، من درس معايير القبول. أما ملف الميزة الكامل فيضيف حولها بنية.

بعد أن أجاب مالك المنتج عن البطاقة الحمراء الأولى (الحد الأدنى يُطبَّق على المجموع الفرعي قبل رسوم التوصيل)، صاغ الفريق القاعدتين 1 و2 من القصة SHOP-31 في الملف أدناه. فصارت كل بطاقة زرقاء Rule وكل بطاقة خضراء Scenario. ويوصي مرجع Gherkin بثلاث إلى خمس خطوات لكل سيناريو؛ فإذا زادت لم يعد المثال يُقرأ مواصفةً.

  • Feature: واحدة لكل ملف. اسمها والوصف الحر تحتها يبيّنان الغرض من الميزة، والوصف لا يُنفَّذ.
  • Rule: تجمع السيناريوهات التي توضّح قاعدة عمل واحدة، كالبطاقة الزرقاء مع بطاقاتها الخضراء.
  • Scenario أو مرادفها Example: مثال محدد واحد لسلوك واحد.
  • Background: خطوات Given مشتركة بين كل سيناريوهات الملف أو القاعدة. تعمل قبل كل سيناريو، بعد أي خطافات (Hooks) من نوع Before، وهي خطافات التهيئة في Cucumber، مثل beforeEach في المشغّلات الأخرى. اجعلها قصيرة، ولا تضع فيها إلا السياق الذي يحتاجه القارئ لفهم السيناريوهات.
  • جدول البيانات (Data Table): صفوف بين علامات | تحت خطوة، تُمرَّر إلى شيفرة تلك الخطوة وسيطاً واحداً. والنص متعدد الأسطر (Doc String): نص بين سطرين من """، لنص بريد إلكتروني أو حمولة JSON.
  • الوسوم (Tags) مثل @smoke أو @SHOP-31 تُعلِّم ميزة أو قاعدة أو سيناريو أو جدول أمثلة لتختار ما يُشغَّل. والوسوم تُورَّث: الوسم على الميزة ينطبق على كل سيناريو فيها.
  • تبدأ التعليقات بالعلامة # في أول السطر. وتحوّل ترويسة # language: في السطر الأول الكلماتِ المفتاحية إلى لغة منطوقة أخرى.
# القصة SHOP-31. القواعد متفق عليها في جلسة خريطة الأمثلة.
@checkout
Feature: رموز الخصم عند إتمام الشراء
  يستطيع العميل إدخال رمز خصم واحد لكل طلب.
  تُحسب نسبة الخصم من المجموع الفرعي قبل رسوم التوصيل.

  Background:
    Given رموز الخصم التالية موجودة:
      | code   | percent | min_subtotal | valid_until |
      | SAVE10 | 10      | 200.00       | 2027-03-31  |

  Rule: لا يُطبَّق الرمز إلا من حدّه الأدنى للمجموع الفرعي

    Scenario: مجموع فرعي عند الحد الأدنى يحصل على الخصم
      Given لدى هدى سلة مجموعها الفرعي 200.00 ريال
      When تطبّق الرمز "SAVE10"
      Then المبلغ المستحق قبل التوصيل 180.00 ريال

    Scenario: مجموع فرعي تحت الحد الأدنى مباشرة يبقى سعره
      Given لدى هدى سلة مجموعها الفرعي 199.99 ريال
      When تطبّق الرمز "SAVE10"
      Then يُرفض الرمز مع الرسالة "أكمل 200.00 ريال لاستخدام هذا الرمز"
      And المبلغ المستحق قبل التوصيل 199.99 ريال

  Rule: الرمز المنتهي الصلاحية يُرفض

    @smoke
    Scenario: رمز يُستخدم في اليوم التالي لتاريخ انتهائه
      Given اليوم هو 1 أبريل 2027
      And لدى هدى سلة مجموعها الفرعي 250.00 ريال
      When تطبّق الرمز "SAVE10"
      Then يُرفض الرمز مع الرسالة "انتهت صلاحية هذا الرمز"
ملف الميزة للقصة SHOP-31 مفصّلاً: وسم، والميزة ووصفها، وخلفية بجدول بيانات، وقاعدة، وسيناريو تُطابَق كلُّ خطوة من خطواته Given وWhen وThen مع تعريف خطوة.

القاعدة 1 من القصة SHOP-31 في ملف ميزة (مقتطف)

الجزء 1:@checkout
الجزء 2:Feature: Discount codes at checkout
الجزء 3:  A customer can enter one discount code per order.

الجزء 4:  Background:
    Given these discount codes exist:
الجزء 5:      | code   | percent | min_subtotal | valid_until |
      | SAVE10 | 10      | 200.00       | 2027-03-31  |

الجزء 6:  Rule: A code applies only from its minimum subtotal

الجزء 7:    Scenario: A subtotal at the minimum gets the discount
الجزء 8:      Given Huda has a cart with a subtotal of 200.00 SAR
الجزء 9:      When she applies the code "SAVE10"
الجزء 10:      Then the amount to pay before delivery is 180.00 SAR
  • 4–5يعمل قبل كل سيناريو
  • 8–10الخطوات: يُطابَق كل سطر مع تعريف خطوة
  1. 1الوسم (Tag)يُعلِّم الميزة كلها؛ ويرثه كل سيناريو فيها، فيستطيع تشغيل واحد أن يختارها جميعاً.
  2. 2الميزة (Feature)واحدة لكل ملف: القدرة التي تصفها السيناريوهات.
  3. 3الوصفنص حر للقرّاء؛ لا ينفّذه المشغّل.
  4. 4الخلفية (Background)سياق مشترك بين كل السيناريوهات؛ يعمل قبل كل سيناريو، بعد أي خطافات Before.
  5. 5جدول البيانات (Data Table)صفوف تُمرَّر إلى شيفرة الخطوة وسيطاً واحداً: رمز الخصم الذي تعتمد عليه السيناريوهات.
  6. 6القاعدة (Rule)قاعدة عمل واحدة، هي البطاقة الزرقاء في خريطة الأمثلة؛ وسيناريوهاتها هي البطاقات الخضراء.
  7. 7السيناريو (Scenario)مثال واحد لسلوك واحد؛ والعنوان يصرّح بالنتيجة.
  8. 8Given: السياقيضع النظام في حالة معروفة؛ ويلتقط {word} و{float} القيمتين Huda و200.00.
  9. 9When: الإجراءالإجراء الوحيد قيد الاختبار؛ ويلتقط {string} القيمة SAVE10.
  10. 10Then: النتيجةنتيجة قابلة للملاحظة؛ ويؤكد تعريف خطوتها أن الصفحة تعرض 180.00.

جرّبها: مولّد معايير القبول

السيناريو الجيد والسيناريو الضعيف

أكثر صور سوء تطبيق BDD شيوعاً ملفُ ميزة هو في حقيقته نص اختبار (Test Script): كل نقرة وحقل وعنوان URL مكتوب بصيغة Given/When/Then. هذا الأسلوب الأمري (Imperative) ينكسر مع كل إعادة تصميم، ويدفن القاعدة تحت تفاصيل الواجهة. أما الأسلوب التصريحي (Declarative) فيقول ما يحدث بلغة الأعمال ويترك الكيفية لشيفرة الأتمتة. قارن بين صيغتين للمثال نفسه.

لا شيء في الصيغة الأولى يبيّن لماذا ⁦180.00⁩ هي الإجابة الصحيحة: فالمجموع الفرعي ⁦200.00⁩ ريال، وهو الحقيقة الوحيدة التي تعتمد عليها القاعدة، مختبئ في سعر كتاب. وخطوتها الأخيرة تفحص جدولاً في قاعدة البيانات لا يراه أي صاحب مصلحة، وخطواتها تلصق عدة إجراءات معاً بحرف «و»، فلا يمكن إعادة استخدامها. أما الصيغة الثانية فيستطيع مالك المنتج قراءتها والموافقة عليها، وتبقى صالحة دون تغيير إذا أُعيد تصميم صفحة الدفع.

  • سلوك واحد لكل سيناريو، وعنوان يصرّح به: «مجموع فرعي تحت الحد الأدنى مباشرة يبقى سعره»، لا «اختبار الخصم ⁦2⁩».
  • خطوات تصريحية بكلمات الأعمال؛ والنقرات مكانها تعريفات الخطوات وكائنات الصفحات (Page Objects).
  • البيانات التي تغيّر النتيجة فقط. كل قيمة في السيناريو يجب أن تهمّ القاعدة، ولا يُخفى شيء تعتمد عليه القاعدة.
  • نتيجة يستطيع المستخدم أو صاحب المصلحة ملاحظتها في كل Then، ومحددة بما يكفي لتفشل: «يجب أن أرى ⁦180.00⁩» لا تقول أين.
  • سيناريوهات مستقلة قابلة للتكرار: كل سيناريو يجهّز سياقه بنفسه، وينجح أو يفشل وحده، بأي ترتيب وفي أي يوم. فسيناريوهات القاعدة 1 لا تذكر تاريخاً، ولذلك يجب أن تثبّت أتمتتُها ساعة المتجر على تاريخ معروف، في خطاف Before مثلاً؛ وإلا بدأت تفشل في اليوم الذي تنتهي فيه صلاحية SAVE10.
  • عبارة واحدة لكل فكرة في المشروع كله، مثل «المجموع الفرعي» في كل مكان بدلاً من «إجمالي السلة» في ملف و«مبلغ العربة» في آخر، حتى تعيد الخطوات استخدام التعريفات نفسها.
# أمري: نص اختبار في ثوب سيناريو
Scenario: اختبار الخصم 2
  Given أفتح "https://shop.example/login"
  And أكتب "huda@example.com" في "email" وأنقر "Sign in"
  And أنقر "Add to cart" بجانب "Desert Stars"
  And أنقر "Checkout"
  When أكتب "SAVE10" في "code" وأنقر "Apply"
  Then يجب أن أرى "180.00"
  And جدول الخصومات فيه صف لهذا الطلب

# تصريحي: سلوك واحد بلغة الأعمال
Scenario: مجموع فرعي عند الحد الأدنى يحصل على الخصم
  Given لدى هدى سلة مجموعها الفرعي 200.00 ريال
  When تطبّق الرمز "SAVE10"
  Then المبلغ المستحق قبل التوصيل 180.00 ريال

كيف ترتبط السيناريوهات بالأتمتة

يقرأ مشغّل BDD مثل Cucumber ملفات الميزات، ويبحث لكل خطوة عن تعريف خطوة (Step Definition): دالة مسجَّلة بنمط يجب أن يطابقه نص الخطوة. والنمط في الغالب تعبير Cucumber (Cucumber Expression)، تلتقط فيه معاملات مثل {int} و{float} و{word} و{string} قيماً من النص وتمرّرها إلى الدالة وسائطَ، وتصلح التعابير النمطية (Regular Expressions) أيضاً. والكلمة المفتاحية ليست جزءاً من المطابقة: خطوة Given وخطوة Then بالنص نفسه تستدعيان التعريف نفسه، فلا يمكن أن يعني النص نفسه شيئين مختلفين في موضعين.

تعريفات الخطوات شيفرة ربط (Glue Code) ويجب أن تبقى خفيفة. فهي تترجم خطوة الأعمال إلى استدعاءات لطبقة الأتمتة التي عرفتها في درسَي «نموذج كائنات الصفحات» (Page Object Model) و«معمارية أتمتة الاختبار»: عميل API يجهّز البيانات لخطوة Given، وكائن صفحة ينفّذ خطوة When كما يفعل المستخدم، وخطوة Then تقرأ النتيجة وتُجري التأكيد (Assertion) الذي يقارن النتيجة الفعلية بالمتوقعة. والقيم التي تنتجها خطوة لخطوة لاحقة، مثل السلة التي أنشأتها Given، تُحفظ في كائن سياق. وفي Cucumber للغة JavaScript هذا هو كائن World، المتاح باسم this داخل دالة الخطوة (ولذلك تُكتب دوال الخطوات بالكلمة function لا بصيغة الدالة السهمية (Arrow Function)، لأن الدالة السهمية لا تملك this خاصاً بها) والمُنشأ من جديد لكل سيناريو، وهذا ما يبقي السيناريوهات مستقلة؛ وعادةً يُنشأ فيه أيضاً عميل API وكائنات الصفحات، لتصل إليها كل خطوة.

تنتهي كل خطوة بحالة: ناجحة (passed)، أو فاشلة (failed)، أو متخطّاة (skipped، فالخطوات التي تلي خطوة فاشلة أو معلّقة أو غير معرَّفة لا تُشغَّل)، أو معلّقة (pending، تعريف لم يكتمل بعد)، أو غير معرَّفة (undefined، لا تعريف يطابقها، ويطبع Cucumber مقتطف شيفرة للبدء منه)، أو ملتبسة (ambiguous، يطابقها أكثر من تعريف). ويعرض التقرير السيناريوهات والخطوات بكلمات ملف الميزة، فتدلّ خطوة Then الفاشلة على القاعدة التي انكسرت. والوسوم تختار ما يُشغَّل، مثل npx cucumber-js --tags "@smoke and not @wip" في خط الأنابيب (Pipeline).

import { Given, When, Then } from "@cucumber/cucumber";
import assert from "node:assert/strict";

// يطابق خطوة Background؛ ويصل جدول البيانات وسيطاً أخيراً
Given("رموز الخصم التالية موجودة:", async function (table) {
  for (const row of table.hashes()) await this.api.createCode(row);
});

// يطابق: Given لدى هدى سلة مجموعها الفرعي 200.00 ريال
Given("لدى {word} سلة مجموعها الفرعي {float} ريال",
  async function (customer, subtotal) {
    this.cart = await this.api.createCart(customer, subtotal);
  });

// يطابق: When تطبّق الرمز "SAVE10"
When("تطبّق الرمز {string}", async function (code) {
  await this.checkout.open(this.cart);
  await this.checkout.applyCode(code);
});

// يطابق الخطوتين: Then المبلغ المستحق ... وAnd المبلغ المستحق ...
Then("المبلغ المستحق قبل التوصيل {float} ريال",
  async function (expected) {
    assert.equal(await this.checkout.amountBeforeDelivery(), expected);
  });

Then("يُرفض الرمز مع الرسالة {string}", async function (message) {
  assert.equal(await this.checkout.codeMessage(), message);
});
سيناريو من القصة SHOP-31 أثناء التشغيل: يطابق Cucumber كل خطوة مع تعريفها، وتستدعي التعريفات عميل API وكائن صفحة، ويحسم تأكيد خطوة Then النتيجة.
  • مشغّل Cucumber يقرأ ملف الميزة
  • تعريفات الخطوات شيفرة الربط
  • عميل API وكائن الصفحة طبقة الأتمتة
  • المتجر النظام قيد الاختبار
  1. 1 مشغّل Cucumber داخليًا: يحلّل الملف؛ ويشغّل الخطافات والخلفية ثم السيناريو npx cucumber-js --tags @checkout
  2. 2 من مشغّل Cucumber إلى تعريفات الخطوات: خطوة Given تطابق تعبيرها {word} = هدى، و{float} = 200
  3. 3 من تعريفات الخطوات إلى عميل API وكائن الصفحة: تنشئ السلة عبر عميل API api.createCart(customer, subtotal)
  4. 4 من عميل API وكائن الصفحة إلى المتجر: يجهّز البيانات عبر API لا عبر الشاشات
  5. 5 من مشغّل Cucumber إلى تعريفات الخطوات: خطوة When: يلتقط {string} القيمة SAVE10
  6. 6 من تعريفات الخطوات إلى عميل API وكائن الصفحة: تطبّق الرمز عبر كائن الصفحة checkout.applyCode(code)
  7. 7 من عميل API وكائن الصفحة إلى المتجر: يكتب الرمز ويختار Apply كما يفعل المستخدم
  8. 8 من مشغّل Cucumber إلى تعريفات الخطوات: خطوة Then: تعريف واحد يخدم كل Then أو And بهذا النص
  9. 9 من تعريفات الخطوات إلى عميل API وكائن الصفحة: تطلب من كائن الصفحة المبلغ قبل التوصيل checkout.amountBeforeDelivery()
  10. 10 من عميل API وكائن الصفحة إلى تعريفات الخطوات: يعيد 180 مقروءاً من صفحة الدفع ردّ
  11. 11 تعريفات الخطوات داخليًا: التأكيد: القيمة الفعلية 180 تساوي المتوقعة 180 assert.equal(actual, expected) نجاح

النتيجة: نجح السيناريو. والتأكيد الفاشل يجعل خطوته فاشلة، وتُتخطّى الخطوات التي بعدها. نجاح

جرّبها: أداة اختبار Regex

أخطاء شائعة

  • التعامل مع BDD على أنه اختيار أداة: تثبيت Cucumber ثم يكتب المختبِرون ملفات الميزات وحدهم بعد اكتمال التطوير. فلا اكتشاف ولا فهم مشترك، بل طبقة إضافية تحتاج إلى صيانة.
  • كتابة سيناريوهات أمرية على مستوى الواجهة مليئة بالنقرات وأسماء الحقول وعناوين URL، تنكسر مع كل إعادة تصميم وتخفي القاعدة.
  • خطوات مركّبة تربط عدة إجراءات بحرف «و»، وسيناريوهات تختبر عدة سلوكيات بأزواج متكررة من When وThen. فلا يمكن إعادة استخدامها، وإذا فشلت لا يعرف أحد السبب.
  • سيناريوهات يعتمد بعضها على بعض أو على بيانات تركها سيناريو سابق. على كل سيناريو أن يجهّز سياقه بنفسه.
  • تحويل كل تركيبة بيانات إلى صفوف في جدول Examples حتى تصير عشرات الاختبارات على مستوى الواجهة. انقل التركيبات إلى اختبارات الوحدة أو API، واترك بضعة أمثلة تشرح القاعدة.
  • ترك طبقة تعريفات الخطوات تتضخم: صيغ متقاربة مثل «إجمالي السلة هو» و«مبلغ العربة هو» لكل منها تعريف، أو منطق داخل تعريفات الخطوات بدلاً من كائنات الصفحات وعملاء API.

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

في الاكتشاف، المختبِر هو الصديق الذي يسأل «ماذا لو؟». فأنت تقترح القيم الحدّية (⁦199.99⁩ و⁦200.00⁩) والأمثلة السلبية وأسئلة الحالة والتوقيت، وتتأكد أن لكل قاعدة مثالاً يُظهرها وهي ترفض، لا وهي تقبل فقط. حوّل كل سؤال بلا جواب إلى بطاقة حمراء لها مسؤول بدلاً من التخمين. وفي الصياغة، راجع السيناريوهات كما تراجع المتطلبات: هل يستطيع مالك المنتج قراءة كل سيناريو والموافقة عليه؟ وهل كل سيناريو سلوك واحد بنتيجة قابلة للملاحظة؟

وفي الأتمتة، اعمل مع المطور على تعريفات الخطوات، واحتفظ بقائمة مشتركة لعبارات الخطوات المعتمدة، وأضف إلى كل سيناريو وسماً بمعرّف قصته ليبقى التتبع ممكناً. وإذا فشل سيناريو في خط الأنابيب، فالخطوة الفاشلة تخبرك بالقاعدة التي انكسرت؛ ثم تتبّع سببه كما في درس «تقارير الأتمتة وتصحيح الأخطاء». وعندما تتغير قاعدة، غيّر السيناريو أولاً ثم الشيفرة: فالتوثيق الحي لا يبقى حياً إلا ما دامت السيناريوهات صحيحة.

تمرين عملي

حوّل نص اختبار لحجز حصة رياضية إلى سيناريوهات BDD

المطلوب

لتطبيق نادٍ رياضي القصة أدناه، وملاحظات من مالك المنتج، ومسودة ملف ميزة كتبها أحد أعضاء الفريق بعد بناء الميزة. تعامل مع الملاحظات على أنها مدخلات جلسة الاكتشاف: ارسم القواعد والأمثلة والأسئلة المفتوحة، ثم أعد كتابة ملف الميزة وخطّط لتعريفات خطواته.

إذا لم تُذكر قيمة أو رسالة، فاقترح واحدة وعلّمها سؤالاً لمالك المنتج بدلاً من أن تقررها بنفسك.

المادة
GYM-12  حجز مكان في حصة رياضية
بصفتي عضواً، أريد حجز مكان في حصة،
حتى أضمن أن لي مكاناً.
ملاحظات من مالك المنتج: يُفتح الحجز قبل بدء الحصة بسبعة أيام.
في الحصة 20 مكاناً. عندما تمتلئ، يستطيع الأعضاء الانضمام إلى قائمة انتظار.

مسودة ملف الميزة:
Feature: الحجز
  Scenario: اختبار الحجز
    Given أنا في صفحة تسجيل الدخول
    And أدخل بريدي وكلمة المرور وأنقر "Log in"
    And أنقر "Classes" وأختار "Spinning, Monday 18:00"
    When أنقر "Book"
    Then أرى رسالة خضراء
    When أنقر "Book" للحصة "Yoga, Tuesday 09:00"
    Then جدول الحجوزات فيه صفان للعضو 1187
الخطوات
  1. اكتب كل مشكلة في سيناريو المسودة.
  2. ارسم خريطة أمثلة: القواعد، وأمثلة بقيم حدّية لكل قاعدة، والأسئلة التي ستطرحها.
  3. أعد كتابة ملف الميزة بكتلة Rule لكل قاعدة عمل وسيناريوهات تصريحية قصيرة.
  4. اكتب تعبير Cucumber لكل تعريف خطوة، وبيّن الخطوات التي تتشارك تعريفاً واحداً.
إظهار الإجابة النموذجية
الإجابة النموذجية

المشكلات: العنوان لا يسمّي سلوكاً؛ وتسجيل الدخول والنقرات وأسماء القوائم تفاصيل أمرية؛ وخطوتان تربطان عدة إجراءات بحرف «و»؛ وزوجان من When وThen يختبران سلوكين؛ و«رسالة خضراء» ليست نتيجة محددة؛ وخطوة Then الأخيرة تفحص جدولاً في قاعدة البيانات لا يراه أحد؛ ولا قاعدة ولا رفض ولا قيمة حدّية.

خريطة الأمثلة. القاعدة 1: يُفتح الحجز قبل بدء الحصة بسبعة أيام؛ الأمثلة: الحجز قبل البدء بسبعة أيام تماماً مقبول، والحجز قبل البدء بسبعة أيام ودقيقة مرفوض. القاعدة 2: في الحصة ⁦20⁩ مكاناً؛ الأمثلة: مع ⁦19⁩ مكاناً محجوزاً يُقبل الحجز ولا يبقى أي مكان، ومع ⁦20⁩ محجوزاً يُرفض الحجز ويُعرض الانضمام إلى قائمة الانتظار. القاعدة 3: يستطيع العضو الانضمام إلى قائمة انتظار حصة ممتلئة؛ مثال: ينضم سامي فيكون رقم ⁦1⁩ في القائمة. الأسئلة: هل تُحسب الأيام السبعة بالدقيقة أم بالأيام التقويمية؟ هل يستطيع العضو حجز الحصة نفسها مرتين؟ عندما يلغي أحدهم، هل يُحجز المكان تلقائياً للأول في قائمة الانتظار أم يُبلَّغ فقط؟

إعادة الكتابة للقاعدة 2: Feature: حجز مكان في حصة. Rule: في الحصة ⁦20⁩ مكاناً. Scenario: يمكن حجز آخر مكان، وفيه Given الحصة "Spinning, Monday 18:00" محجوز فيها ⁦19⁩ من ⁦20⁩ مكاناً؛ When يحجز سامي الحصة "Spinning, Monday 18:00"؛ Then يُؤكَّد الحجز؛ And عدد الأماكن المتبقية في الحصة ⁦0⁩. Scenario: الحصة الممتلئة تعرض قائمة الانتظار، وفيه Given الحصة "Spinning, Monday 18:00" محجوز فيها ⁦20⁩ من ⁦20⁩ مكاناً؛ When يحجز سامي الحصة "Spinning, Monday 18:00"؛ Then يُرفض الحجز مع الرسالة "هذه الحصة ممتلئة"؛ And يُعرض على سامي الانضمام إلى قائمة الانتظار. أما القاعدة 1 فتتعلق بلحظة زمنية، فتحتاج قيمتها الحدّية إلى ساعة: Scenario: يُفتح الحجز قبل الحصة بسبعة أيام تماماً، وفيه Given الحصة "Yoga, Tuesday 09:00" تبدأ في ⁦"2026-11-10 09:00"⁩؛ And الوقت الآن ⁦"2026-11-03 09:00"⁩؛ When يحجز سامي الحصة "Yoga, Tuesday 09:00"؛ Then يُؤكَّد الحجز. وسيناريو ثانٍ يضبط الوقت على ⁦"2026-11-03 08:59"⁩ ويتوقع Then يُرفض الحجز مع الرسالة "يُفتح الحجز قبل الحصة بسبعة أيام". وتأخذ القاعدة 3 كتلة Rule خاصة بها بالطريقة نفسها.

تعريفات الخطوات بتعابير Cucumber: الحصة {string} محجوز فيها {int} من {int} مكاناً؛ يحجز {word} الحصة {string}؛ يُؤكَّد الحجز؛ عدد الأماكن المتبقية في الحصة {int}؛ يُرفض الحجز مع الرسالة {string}؛ يُعرض على {word} الانضمام إلى قائمة الانتظار؛ وللقاعدة 1: الحصة {string} تبدأ في {string} والوقت الآن {string}. ويعيد سيناريوَا القاعدة 2 استخدام تعريفَي Given وWhen نفسيهما بقيم مختلفة، وتعيد القاعدة 1 استخدام يحجز {word} الحصة {string} ويُؤكَّد الحجز ويُرفض الحجز مع الرسالة {string} من القاعدة 2. والرسالتان «هذه الحصة ممتلئة» و«يُفتح الحجز قبل الحصة بسبعة أيام» اقتراحان ينتظران تأكيد مالك المنتج.

أسئلة شائعة

هل BDD هو نفسه كتابة الاختبارات بلغة Gherkin؟

لا. BDD هو التعاون: اكتشاف السلوك عبر أمثلة محددة بمنظورات الأعمال والتطوير والاختبار، ثم صياغة تلك الأمثلة وأتمتتها. أما Gherkin فهي الصيغة التي تُكتب بها الأمثلة. وملفات الميزات التي يكتبها المختبِرون وحدهم بعد وجود الشيفرة نصوص اختبار مؤتمتة بطبقة إضافية: تحمل كلفة صيانة BDD دون فائدته الأساسية، وهي الفهم المشترك.

من يكتب تعريفات الخطوات؟

يعتمد ذلك على الفريق. كثيراً ما يكتبها المطورون أو مهندسو الأتمتة، أحياناً أثناء بناء الميزة، بينما يكتب المختبِرون السيناريوهات أو يراجعونها ويعملون معهم على الأتمتة. والمهم أن يكون نص السيناريو متفقاً عليه مع جهة الأعمال، وأن تبقى تعريفات الخطوات خفيفة تستدعي كائنات الصفحات وعملاء API نفسها التي تستخدمها بقية شيفرة الاختبار.

هل يجب أن يكون كل اختبار سيناريو Gherkin؟

لا. استخدم السيناريوهات للسلوك الذي يهمّ الأعمال ويريدون قراءته، مع بضعة أمثلة لكل قاعدة. أما تركيبات المدخلات التفصيلية والفحوص التقنية ومعظم الحالات السلبية فأرخص على شكل اختبارات وحدة أو اختبارات API دون طبقة Gherkin، انسجاماً مع هرم أتمتة الاختبار.

هل يمكن كتابة Gherkin بالعربية أو الهندية؟

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

المراجع

  • Gherkin Reference — CucumberCucumberصياغة Gherkin للمواصفات القابلة للتنفيذ: Feature وRule وScenario، وخطوات Given/When/Then، وBackground وScenario Outline وExamples.
  • Behaviour-Driven Development — CucumberCucumberالتطوير الموجّه بالسلوك (BDD) كثلاث ممارسات — الاكتشاف والصياغة والأتمتة — قائمة على أمثلة محددة يتفق عليها أهل الأعمال والفريق التقني.
  • ISTQB Certified Tester Foundation Level (CTFL) v4.0ISTQB · 2023المنهج التأسيسي لاختبار البرمجيات: مستويات الاختبار وأنواعه وتقنيات تصميم الاختبارات وإدارة الاختبار والأدوات.

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

ولّد معايير قبول بصيغة Given/When/Then وقائمة تحقق لتعريف الإنجاز (Definition of Done) من قصة مستخدم.

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

ولّد القيم الحدّية (min-1 وmin وmin+1 والقيمة الاسمية وmax-1 وmax وmax+1) للقيود الرقمية وقيود الطول.

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

أنشئ جداول القرار من الشروط والإجراءات، وولّد جميع تركيبات القواعد، واختزل المستحيل منها.

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

اختبر التعابير النمطية مباشرةً مع إبراز التطابقات ومجموعات الالتقاط ومعاينة الاستبدال.

أدوات مساعدة

للتعمّق أكثر

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

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