اختبار في Rust: من اختبارات الوحدة إلى الاختبار القائم على الخصائص
Rust هي واحدة من اللغات القليلة التي يستبعد فيها نظام الأنواع والملكية فئات كاملة من الأخطاء في مرحلة الترجمة (سباقات البيانات، المؤشرات المعلقة، تحرير الذاكرة المزدوج). ومع ذلك، تبقى الأخطاء المنطقية، والمعالجة غير الصحيحة للحالات الحدودية، ومنطق الأعمال الخاطئ. لاكتشافها، تشكلت في نظام Rust البيئي مجموعة قوية من أدوات الاختبار - بدءًا من مشغل الاختبار المدمج إلى أطر العمل المتقدمة لتوليد بيانات الاختبار.
الأدوات المدمجة: cargo test
أداة الاختبار الأساسية مدمجة مباشرة في المترجم ومدير الحزم. لإنشاء الاختبارات، يكفي إضافة وحدة مع السمة #[cfg(test)] ووضع علامة على الدوال بالسمة #[test].
// src/lib.rspub fn add(left: u64, right: u64) -> u64 { left + right}
#[cfg(test)]mod tests { use super::*;
#[test] fn test_add() { let result = add(2, 2); assert_eq!(result, 4); }
#[test] fn test_add_negative() { // u64 لا يدعم الأرقام السالبة - هذا خطأ في الترجمة // ولكن إذا كانت الدالة تقبل i64: // assert_eq!(add(-2, 2), 0); }}التشغيل: cargo test. العلم -- --nocapture يطبع println! من الاختبارات. العلم -- --test-threads=1 يعطل التشغيل المتوازي.
اختبارات التكامل
توجد اختبارات التكامل في مجلد tests/ على مستوى جذر المشروع. كل ملف هو crate منفصل يمكنه اختبار واجهة API العامة للمكتبة.
// tests/integration_test.rsuse my_library::add;
#[test]fn test_integration() { assert_eq!(add(10, 20), 30);}لاختبار binary crates (التي تحتوي على main.rs) يمكن استخدام assert_cmd - فهو يشغل الملف الثنائي كعملية خارجية ويتحقق من stdout/stderr.
الاختبار القائم على الخصائص: proptest و quickcheck
بدلاً من كتابة عشرات حالات الاختبار يدويًا، يقوم الاختبار القائم على الخصائص بتوليد آلاف من بيانات الإدخال العشوائية والتحقق من الثوابت. إطاران رئيسيان:
- proptest - حديث، مرن، مع دعم الاستراتيجيات، التصفية، وضغط الأمثلة (shrink).
- quickcheck - أقدم، مستوحى من Haskell QuickCheck، أبسط ولكنه أقل قوة.
مثال مع proptest:
use proptest::prelude::*;
fn reverse_string(s: &str) -> String { s.chars().rev().collect()}
proptest! { #[test] fn test_reverse_twice(s in ".*") { let reversed = reverse_string(&s); let double_reversed = reverse_string(&reversed); assert_eq!(s, double_reversed); }
#[test] fn test_reverse_length(s in ".*") { let reversed = reverse_string(&s); assert_eq!(s.len(), reversed.len()); }}إذا فشل الاختبار، يجد proptest أصغر مثال مضاد (shrinking).
اختبار الكود غير المتزامن: tokio-test
بالنسبة للدوال غير المتزامنة التي تعمل مع tokio، الاختبارات العادية غير مناسبة - هناك حاجة إلى بيئة تشغيل غير متزامنة. يوفر tokio-test الماكرو #[tokio::test].
use tokio::time::{sleep, Duration};
async fn delayed_add(a: u32, b: u32) -> u32 { sleep(Duration::from_millis(10)).await; a + b}
#[tokio::test]async fn test_delayed_add() { let result = delayed_add(3, 5).await; assert_eq!(result, 8);}يمكن أيضًا استخدام futures-test للعمل مع futures و async-std.
المحاكاة (Mocks) والنماذج (Stubs): mockall و mockito
لعزل الاختبارات عن التبعيات الخارجية (قواعد البيانات، API، نظام الملفات)