الأدلة التقنية

من فكرة غامضة إلى متطلبات Requirements واضحة: مثال عملي مع القوالب

كيف تحول جملة «نبغى نظام حجز مواعيد لعيادة» إلى متطلبات Requirements واضحة يمكن بناؤها واختبارها، مع قوالب جاهزة للنسخ.

من فكرة غامضة إلى متطلبات Requirements واضحة: مثال عملي مع القوالب

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

  • الأسئلة الأولى التي يثيرها الطلب، وخطة المقابلات مع قالب أسئلة جاهز.
  • تحويل ملاحظات المقابلات إلى بيان مشكلة Problem Statement وأهداف قابلة للقياس وقاموس مصطلحات.
  • خريطة أصحاب المصلحة Stakeholders للعيادة.
  • قصص المستخدم User Stories بأولويات MoSCoW، وقواعد العمل Business Rules، ومعايير القبول بصيغة Gherkin، مع تشغيلها كاختبار حقيقي بأداة behave.
  • المتطلبات غير الوظيفية بالأرقام: الأداء والتوافرية والأمن والعربية والخصوصية.
  • ما هو خارج النطاق، والافتراضات Assumptions، والقيود Constraints.
  • نموذج أولي منخفض الدقة Low-fidelity Prototype لشاشتي الحجز.
  • وثيقة SRS من صفحة واحدة بالعربية، وقائمة المراجعة والتوقيع.
  • طلب تغيير Change Request بعد التوقيع: من التسجيل وتحليل الأثر إلى الاختبار الذي يفشل ثم ينجح.

الطلب كما وصل، والأسئلة الأولى

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

ثمانية مربعات مرقمة في صفين: الطلب، ثم المقابلات والملاحظة، ثم بيان المشكلة، ثم خريطة أصحاب المصلحة، ثم القصص ومعايير القبول بصيغة Gherkin، ثم المتطلبات غير الوظيفية والنطاق، ثم وثيقة SRS من صفحة، ثم المراجعة والتوقيع، وسهم متقطع من التوقيع إلى القصص لطلب تغيير لاحق
كل خطوة تنتج شيئاً مكتوباً يراجعه صاحب العمل قبل الخطوة التالية

خطة المقابلات Interviews وقالب الأسئلة

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

منالطريقةالمدةماذا نريد أن نعرف
مالك العيادةمقابلة45 دقيقةأهداف العمل، ومقياس النجاح، والميزانية، والموعد، ومن يقرر
طبيب من كل تخصصمقابلة30 دقيقة لكل طبيبمدة كل نوع موعد، والراحة بين المواعيد، والإجازات، وما يريد أن يراه صباحاً
موظفتا الاستقبالملاحظة Observation نصف يوم، ثم مقابلة4 ساعات و30 دقيقةكيف يتم الحجز الآن فعلاً، والاستثناءات اليومية، وما يضيع وقتهن
خمسة مرضىمكالمة قصيرة10 دقائق لكل مريضكيف يفضلون الحجز، وما الذي يزعجهم في الطريقة الحالية
المحاسبمقابلة20 دقيقةهل يرتبط الموعد بالفاتورة، وماذا يحتاج من تقارير
الوثائق الحاليةتحليل وثائق Document Analysisساعةملف Excel للمواعيد، ونموذج تسجيل المريض، وأي سياسة مكتوبة للإلغاء

والقالب التالي هو دليل المقابلة Interview Guide الذي نستخدمه، ولاحظ أنه أسئلة مفتوحة تطلب أمثلة حقيقية، وليس قائمة ميزات نسأل «هل تريدونها؟»:

# دليل مقابلة جمع المتطلبات
المشروع: ............  الشخص: ............  الدور: ............
التاريخ: ............  المقابل: ............  مدة المقابلة: ......

## 1. السياق (5 دقائق)
- صف لي يوم عملك العادي من أوله.
- ما الجزء من عملك الذي له علاقة بالمواعيد؟

## 2. الطريقة الحالية (10 دقائق)
- احك لي عن آخر مرة حجزت فيها موعداً لمريض، خطوة بخطوة.
- أين تكتب الموعد الآن؟ أرني الملف أو الدفتر إن أمكن.
- من غيرك يلمس هذه المعلومة؟

## 3. المشكلات (10 دقائق)
- ما الذي يضيع وقتك أكثر شيء في المواعيد؟
- احك لي عن آخر مرة حدث فيها خطأ في موعد. ماذا حدث بعدها؟
- كم مرة يحدث هذا تقريباً في الأسبوع؟

## 4. القواعد والاستثناءات (10 دقائق)
- ما الذي لا يجب أن يحدث أبداً في الحجز؟
- ماذا تفعل إذا ألغى المريض قبل موعده بقليل؟ إذا لم يحضر؟
- ماذا يحدث لمواعيد الطبيب إذا غاب فجأة؟

## 5. النجاح (5 دقائق)
- بعد ستة أشهر من تشغيل النظام، كيف ستعرف أنه نجح؟
- لو استطعت تغيير شيء واحد فقط، ماذا سيكون؟

## 6. البيانات والقيود (5 دقائق)
- ما البيانات التي تحتاجها عن المريض لحجز موعد؟ وما الذي لا تحتاجه؟
- هل هناك نظام آخر يجب أن يتكامل معه؟ أو جهة يجب أن نلتزم بمتطلباتها؟

## 7. الإغلاق
- هل هناك شيء لم أسأل عنه وكان يجب أن أسأل؟
- من غيرك يجب أن أكلمه؟
- هل يمكنني الرجوع إليك بأسئلة بعد كتابة الملاحظات؟

## ملاحظات المقابل
- الكلمات التي استخدمها كما قالها (للقاموس):
- الحلول التي اقترحها (نسأل عنها: لماذا؟):
- الأرقام التي ذكرها (نتحقق منها):

في القالب أعلاه لاحظ التالي:

  • السؤال «احك لي عن آخر مرة…» أهم سؤال في القالب، والسبب أن الناس عندما يصفون عملهم بشكل عام يصفون الطريقة الرسمية، وعندما يحكون عن مرة حقيقية تظهر الاستثناءات والطرق المختصرة التي يستخدمونها فعلاً.
  • قسم «الحلول التي اقترحها» موجود لأن كل حل يقترحه الشخص يجب أن نسأل خلفه «لماذا؟» حتى نصل إلى الحاجة كما شرحنا في الحاجة أم الحل.
  • كلمات الشخص كما قالها تدخل قاموس المصطلحات Glossary، فإذا قالت موظفة الاستقبال «حجز مبدئي» فهذا مصطلح له معنى في عملها ويجب أن نفهمه قبل أن نكتب الكود.

