Latest articles
JOURNAL INDEX
لغات البرمجة/2026.10.09/2 VIEWS/5 MIN READ

Property-Based Testing: How to Find Bugs Example-Based Tests Miss

عبدالرحمن ربيع علي
Abdelrahman Rabie Ali Software Engineer & AI Builder

A practical guide to property-based testing, generated inputs, edge cases, shrinking, and useful invariants in software projects.

اختبار الخصائص Property-Based Testing: كيف تكتشف أخطاء لا تراها الاختبارات التقليدية

اختبار الخصائص Property-Based Testing: كيف تكتشف أخطاء لا تراها الاختبارات التقليدية

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

يغيّر اختبار الخصائص (Property-Based Testing) طريقة التفكير: بدل أن تكتب كل نتيجة متوقعة يدويًا، تصف قاعدة يجب أن تظل صحيحة لمجموعة كبيرة من المدخلات، ثم تولّد أداة الاختبار قيمًا كثيرة وتحاول العثور على حالة تكسر القاعدة. وعندما تفشل، تحاول عادةً تقليص المدخل إلى أصغر مثال يوضح العيب.

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

من تحويل الفكرة إلى خاصية قابلة للاختبار

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

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

تجنّب الخصائص الضعيفة

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

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

إدارة العشوائية وإعادة إنتاج الأعطال

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

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

خطة اعتماد داخل الفريق

  1. ابدأ بدالة نقية أو تحويل بيانات واضح السلوك.
  2. اكتب خاصيتين أو ثلاثًا، وراجعها مع شخص يعرف متطلبات المجال.
  3. أنشئ مولّدًا يغطي القيم المعتادة والحدود والحالات الفارغة.
  4. شغّل عددًا معتدلًا من الحالات في كل طلب دمج، ثم وسّع الاختبار في التشغيل الليلي.
  5. سجّل زمن الاختبار والأعطال المكتشفة وتكلفة صيانته.
  6. وثّق الحالات التي لا تغطيها الأداة واستكملها باختبارات أمثلة أو اختبارات تكامل.

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

أمثلة عملية على خصائص مفيدة

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

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

مراجعة النتائج وتكلفة الصيانة

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

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

قائمة مراجعة قبل اعتماد الاختبار

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

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

الاختبار في الأنظمة التي تتغير حالتها

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

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

مراجعة الاختبارات ضمن طلبات الدمج

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

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

النتيجة النهائية

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

مطور يراجع شيفرة برمجية على شاشة حاسوب أثناء اختبار الخصائص
مثال بصري لبيئة تطوير واختبار البرمجيات
شاشة تعرض شيفرة برمجية لتحليل الأخطاء واختبار الحالات الحدية
تحليل الشيفرة والحالات الحدية
مطور يراجع شيفرة برمجية على حاسوب محمول أثناء اختبار جودة البرنامج
مطور يراجع شيفرة برمجية على حاسوب محمول أثناء اختبار جودة البرنامج
فريق تقني يتعاون على مراجعة البرمجيات وتحليل نتائج الاختبارات
فريق تقني يتعاون على مراجعة البرمجيات وتحليل نتائج الاختبارات

كيف تختار الأداة المناسبة؟

اختر المكتبة وفق لغة المشروع ونمط الاختبار الموجود بالفعل. في Python توفر Hypothesis مولّدات قابلة للتركيب وتقليصًا للحالات الفاشلة، بينما يوفر fast-check إمكانات مشابهة لمشاريع JavaScript وTypeScript. راجع نشاط المشروع وتوافقه مع إصدار اللغة، واقرأ أمثلة الاستخدام الرسمية قبل اعتماد الأداة. لا تضف مكتبة جديدة لمجرد أنها شائعة؛ يجب أن تحل مشكلة واضحة وأن يستطيع الفريق صيانتها.

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

متى لا يكون الاختبار بالخصائص هو الخيار الأول؟

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

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

نموذج صغير لتوثيق الخاصية

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

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

الخلاصة

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

دمج النتائج في دورة التطوير

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

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

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

وعند إضافة خاصية جديدة، اربطها بمتطلب محدد وراجعها في طلب الدمج، حتى تبقى مجموعة الاختبارات متسقة مع سلوك المنتج المتوقع ولا تتراكم فيها قواعد قديمة أو متناقضة.

واحرص على توثيق الحالات الفاشلة المهمة للرجوع إليها لاحقًا.

مقالات ذات صلة

وللتوسع في جودة البرمجيات، اقرأ أيضًا دليل TypeScript في المشاريع الكبيرة، ثم قارن مبادئ الاختبار مع هندسة تطبيقات الموبايل القابلة للتوسع والاختبار.

SHARE Share this article
عبدالرحمن ربيع علي
Written by

Abdelrahman Rabie Ali

Software Engineer & AI Builder

Full-stack software engineer and graphic designer with 4+ years building modern web applications using PHP, JavaScript, HTML, and CSS. Strong UI/UX background and advanced use of AI tools to boost development efficiency, automation, and decision-making.

Article tags

#Software Testing #Python #TypeScript #Programming
COMMENTS

Comments

Be the first to comment on this article.

Add your comment

Your comment appears after review.