الاختبار القائم على الخصائص في Rust: كيفية اكتشاف الأخطاء التي لم تبحث عنها

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

الاختبار القائم على الخصائص في Rust: فن اكتشاف الأخطاء التي لم تبحث عنها

\n\n

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

\n\n

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

\n\n

ما هو الاختبار القائم على الخصائص وكيف يختلف عن الاختبار العادي؟

\n\n

تخيل أنك تختبر دالة فرز. يبدو اختبار الوحدة العادي كالتالي:

\n\n
fn test_sort() {\n    let mut arr = vec![3, 1, 2];\n    arr.sort();\n    assert_eq!(arr, vec![1, 2, 3]);\n}
\n\n

هل يعمل؟ يعمل. ولكن ماذا لو كانت المصفوفة مرتبة بالفعل؟ ماذا لو كانت فارغة؟ ماذا لو كانت تحتوي على 10,000 عنصر بترتيب عكسي؟ كتابة اختبارات لكل حالة يدويًا هو طريق لا نهاية له.

\n\n

الاختبار القائم على الخصائص يقلب النهج. بدلاً من تحديد بيانات إدخال محددة، تصف خاصية يجب أن تتحقق لأي إدخال صحيح. والمكتبة المتخصصة (مثل proptest أو quickcheck) تقوم بتوليد مئات وآلاف من حالات الاختبار العشوائية، محاولة العثور على تلك الحالة الوحيدة التي تنتهك خاصيتك.

\n\n

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

\n\n

لماذا Rust هي البيئة المثالية للاختبار القائم على الخصائص

\n\n

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

\n\n

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

\n\n

الأدوات: proptest مقابل quickcheck

\n\n

في نظام Rust البيئي، هناك إطاران رئيسيان: proptest و quickcheck. كلاهما يفعل نفس الشيء، ولكن بفلسفة مختلفة.

\n\n
    \n
  • quickcheck - أقدم، وأكثر بساطة. يعمل من خلال trait Arbitrary. تكتب دالة تأخذ بيانات عشوائية، ويتم تشغيل الاختبار عدة مرات.
  • \n
  • proptest - حديث، وقوي، مع العديد من استراتيجيات توليد البيانات. يسمح بضبط دقيق لنوع البيانات التي يتم توليدها، ويمكنه "تقليص" المثال الفاشل إلى أصغر حالة قابلة للتكرار. هذا هو المعيار الذهبي اليوم.
  • \n
\n\n

سنستخدم proptest لأنه يعطي تحكمًا أكبر وموثقًا بشكل أفضل. سنقوم بتثبيته:

\n\n
# Cargo.toml\n[dev-dependencies]\nproptest = "1.4"
\n\n

كتابة أول اختبار قائم على الخصائص في Rust

\n\n

لنعد إلى الفرز الخاص بنا. لنكتب اختبارًا يتحقق من أن النتيجة مرتبة بالفعل:

\n\n
use proptest::prelude::*;\n\nfn is_sorted<T: Ord>(slice: &[T]) -> bool {\n    slice.windows(2).all(|w| w[0] <= w[1])\n}\n\nproptest!                

المدونات

توصيات الكتب