من الملاحظات إلى بيان المشكلة Problem Statement

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

ملاحظات المقابلات (مثال توضيحي)

[الاستقبال - ملاحظة نصف يوم]
- الحجز في ملف Excel مشترك، وأحياناً يفتحه شخصان فيضيع تعديل أحدهما.
- معظم المكالمات في الصباح لتغيير موعد أو إلغائه، وليس لحجز جديد.
- عند غياب طبيب تتصل الموظفة بكل مرضاه واحداً واحداً.
- "حجز مبدئي" = موعد محجوز لمريض لم يؤكد بعد (يُلغى إذا لم يؤكد قبل يوم).

[المالك]
- الهدف: تقليل المكالمات على الاستقبال، وتقليل المواعيد التي لا يحضر أصحابها.
- يقترح "تطبيق جوال" -> لماذا؟ -> "المرضى يبغون يحجزون بالليل".
- الموعد يجب أن يكون جاهزاً قبل موسم الإجازات.

[الأطباء]
- مدة الموعد تختلف: كشف 30 دقيقة، تنظيف 45، علاج عصب 60.
- الطبيب يعرف مواعيد يومه من ورقة يطبعها الاستقبال صباحاً.

[المرضى]
- 4 من 5 قالوا إنهم يفضلون الحجز من الجوال دون اتصال.
- مريضة: "أبغى أعرف الأوقات الفاضية بدل ما أسأل وقت وقت".

ومن هذه الملاحظات نكتب بيان المشكلة بالقالب التالي، وهو قالب قصير يجبرك على أن تقول من يتأثر وكيف تعرف أنك نجحت قبل أن تذكر أي حل:

# بيان المشكلة
المشكلة: [ما الذي يحدث الآن]
تؤثر على: [من يتأثر]
وأثرها: [ماذا يكلف، بالأرقام إن وجدت]
والحل الناجح سوف: [ماذا يتغير، دون ذكر تقنية]
ونعرف أننا نجحنا عندما: [مقياس واحد أو اثنان، مع القيمة الحالية والمستهدفة]
# بيان المشكلة: عيادة الأسنان (مثال)
المشكلة: المواعيد تدار في ملف Excel مشترك وعبر الهاتف فقط، والمرضى
لا يعرفون الأوقات المتاحة إلا بالاتصال أثناء الدوام.
تؤثر على: المرضى، وموظفتي الاستقبال، والأطباء.
وأثرها: أغلب وقت الاستقبال الصباحي في مكالمات تغيير وإلغاء، وتعديلات
تضيع عند فتح الملف من جهازين، والطبيب يعرف جدوله من ورقة مطبوعة.
والحل الناجح سوف: يسمح للمريض أن يرى الأوقات المتاحة ويحجز ويلغي بنفسه
في أي وقت، ويعطي الاستقبال والأطباء جدولاً واحداً محدثاً دائماً.
ونعرف أننا نجحنا عندما: تنخفض مكالمات الحجز والتغيير اليومية من [القيمة
الحالية التي نقيسها في أسبوع] إلى النصف خلال ثلاثة أشهر من الإطلاق،
وتنخفض نسبة المواعيد التي لا يحضر أصحابها من [القيمة الحالية] إلى [الهدف].

لاحظ أن البيان لا يذكر «تطبيق جوال» رغم أن المالك اقترحه، لأن السؤال «لماذا؟» أظهر أن الحاجة هي الحجز في أي وقت ومن الجوال، وهذا يتحقق أيضاً بموقع يعمل جيداً على الجوال، والقرار بين الاثنين قرار تصميم وكلفة يأتي لاحقاً. ولاحظ أيضاً أن القيم الحالية فارغة، فلا نخترع رقماً، وإنما نكتب أننا سوف نقيسه في أسبوع قبل البدء، لأن هدفاً بلا قيمة بداية لا يمكن أن تعرف هل تحقق.

ومع بيان المشكلة نبدأ قاموساً صغيراً للمصطلحات، وهو نفس فكرة اللغة المشتركة Ubiquitous Language، أي أن الكلمة التي يقولها الاستقبال هي نفسها التي تظهر في الوثيقة والكود والاختبار:

المصطلح كما يقوله أهل العملالاسم في الكودالتعريف
فترةSlotوقت بداية ونهاية عند طبيب معين، قد تكون متاحة أو محجوزة
موعد أو حجزBookingفترة محجوزة لمريض واحد
حجز مبدئيTentativeBookingحجز لم يؤكده المريض بعد، ويلغى تلقائياً إذا لم يؤكد قبل يوم
عدم حضورNoShowموعد مر وقته ولم يحضر المريض ولم يلغ
جدول اليومDayScheduleكل مواعيد طبيب في يوم واحد مرتبة بالوقت

خريطة أصحاب المصلحة Stakeholder Map

الآن نضع كل من ظهر في المقابلات على خريطة بمحورين: قوته في القرار Power، واهتمامه بالنظام Interest، والهدف أن نعرف من نشركه في كل قرار ومن نكتفي بإبلاغه، ومن قد ننساه. والصورة التالية تبين الخريطة للعيادة:

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

ونحول الخريطة إلى جدول يبقى مع الوثيقة، لأن الصورة تنسى والجدول يحدث:

صاحب المصلحةماذا يريد من النظامكيف نشركهمن يمثله
مالك العيادة (الراعي Sponsor)مكالمات أقل، ومواعيد ضائعة أقل، والتزام بالموعد والميزانيةيعتمد الوثيقة وكل طلب تغييرالمالك نفسه
الأطباءجدول يومي دقيق، والتحكم في ساعات العمل والإجازاتمراجعة القصص التي تخصهم والنموذج الأوليالطبيب المسؤول
الاستقبالحجز سريع لمن يتصل، وتغييرات أقل يدوياًملاحظة عملهم، وتجربة النموذج الأوليموظفة الاستقبال الأقدم
المرضىرؤية الأوقات المتاحة والحجز والإلغاء في أي وقتمكالمات قصيرة، واختبار سهولة الاستخدام قبل الإطلاقعينة من خمسة مرضى
مسؤول الالتزام والخصوصيةبيانات أقل، وموافقة واضحة، ومكان حفظ معروفمراجعة المتطلبات غير الوظيفية للخصوصية والأمنمن يعينه المالك

