لماذا BDD على Rust ليس عن اختبارات مملة
\n\nتخيل: أنت تكتب على Rust خدمة عالية التحميل. الكود آمن، سريع، ولكن عندما يصل المنتج إلى العميل، يقول: "لكنني اعتقدت أن الزر يجب أن يعمل بشكل مختلف". مألوف؟ الفجوة الكلاسيكية بين الكود والتوقعات. هنا يأتي دور BDD (التطوير القائم على السلوك) — منهجية تترجم متطلبات العمل إلى لغة يفهمها المدير ومترجم Rust على حد سواء.
\n\nفي هذه المقالة، سنشرح كيفية تطبيق BDD على Rust عمليًا. ستتعلم كيفية كتابة السيناريوهات بلغة بشرية (Gherkin)، وأتمتتها باستخدام cucumber و specs، ولماذا يجعل هذا كودك ليس فقط موثوقًا، بل شفافًا للفريق بأكمله. لا ماء زائد — فقط كود حي وأمثلة حقيقية.
ما هو BDD ولماذا Rust مثالي له
\n\nالتطوير القائم على السلوك هو تطور لـ TDD. إذا كان TDD يركز على "كيف نختبر الكود"، فإن BDD يجيب على السؤال "أي سلوك نريد رؤيته". تكتب سيناريوهات بلغة Gherkin (Given-When-Then)، والتي تكون في نفس الوقت اختبارات ووثائق.
\n\nRust — لغة تعشق الصرامة والقابلية للتنبؤ. BDD في Rust يمنح:
\n- \n
- كودًا موثقًا ذاتيًا — أي مطور يقرأ السيناريو ويفهم منطق العمل. \n
- أمانًا على مستوى الأنواع — المترجم يتحقق من أن خطواتك تتوافق مع الهيكل. \n
- تشغيلًا متوازيًا — اختبارات BDD في Rust تعمل بسرعة بفضل
asyncوتعدد الخيوط. \n
متى يكون BDD على Rust مفيدًا بشكل خاص؟
\n- \n
- خدمات API بعقود واضحة (REST, GraphQL). \n
- أنظمة ذات قواعد عمل (المالية، اللوجستيات، الألعاب). \n
- الخدمات المصغرة حيث التكامل بين المكونات مهم. \n
أدوات BDD في Rust: Cucumber و Specs
\n\nالنظام البيئي لـ Rust يقدم نهجين رئيسيين:
\n\n1. cucumber — إطار BDD الكلاسيكي
\nيسمح بكتابة السيناريوهات بـ Gherkin (ملفات .feature) وربطها بكود Rust. يدعم async، والماكروات المخصصة، والتقارير.
2. specs — بديل خفيف وسريع
\nلا يتطلب ملفات Gherkin — تُكتب السيناريوهات مباشرة في Rust باستخدام الماكروات. مثالي إذا كنت لا تريد إضافة محلل منفصل.
\n\nسنركز على cucumber، لأنه يمنح أقصى مرونة ويتوافق مع معيار BDD.
\n\nكتابة أول سيناريو BDD على Rust
\n\nلنقم بإنشاء آلة حاسبة خصم بسيطة. قاعدة العمل: إذا كان مبلغ الشراء أكبر من 1000 روبل، الخصم 10%. وإلا — 0%.
\n\nالخطوة 1. تثبيت التبعيات
\nأضف إلى Cargo.toml:
[dev-dependencies]\ncucumber = "0.20"\ntokio = { version = "1", features = ["full"] }\nasync-trait = "0.1"\n\n\nالخطوة 2. إنشاء ملف feature
\nملف tests/features/discount.feature:
Feature: حساب الخصم\n كمشتري\n أريد الحصول على خصم على المشتريات الكبيرة\n لكي أوفر المال\n\n Scenario: شراء أكبر من 1000 روبل\n Given مبلغ الشراء يساوي "1500"\n When أطلب حساب الخصم\n Then يجب أن يكون حجم الخصم "150"\n\n Scenario: شراء أقل من 1000 روبل\n Given مبلغ الشراء يساوي "500"\n When أطلب حساب الخصم\n Then يجب أن يكون حجم الخصم "0"\n\n\nالخطوة 3. كتابة الخطوات في Rust
\nأنشئ tests/steps/discount_steps.rs:
use cucumber::{given, then, when, World};\nuse async_trait::async_trait;\n\n#[derive(Debug, Default)]\nstruct DiscountWorld {\n purchase_amount: f64,\n discount: f64,\n}\n\n#[async_trait(?Send)]\nimpl World for DiscountWorld {\n // تنفيذ فارغ للتبسيط\n async fn new() -> Self {\n Self::d