قصص المستخدم User Stories وقواعد العمل Business Rules

من الملاحظات وبيان المشكلة نكتب قصص المستخدم، كل قصة برقم ودور وحاجة وسبب، ونعطي كل واحدة أولوية بطريقة MoSCoW التي شرحناها في ترتيب الأولويات:

الرقمالقصةالأولوية
US-01بصفتي مريضاً، أريد أن أرى الأوقات المتاحة لطبيب وأحجز أحدها، حتى لا أضطر للاتصال أثناء الدوامMust
US-02بصفتي مريضاً، أريد أن ألغي موعدي بنفسي، حتى يستفيد غيري من الوقت ولا أضطر للاتصالMust
US-03بصفتي موظفة استقبال، أريد أن أحجز لمريض يتصل بالهاتف من نفس الجدول، حتى لا يكون هناك جدولانMust
US-04بصفتي طبيباً، أريد أن أحدد ساعات عملي وإجازاتي، حتى لا يحجز أحد في وقت لست فيهMust
US-05بصفتي طبيباً، أريد أن أرى جدول يومي محدثاً على جوالي، حتى لا أعتمد على ورقة مطبوعةShould
US-06بصفتي مريضاً، أريد رسالة تذكير قبل موعدي بيوم، حتى لا أنسىShould
US-07بصفتي موظفة استقبال، أريد أن أنقل مواعيد طبيب غائب إلى طبيب آخر وأبلغ المرضى دفعة واحدة، حتى لا أتصل بكل مريضCould
US-08بصفتي مريضاً، أريد أن أدفع مقدماً عند الحجزWon't this time

وبجانب القصص نكتب قواعد العمل Business Rules مستقلة برقم، والسبب أن القاعدة الواحدة تخدم أكثر من قصة، فمنع الحجز المزدوج يطبق على حجز المريض وحجز الاستقبال معاً، وإذا كتبتها داخل قصة واحدة فسوف تنساها في الأخرى:

الرقمالقاعدةالمصدر
BR-01لا تحجز الفترة الواحدة عند نفس الطبيب لأكثر من مريضالاستقبال والمالك
BR-02لا يقبل الحجز إذا بقي على الموعد أقل من ساعتينالأطباء
BR-03يسمح بالحجز حتى 30 يوماً قادمة فقطالمالك
BR-04يسمح للمريض بالإلغاء حتى 4 ساعات قبل الموعد، وبعدها بالاتصال فقطالمالك
BR-05مدة الموعد حسب نوعه: كشف 30 دقيقة، تنظيف 45، علاج عصب 60الأطباء

ولاحظ أن قاعدة BR-01 هي نفس المشكلة التي كتبنا لها سجل قرار معماري كاملاً في مثال منع الحجز المزدوج، فالمتطلب يقول ماذا يجب أن يكون صحيحاً، وسجل القرار يقول كيف ضمنا ذلك في قاعدة البيانات ولماذا، وكلاهما يحمل نفس الرقم.

بطاقة القصة Story Card كاملة مع معايير القبول

القصة في الجدول سطر واحد، وقبل أن تدخل الدورة Sprint نكتبها كبطاقة كاملة بالقالب التالي، وهو ما تضعه في وصف التذكرة Ticket في نظام إدارة المهام، مثل Taiga إن كنت تستخدمه كما شرحنا في دليل Taiga، مع وسوم بأرقام القواعد:

# US-01: حجز موعد متاح عبر الإنترنت
الأولوية: Must        الحالة: جاهزة للتطوير       الإصدار: 1.0
القواعد المرتبطة: BR-01, BR-02, BR-03, BR-05

## القصة
بصفتي مريضاً،
أريد أن أرى الأوقات المتاحة لطبيب وأحجز أحدها،
حتى لا أضطر للاتصال بالعيادة أثناء الدوام.

## معايير القبول
1. بفرض أن الفترة متاحة، عندما يحجزها المريض، يتأكد الحجز وتختفي
   الفترة من الأوقات المتاحة لغيره.                         (BR-01)
2. بفرض أن مريضاً حجز الفترة، عندما يحاول مريض آخر حجزها، يرفض
   الحجز برسالة «هذا الموعد لم يعد متاحاً».                 (BR-01)
3. لا يقبل الحجز إذا بقي على الموعد أقل من ساعتين.          (BR-02)
4. لا يقبل الحجز بعد 30 يوماً من اليوم.                      (BR-03)
5. كل النصوص والرسائل بالعربية، والشاشة من اليمين إلى اليسار. (NFR-05)

## خارج هذه القصة
- الدفع (US-08)، ورسالة التذكير (US-06).

## أسئلة مفتوحة
- لا يوجد.

## تعريف الجاهزية (قبل دخول الدورة)
- [ ] معايير القبول مكتوبة بأمثلة ومقبولة من المالك
- [ ] النموذج الأولي للشاشة راجعته موظفة الاستقبال
- [ ] القواعد المرتبطة لها أرقام في الوثيقة

معايير القبول Acceptance Criteria بصيغة Gherkin، وتشغيلها كاختبار

معايير القبول المكتوبة بالعربية في البطاقة يقرؤها المالك، والآن نكتب القواعد المهمة منها بصيغة Gherkin حتى تتحول إلى اختبار آلي Automated Test يعمل مع كل تغيير، ونشغلها بأداة behave لبايثون داخل Container من Docker Image الرسمية لبايثون، فلا تحتاج أن تثبت شيئاً على جهازك. وننشئ مجلداً فيه الملفات التالية:

lab/
├── booking.py                  قواعد الحجز BR-01 إلى BR-04
└── features/
    ├── booking.feature         معايير القبول بصيغة Gherkin
    └── steps/
        └── booking_steps.py    ربط كل خطوة بالكود

أولاً ملف معايير القبول features/booking.feature، ولاحظ الوسوم Tags مثل @US-01 و@BR-01 فوق كل سيناريو، فهي التي تربط الاختبار بالقصة والقاعدة:

Feature: Online appointment booking
  As a patient
  I want to book a free slot with a doctor online
  So that I do not have to call the clinic during working hours

  Background:
    Given the clock is "2026-10-12 09:00"
    And doctor "Dr. Huda" has a free slot at "2026-10-14 10:00"

  @US-01 @BR-01
  Scenario: Book a free slot
    When patient "P-100" books doctor "Dr. Huda" at "2026-10-14 10:00"
    Then the booking is confirmed
    And the slot "2026-10-14 10:00" of doctor "Dr. Huda" is no longer free

  @US-01 @BR-01
  Scenario: Two patients cannot take the same slot
    Given patient "P-100" has booked doctor "Dr. Huda" at "2026-10-14 10:00"
    When patient "P-200" books doctor "Dr. Huda" at "2026-10-14 10:00"
    Then the booking is rejected with "SLOT_TAKEN"

  @US-01 @BR-02 @BR-03
  Scenario Outline: Booking window
    When patient "P-100" books doctor "Dr. Huda" at "<slot>"
    Then the result is "<result>"

    Examples:
      | slot             | result    |
      | 2026-10-12 10:00 | TOO_LATE  |
      | 2026-10-12 11:00 | CONFIRMED |
      | 2026-11-11 09:00 | CONFIRMED |
      | 2026-11-11 10:00 | TOO_FAR   |

  @US-02 @BR-04
  Scenario: Cancel early enough and the slot is free again
    Given patient "P-100" has booked doctor "Dr. Huda" at "2026-10-14 10:00"
    And the clock is "2026-10-14 05:00"
    When patient "P-100" cancels the booking at "2026-10-14 10:00"
    Then the slot "2026-10-14 10:00" of doctor "Dr. Huda" is free

  @US-02 @BR-04
  Scenario: Cancel too late
    Given patient "P-100" has booked doctor "Dr. Huda" at "2026-10-14 10:00"
    And the clock is "2026-10-14 07:00"
    When patient "P-100" cancels the booking at "2026-10-14 10:00"
    Then the cancellation is rejected with "CANCEL_TOO_LATE"

ثم الكود الذي يطبق القواعد في booking.py، وهو كود بسيط في الذاكرة لأن هدفنا هنا المتطلبات وليس قاعدة البيانات:

# Booking rules of the clinic example (BR-01 .. BR-04 in the SRS).
from datetime import datetime, timedelta

MIN_NOTICE = timedelta(hours=2)        # BR-02: book at least 2 hours before the slot
MAX_AHEAD = timedelta(days=30)         # BR-03: book at most 30 days ahead
CANCEL_CUTOFF = timedelta(hours=4)     # BR-04: cancel until 4 hours before the slot


class BookingError(Exception):
    def __init__(self, code):
        super().__init__(code)
        self.code = code


class Clinic:
    def __init__(self, now):
        self.now = now
        self.bookings = {}             # (doctor, start) -> patient

    def book(self, doctor, start, patient):
        if start - self.now < MIN_NOTICE:
            raise BookingError("TOO_LATE")
        if start - self.now > MAX_AHEAD:
            raise BookingError("TOO_FAR")
        if (doctor, start) in self.bookings:   # BR-01: one patient per slot
            raise BookingError("SLOT_TAKEN")
        self.bookings[(doctor, start)] = patient

    def cancel(self, doctor, start, patient):
        if self.bookings.get((doctor, start)) != patient:
            raise BookingError("NOT_FOUND")
        if start - self.now < CANCEL_CUTOFF:
            raise BookingError("CANCEL_TOO_LATE")
        del self.bookings[(doctor, start)]

    def is_free(self, doctor, start):
        return (doctor, start) not in self.bookings

ثم ملف الخطوات features/steps/booking_steps.py الذي يربط كل جملة في ملف Gherkin بدالة بايثون:

from datetime import datetime
from behave import given, when, then
from booking import Clinic, BookingError

DOCTOR = "Dr. Huda"


def at(text):
    return datetime.strptime(text, "%Y-%m-%d %H:%M")


@given('the clock is "{now}"')
def step_clock(context, now):
    if not hasattr(context, "clinic"):
        context.clinic = Clinic(at(now))
    context.clinic.now = at(now)


@given('doctor "{doctor}" has a free slot at "{slot}"')
def step_free_slot(context, doctor, slot):
    assert context.clinic.is_free(doctor, at(slot))


@given('patient "{patient}" has booked doctor "{doctor}" at "{slot}"')
def step_booked(context, patient, doctor, slot):
    context.clinic.book(doctor, at(slot), patient)


@when('patient "{patient}" books doctor "{doctor}" at "{slot}"')
def step_book(context, patient, doctor, slot):
    try:
        context.clinic.book(doctor, at(slot), patient)
        context.result = "CONFIRMED"
    except BookingError as e:
        context.result = e.code


@when('patient "{patient}" cancels the booking at "{slot}"')
def step_cancel(context, patient, slot):
    try:
        context.clinic.cancel(DOCTOR, at(slot), patient)
        context.result = "CANCELLED"
    except BookingError as e:
        context.result = e.code


@then('the booking is confirmed')
def step_confirmed(context):
    assert context.result == "CONFIRMED", context.result


@then('the booking is rejected with "{code}"')
@then('the cancellation is rejected with "{code}"')
@then('the result is "{code}"')
def step_result(context, code):
    assert context.result == code, f"expected {code}, got {context.result}"


@then('the slot "{slot}" of doctor "{doctor}" is no longer free')
def step_not_free(context, slot, doctor):
    assert not context.clinic.is_free(doctor, at(slot))


@then('the slot "{slot}" of doctor "{doctor}" is free')
def step_is_free(context, slot, doctor):
    assert context.clinic.is_free(doctor, at(slot))

الآن نشغل الاختبارات من داخل مجلد lab:

docker run --rm --name lab-req-behave -v "$PWD":/work -w /work -e PYTHONPATH=/work python:3.12-slim sh -c "pip install -q --root-user-action=ignore --disable-pip-version-check behave==1.3.3 && behave --no-color --format progress"

والمخرج سوف يكون كما يلي:

USING RUNNER: behave.runner:Runner
features/booking.feature  ........

1 feature passed, 0 failed, 0 skipped
8 scenarios passed, 0 failed, 0 skipped
38 steps passed, 0 failed, 0 skipped
Took 0min 0.044s

في الإعداد أعلاه لاحظ التالي:

  • السيناريو Scenario Outline مع جدول Examples يختبر الحدود Boundary Values بالضبط: الساعة 10:00 أقل من ساعتين فترفض، و11:00 ساعتان بالضبط فتقبل، و30 يوماً بالضبط تقبل، وبعدها بساعة ترفض. وهذه الحدود هي أكثر ما يختلف عليه الناس، فإذا قال المالك «ساعتين» فهل يقصد ساعتين أو أكثر؟ الجدول يجبرك على أن تسأل، ويجبره على أن يجيب.
  • القسم Background يعمل قبل كل سيناريو، لذلك تبدأ كل حالة من ساعة ثابتة هي 2026-10-12 09:00، والساعة الثابتة هي ما يجعل الاختبار يعطي نفس النتيجة اليوم وبعد سنة.
  • الثمانية سيناريوهات نجحت، والنقاط الثماني في المخرج هي سيناريو لكل نقطة، وعدد الخطوات 38 لأن خطوتي ال Background تتكرران في كل سيناريو.

والوسوم تعطيك التتبع Traceability مجاناً، فإذا أردت أن تشغل فقط ما يخص قاعدة الإلغاء:

docker run --rm --name lab-req-behave -v "$PWD":/work -w /work -e PYTHONPATH=/work python:3.12-slim sh -c "pip install -q --root-user-action=ignore --disable-pip-version-check behave==1.3.3 && behave --no-color --format progress --tags=@BR-04"

والمخرج سوف يكون كما يلي، حيث تخطى behave ستة سيناريوهات لا تحمل الوسم:

USING RUNNER: behave.runner:Runner
features/booking.feature  SSSSSS..

1 feature passed, 0 failed, 0 skipped
2 scenarios passed, 0 failed, 6 skipped
12 steps passed, 0 failed, 26 skipped
Took 0min 0.019s

وقد يتساءل البعض: هل يجب أن تكتب كل معايير القبول بصيغة Gherkin؟ والإجابة لا، فاكتب بها قواعد العمل التي لها أمثلة وحدود، مثل الحجز والإلغاء والأسعار والصلاحيات، واترك تفاصيل الواجهة مثل لون الزر ومكانه للنموذج الأولي والمراجعة اليدوية، والسبب أن السيناريو الذي يختبر تفاصيل الواجهة ينكسر مع كل تعديل في التصميم دون أن يحمي قاعدة مهمة. ومكان هذه الاختبارات بين بقية الأنواع شرحناه في اختبار القبول Acceptance Test.

المتطلبات غير الوظيفية Non-functional Requirements بالأرقام

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

الرقمالخاصيةالمتطلب القابل للقياسكيف نتحقق منه
NFR-01الأداء Performance95% من طلبات عرض الأوقات المتاحة تعود في أقل من ثانية واحدة عند 50 مستخدماً متزامناًاختبار أحمال Load Test قبل الإطلاق، كما في اختبار الأحمال
NFR-02التوافرية Availability99.5% شهرياً، والصيانة المخططة بين 1:00 و5:00 فجراً فقطمراقبة خارجية تسجل التوقف كل دقيقة
NFR-03النسخ الاحتياطي Backupلا نفقد أكثر من ساعة من الحجوزات RPO، ونستعيد الخدمة في أقل من 4 ساعات RTOتجربة استعادة كل ثلاثة أشهر، كما في قاعدة 3-2-1
NFR-04الأمن Securityكل حسابات الموظفين بتحقق ثنائي MFA، والمريض يرى ويعدل حجوزاته فقط، والتطبيق يحقق المستوى الثاني من OWASP ASVSاختبارات صلاحيات آلية ومراجعة كما في مقال ASVS
NFR-05العربية والاتجاه RTLكل الشاشات والرسائل بالعربية ومن اليمين إلى اليسار، والتواريخ ميلادية مع الهجري بجانبها، والأوقات بتوقيت الرياضاختبار مرئي للشاشات، وحالات اختبار للتاريخ والوقت كما في مقال التاريخ والوقت
NFR-06الخصوصية Privacyنجمع للحجز الاسم ورقم الجوال فقط، ولا نرسل رسائل التذكير إلا بموافقة صريحة يمكن سحبها، ونحذف بيانات الحجوزات بعد مدة الاحتفاظ التي تعتمدها العيادةمراجعة الحقول قبل الإطلاق، واختبار سحب الموافقة
NFR-07سجل التدقيق Audit Trailكل إنشاء وإلغاء وتعديل لموعد يسجل من فعله ومتى ومن أين، ولا يمكن تعديل السجلاختبار تكامل، كما في مقال سجل التدقيق
NFR-08سهولة الاستخدام Usability4 من 5 مرضى يستخدمون النظام لأول مرة يكملون الحجز من الجوال في أقل من دقيقتين دون مساعدةجلسة اختبار مع خمسة مرضى على النموذج ثم على النسخة التجريبية
NFR-09إمكانية الوصول Accessibilityشاشات المريض تحقق مستوى AA في WCAG 2.2فحص آلي ثم مراجعة يدوية بقارئ الشاشة

وفي الجدول أعلاه لاحظ التالي:

  • كل متطلب له طريقة تحقق في نفس السطر، فإذا لم تستطع أن تكتب طريقة التحقق فالمتطلب ما زال غامضاً.
  • متطلب الخصوصية NFR-06 مكتوب بلغة يمكن اختبارها، وهو مبني على نظام حماية البيانات الشخصية PDPL الذي يمكنك قراءة نصه في منصة حماية البيانات الشخصية، وبيانات المرضى الصحية من البيانات الحساسة فيه، لذلك يراجع مسؤول الالتزام هذا السطر قبل التوقيع ولا يكتبه المطور وحده.
  • المتطلبات الأمنية التفصيلية لا تكتب كلها هنا، وإنما نختار مستوى ASVS ونمر على متطلباته عند كتابة كل قصة، ونكشف ما هو خاص بتصميمنا بجلسة نمذجة التهديدات.

خارج النطاق Out of Scope والافتراضات Assumptions والقيود Constraints

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

## خارج النطاق في الإصدار 1.0
- الدفع الإلكتروني عند الحجز (US-08)، ويعاد النظر فيه بعد ثلاثة أشهر.
- الربط مع شركات التأمين والتحقق من التغطية.
- الملف الطبي للمريض والتشخيص والوصفات، فالنظام للمواعيد فقط.
- أكثر من فرع للعيادة.
- تطبيق جوال يثبت من المتجر، فالإصدار الأول موقع يعمل على الجوال.
- نقل مواعيد طبيب غائب دفعة واحدة (US-07) إذا لم يتسع الوقت.

ثم الافتراضات، وكل افتراض معه من يتأكد منه ومتى، لأن الافتراض الذي لا يملكه أحد لا يتأكد منه أحد:

الرقمالافتراضمن يتأكدقبلإذا كان خطأ
A-01كل المرضى لديهم رقم جوال يستقبل الرسائلالاستقبال من ملف المرضىنهاية الأسبوع الأولنضيف البريد الإلكتروني كخيار
A-02ملف Excel الحالي يمكن تصديره إلى CSV بأعمدة ثابتةالمطور مع الاستقبالنهاية الأسبوع الأولنضيف مهمة تنظيف بيانات يدوية
A-03مزود الرسائل يدعم العربية واسم المرسل للعيادةالمطورقبل بدء US-06نختار مزوداً آخر
A-04مدة كل نوع موعد ثابتة لكل الأطباءالطبيب المسؤولقبل بدء US-04نجعل المدة إعداداً لكل طبيب

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

نموذج أولي منخفض الدقة Low-fidelity Prototype

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

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

وعند عرض النموذج لا تسأل «هل يعجبك؟»، لأن الإجابة دائماً نعم، وإنما أعط الشخص مهمة: «احجزي موعداً للدكتورة هدى يوم الأربعاء صباحاً»، ثم اصمت وراقب أين يتردد، وبعدها اسأل الأسئلة التالية:

  • هل هناك معلومة تحتاجها لتختار ولا تراها في الشاشة؟
  • ماذا تتوقع أن يحدث بعد الضغط على «تأكيد الحجز»؟
  • إذا أردت إلغاء الموعد بعد يومين، من أين ستبدأ؟
  • بالنسبة للاستقبال: كيف ستحجزين لمريض على الهاتف من نفس الشاشة؟

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

وثيقة SRS من صفحة واحدة

الآن نجمع كل ما سبق في وثيقة واحدة قصيرة، والقالب التالي مبني على الأقسام التي يطلبها المعيار ISO/IEC/IEEE 29148 لمواصفة المتطلبات Requirements Specification، لكنه مختصر لمشروع صغير، والتفاصيل تبقى في القصص والجداول التي تشير إليها الوثيقة بالأرقام. انسخه كما هو واملأه:

# مواصفة المتطلبات: [اسم النظام]
الإصدار: 1.0    التاريخ: ....    الحالة: مسودة / معتمدة    المالك: ....

## 1. الهدف
[بيان المشكلة في 3 إلى 5 أسطر، ومقياس النجاح بالقيمة الحالية والمستهدفة]

## 2. النطاق
داخل النطاق: [القصص Must وShould بأرقامها]
خارج النطاق: [القائمة الصريحة]

## 3. أصحاب المصلحة
[الجدول: صاحب المصلحة، ماذا يريد، كيف نشركه، من يمثله]

## 4. القاموس
[المصطلح كما يقوله أهل العمل، الاسم في الكود، التعريف]

## 5. المتطلبات الوظيفية
[جدول القصص: الرقم، القصة، الأولوية، رابط التذكرة]

## 6. قواعد العمل
[جدول القواعد: الرقم، القاعدة، المصدر]

## 7. المتطلبات غير الوظيفية
[جدول: الرقم، الخاصية، المتطلب بالأرقام، طريقة التحقق]

## 8. القيود
[ما لا يناقش: الموعد، الميزانية، الاستضافة، الأنظمة، التقنية إن وجد سبب]

## 9. الافتراضات والاعتماديات
[جدول: الرقم، الافتراض، من يتأكد، قبل، إذا كان خطأ]

## 10. أسئلة مفتوحة
[كل سؤال، ومن يجيب، ومتى]

## 11. سجل التغييرات
| الإصدار | التاريخ | التغيير | رقم الطلب | اعتمده |
|---|---|---|---|---|
| 1.0 | .... | الإصدار الأول | - | .... |

## 12. الاعتماد
| الدور | الاسم | التاريخ | القرار |
|---|---|---|---|
| الراعي (مالك المنتج) | | | معتمد / بملاحظات |
| ممثل المستخدمين | | | |
| قائد الفريق التقني | | | |

وهذه الوثيقة نفسها بعد ملئها لمثال العيادة، ولاحظ أنها تشير إلى الأرقام ولا تكرر الجداول، والجداول تعيش في المستودع أو نظام إدارة المهام:

# مواصفة المتطلبات: نظام حجز المواعيد للعيادة
الإصدار: 1.0    التاريخ: 2026-10-12    الحالة: معتمدة    المالك: مالك العيادة

## 1. الهدف
يرى المريض الأوقات المتاحة ويحجز ويلغي بنفسه في أي وقت، ويعمل الاستقبال
والأطباء على جدول واحد محدث. النجاح: مكالمات الحجز والتغيير اليومية إلى
النصف خلال 3 أشهر من الإطلاق (القيمة الحالية تقاس في أسبوع قبل البدء).

## 2. النطاق
داخل النطاق: US-01 إلى US-06.   Could: US-07.
خارج النطاق: الدفع، التأمين، الملف الطبي، تعدد الفروع، تطبيق المتجر.

## 3. أصحاب المصلحة       انظر جدول أصحاب المصلحة (5 أطراف).
## 4. القاموس             فترة Slot، موعد Booking، حجز مبدئي، عدم حضور، جدول اليوم.
## 5. المتطلبات الوظيفية   US-01 إلى US-08 مع أولوياتها.
## 6. قواعد العمل         BR-01 إلى BR-05.
## 7. غير الوظيفية        NFR-01 إلى NFR-09.
## 8. القيود              الإطلاق قبل موسم الإجازات، الميزانية المعتمدة،
                          الاستضافة داخل المملكة، متصفح الجوال دون تثبيت.
## 9. الافتراضات          A-01 إلى A-04.
## 10. أسئلة مفتوحة       هل يحق للطبيب حجز وقت لمريض خارج ساعات عمله؟
                          (الطبيب المسؤول، قبل بدء US-04)
## 11. سجل التغييرات      1.0: الإصدار الأول.
## 12. الاعتماد           المالك، والطبيب المسؤول، وقائد الفريق التقني.

المراجعة Review والتوقيع Sign-off

التوقيع ليس إجراءً شكلياً، وإنما هو اللحظة التي يصبح فيها للمتطلبات خط أساس Baseline، أي نسخة متفق عليها يقاس عليها أي تغيير بعدها. وقد ذكر تقرير مكتب المحاسبة الحكومي الأمريكي GAO-15-238 عن HealthCare.gov أن المتطلبات لم تكن تعتمد دائماً، وكانت إحدى توصياته أن تعتمد المتطلبات بتوقيع ثلاثة أطراف أساسية هي مالك العمل وصاحب صلاحية الاعتماد في الجهة وصاحب صلاحية الاعتماد عند المقاول. وقبل التوقيع نمر على القائمة التالية مع صاحب العمل وممثل المستخدمين وقائد الفريق التقني:

# قائمة مراجعة المتطلبات قبل التوقيع

## الوضوح
- [ ] لا توجد كلمات غامضة: سريع، سهل، آمن، مرن، تقريباً، إلخ، حسب الحاجة
- [ ] كل متطلب جملة واحدة بقرار واحد، وله رقم فريد
- [ ] كل مصطلح في القاموس، ونفس الكلمة تعني نفس الشيء في كل مكان

## الاكتمال
- [ ] كل صاحب مصلحة في الخريطة له متطلبات، أو مكتوب لماذا لا
- [ ] كل قصة Must لها معايير قبول بأمثلة، تشمل الحالة العادية والحدود والأخطاء
- [ ] المتطلبات غير الوظيفية مكتوبة بالأرقام، ولكل واحد طريقة تحقق
- [ ] الخصوصية والأمن والامتثال راجعها المسؤول عنها
- [ ] خارج النطاق مكتوب صراحة

## الصحة
- [ ] كل متطلب يمثل حاجة، وليس حلاً لم نسأل عنه «لماذا؟»
- [ ] لا يوجد متطلبان متعارضان
- [ ] كل متطلب يمكن بناؤه ضمن الموعد والميزانية

## المخاطر
- [ ] كل افتراض له من يتأكد منه وموعد
- [ ] كل سؤال مفتوح له من يجيب وموعد
- [ ] النموذج الأولي راجعه مستخدمون حقيقيون

## الإدارة
- [ ] الوثيقة لها إصدار ومالك ومكان واحد يعرفه الجميع
- [ ] طريقة طلب التغيير مكتوبة ومعروفة

طلب تغيير Change Request بعد التوقيع

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

ست خطوات مرقمة من اليمين إلى اليسار: تسجيل الطلب، تحليل الأثر، القرار، تحديث الوثيقة والاختبارات، تعديل الكود حتى تنجح الاختبارات، الإطلاق وإبلاغ المستخدمين، وتحتها قائمة أثر الطلب CR-01: القاعدة BR-04 والقصة US-02 وثابت في booking.py ونص الشاشة ورسالة التذكير وسياسة المواعيد المحجوزة قبل التغيير
التتبع Traceability يجعل قائمة الأثر بحثاً عن الرقم BR-04 وليس تخميناً

الخطوة الأولى والثانية: تسجيل الطلب وتحليل أثره Impact Analysis

نسجل الطلب برقم في نفس المكان الذي تعيش فيه المتطلبات، بالقالب التالي:

# CR-01: الإلغاء قبل 24 ساعة بدل 4 ساعات
طلبه: مالك العيادة    التاريخ: 2026-10-26    المصدر: رسالة واتساب (الرابط)
الحالة: مقبول       الأولوية: Must للإصدار 1.1

## الطلب
تغيير مهلة إلغاء المريض لموعده من 4 ساعات إلى 24 ساعة قبل الموعد.

## السبب
المرضى يلغون متأخراً فلا يتسع الوقت لإعطاء الموعد لمريض آخر.

## تحليل الأثر
- المتطلبات: BR-04 (تعديل)، US-02 (معايير القبول)، SRS 1.0 -> 1.1
- الكود: ثابت CANCEL_CUTOFF في booking.py
- الاختبارات: سيناريوهان بالوسم @BR-04
- الواجهة والرسائل: نص شاشة التأكيد، ونص رسالة التذكير
- البيانات: لا يوجد تغيير في قاعدة البيانات
- الجهد: نصف يوم مع الاختبار
- سؤال للقرار: المواعيد المحجوزة قبل التغيير، بأي قاعدة تلغى؟

## القرار
مقبول. المواعيد المحجوزة قبل تاريخ الإطلاق تبقى على قاعدة 4 ساعات.
اعتمده: مالك العيادة، 2026-10-27

وتحليل الأثر هنا لم يأخذ أكثر من دقائق، لأن الرقم BR-04 موجود في كل مكان، والأمر التالي داخل مجلد lab يعطينا قائمة الأماكن في الكود والاختبارات مباشرة:

grep -rn "BR-04" features booking.py

والمخرج سوف يكون كما يلي، وهو قائمة الأثر في الكود والاختبارات: سيناريوهان في ملف Gherkin، وثابت واحد في الكود:

features/booking.feature:34:  @US-02 @BR-04
features/booking.feature:41:  @US-02 @BR-04
booking.py:1:# Booking rules of the clinic example (BR-01 .. BR-04 in the SRS).
booking.py:6:CANCEL_CUTOFF = timedelta(hours=4)     # BR-04: cancel until 4 hours before the slot

ولاحظ سؤال «المواعيد المحجوزة قبل التغيير» في تحليل الأثر، فهو قرار عمل لم يكن ليظهر لو عدل المطور الرقم في الكود مباشرة عندما وصلته الرسالة، ولو لم يسأل لاكتشف المرضى القاعدة الجديدة عندما يحاولون الإلغاء.

الخطوة الرابعة: نعدل معايير القبول Acceptance Criteria قبل الكود

بعد القرار نعدل الوثيقة إلى الإصدار 1.1، ثم ملف Gherkin قبل أي سطر كود، فنضيف الوسم @CR-01 ونغير الساعة في السيناريوهين حتى يعبرا عن القاعدة الجديدة: الإلغاء قبل الموعد ب 25 ساعة مقبول، وقبله ب 23 ساعة مرفوض:

  @US-02 @BR-04 @CR-01
  Scenario: Cancel early enough and the slot is free again
    Given patient "P-100" has booked doctor "Dr. Huda" at "2026-10-14 10:00"
    And the clock is "2026-10-13 09:00"
    When patient "P-100" cancels the booking at "2026-10-14 10:00"
    Then the slot "2026-10-14 10:00" of doctor "Dr. Huda" is free

  @US-02 @BR-04 @CR-01
  Scenario: Cancel too late
    Given patient "P-100" has booked doctor "Dr. Huda" at "2026-10-14 10:00"
    And the clock is "2026-10-13 11:00"
    When patient "P-100" cancels the booking at "2026-10-14 10:00"
    Then the cancellation is rejected with "CANCEL_TOO_LATE"

ونشغل اختبارات الطلب وحدها قبل أن نلمس الكود:

docker run --rm --name lab-req-behave -v "$PWD":/work -w /work -e PYTHONPATH=/work python:3.12-slim sh -c "pip install -q --root-user-action=ignore --disable-pip-version-check behave==1.3.3 && behave --no-color --format plain --no-timings --tags=@CR-01"

والمخرج سوف يكون كما يلي، وهذا آخر جزء منه:

  Scenario: Cancel too late
    Given the clock is "2026-10-12 09:00" ... passed
    And doctor "Dr. Huda" has a free slot at "2026-10-14 10:00" ... passed
    Given patient "P-100" has booked doctor "Dr. Huda" at "2026-10-14 10:00" ... passed
    And the clock is "2026-10-13 11:00" ... passed
    When patient "P-100" cancels the booking at "2026-10-14 10:00" ... passed
    Then the cancellation is rejected with "CANCEL_TOO_LATE" ... failed
ASSERT FAILED: expected CANCEL_TOO_LATE, got CANCELLED


Failing scenarios:
  features/booking.feature:42  Cancel too late

0 features passed, 1 failed, 0 skipped
1 scenario passed, 1 failed, 6 skipped
11 steps passed, 1 failed, 26 skipped
Took 0min 0.026s

في المخرج أعلاه لاحظ التالي:

  • الاختبار فشل لأن الكود ما زال يطبق قاعدة 4 ساعات، فقبل الإلغاء قبل الموعد ب 23 ساعة، والرسالة expected CANCEL_TOO_LATE, got CANCELLED تقول بالضبط ما الذي يختلف بين المتطلب الجديد والكود الحالي.
  • السيناريو الأول نجح حتى قبل التعديل، لأن الإلغاء قبل 25 ساعة مقبول في القاعدتين، وهذا طبيعي، فالسيناريو الذي يكشف التغيير هو الذي يقع بين الحدين القديم والجديد.
  • هذا الفشل هو ما يسميه SWEBOK في أسلوب ATDD الإذن بتعديل الكود: لا يعدل كود الإنتاج Production Code إلا وهناك اختبار قبول واحد على الأقل يفشل.

الخطوة الخامسة: نعدل الكود حتى تنجح الاختبارات

التعديل في الكود سطر واحد في booking.py، ومعه رقم الطلب في التعليق:

CANCEL_CUTOFF = timedelta(hours=24)    # BR-04 (CR-01): cancel until 24 hours before the slot

ثم نشغل كل الاختبارات وليس اختبارات الطلب وحدها، حتى نتأكد أن التغيير لم يكسر قاعدة أخرى:

docker run --rm --name lab-req-behave -v "$PWD":/work -w /work -e PYTHONPATH=/work python:3.12-slim sh -c "pip install -q --root-user-action=ignore --disable-pip-version-check behave==1.3.3 && behave --no-color --format progress"

والمخرج سوف يكون كما يلي:

USING RUNNER: behave.runner:Runner
features/booking.feature  ........

1 feature passed, 0 failed, 0 skipped
8 scenarios passed, 0 failed, 0 skipped
38 steps passed, 0 failed, 0 skipped
Took 0min 0.043s

وتبقى الخطوة السادسة خارج الكود: نحدث نص شاشة التأكيد ورسالة التذكير، ونضيف سطراً في سجل تغييرات الوثيقة «1.1، CR-01، الإلغاء قبل 24 ساعة، اعتمده المالك»، ونبلغ الاستقبال قبل الإطلاق حتى لا يتفاجأ المرضى. وبهذا الشكل يكون لكل سطر تغير في الكود سبب مكتوب ومن قرره ومتى، وإذا سأل مطور جديد بعد سنة «لماذا 24 ساعة؟» فالبحث عن CR-01 يعطيه القصة كاملة.

وعند الانتهاء لا يبقى شيء من التجربة على جهازك، لأن الخيار --rm يحذف ال Container بعد كل تشغيل، ويمكنك حذف مجلد lab إن لم تعد تحتاجه.

أخطاء شائعة عند تطبيق هذه الخطوات

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

تدريب عملي

  1. أضف قاعدة جديدة BR-06 تقول إن المريض لا يحجز أكثر من موعد واحد مفتوح مع نفس الطبيب، واكتب لها سيناريو Gherkin بالوسم @BR-06، وشغله لترى أنه يفشل، ثم عدل booking.py حتى ينجح.
  2. خذ مشروعاً حقيقياً تعمل عليه، واملأ قالب SRS من صفحة واحدة له في ساعة واحدة، ثم عد الأسئلة المفتوحة والافتراضات التي ظهرت، وأرسلها لمن يستطيع الإجابة عنها.

الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

  • جملة «نبغى نظام حجز مواعيد» تحولت إلى أسئلة أولاً، ثم إلى خطة مقابلات تشمل الاستقبال والأطباء والمرضى، وليس المالك وحده.
  • بيان المشكلة يقول من يتأثر وكيف نعرف أننا نجحنا قبل أن يذكر أي حل، والقيم الحالية تقاس ولا تخترع.
  • القصص تحمل السبب، وقواعد العمل تكتب مستقلة برقم لأنها تخدم أكثر من قصة، ومعايير القبول بأمثلة وحدود.
  • Gherkin مع behave تحول معايير القبول إلى اختبار يعمل، والوسوم مثل @BR-01 تعطيك التتبع مجاناً.
  • المتطلبات غير الوظيفية تكتب بالأرقام مع طريقة التحقق، والخصوصية والأمن يراجعها المسؤول عنها.
  • خارج النطاق والافتراضات والقيود أقسام مكتوبة صراحة، وكل افتراض له من يتأكد منه وموعد.
  • التوقيع يصنع خط الأساس، وكل تغيير بعده يمر بالتسجيل وتحليل الأثر والقرار، ثم الاختبار الذي يفشل قبل تعديل الكود.

سجل التحديثات

  • أكتوبر 2026: كتابة المقال، واختبار ملفات Gherkin بأداة behave 1.3.3 على Python 3.12.15 داخل python:3.12-slim.
Could not load content
نشرة عرب رووت | ArabRoot

معرفة تستحق مكاناً في بريدك.

مقالات مختارة وأدوات مفيدة وأفكار لمشروعك القادم، في رسالة واحدة كل أسبوع.

يمكنك إلغاء الاشتراك متى شئت. الخصوصية

تم استلام طلبك. افتح بريدك واضغط رابط التأكيد لإتمام الاشتراك.