الرئيسية قاعدة المعرفة البرمجة والمنطق الاختبارات في Rust: ضمان جودة البرامج الصناعية قبل النشر
البرمجة والمنطق

الاختبارات في Rust: ضمان جودة البرامج الصناعية قبل النشر

لماذا الاختبارات حاسمة في البرامج الصناعية

في المصنع، خطأ في كود المستشعر قد يعني:

  • قراءة حرارة خاطئة ← تلف معدات بقيمة آلاف الدولارات
  • إنذار لم يعمل ← إصابة عامل
  • حساب معايرة خاطئ ← منتجات معيبة بالكامل
// هل هذه الدالة صحيحة؟ بدون اختبار لا نعرف
fn celsius_to_fahrenheit(c: f64) -> f64 {
    c * 9.0 / 5.0 + 32.0
}

// خطأ بسيط: نسينا + 32 ← كل قراءات الحرارة خاطئة
fn celsius_to_fahrenheit_buggy(c: f64) -> f64 {
    c * 9.0 / 5.0 // خطأ!
}

الاختبارات تكشف الأخطاء قبل أن تصل للمصنع.

اختبارات الوحدة مع #[test]

اختبارات الوحدة توضع في نفس الملف داخل وحدة tests:

/// تحويل قراءة خام من المستشعر إلى درجة مئوية
fn raw_to_celsius(raw: u16) -> f64 {
    // المستشعر يُخرج 0-4095، يمثل -40°C إلى 125°C
    let ratio = raw as f64 / 4095.0;
    -40.0 + ratio * 165.0
}

/// تحقق من أن القراءة ضمن النطاق المقبول
fn is_valid_reading(celsius: f64) -> bool {
    celsius >= -40.0 && celsius <= 125.0
}

#[cfg(test)] // يُجمَّع فقط عند الاختبار
mod tests {
    use super::*; // استيراد كل دوال الوحدة الأصلية

    #[test]
    fn test_raw_zero_is_minimum() {
        let temp = raw_to_celsius(0);
        assert_eq!(temp, -40.0);
    }

    #[test]
    fn test_raw_max_is_maximum() {
        let temp = raw_to_celsius(4095);
        // مقارنة أعداد عشرية بهامش خطأ
        assert!((temp - 125.0).abs() < 0.01);
    }

    #[test]
    fn test_midpoint_reading() {
        let temp = raw_to_celsius(2048);
        // تقريباً منتصف النطاق
        assert!(temp > 40.0 && temp < 44.0);
    }

    #[test]
    fn test_valid_range() {
        assert!(is_valid_reading(25.0));
        assert!(is_valid_reading(-40.0));
        assert!(!is_valid_reading(-41.0));
        assert!(!is_valid_reading(126.0));
    }
}

شغّل الاختبارات: cargo test

اختبار حالات الخطأ

الأنظمة الصناعية يجب أن تتعامل مع المدخلات السيئة بأمان:

#[derive(Debug, PartialEq)]
enum CalibrationError {
    DivisionByZero,
    OutOfRange(f64),
    InsufficientPoints,
}

fn calibrate(reference: &[f64], measured: &[f64]) -> Result<f64, CalibrationError> {
    if reference.len() < 3 {
        return Err(CalibrationError::InsufficientPoints);
    }
    let sum_measured: f64 = measured.iter().sum();
    if sum_measured == 0.0 {
        return Err(CalibrationError::DivisionByZero);
    }
    let sum_reference: f64 = reference.iter().sum();
    let factor = sum_reference / sum_measured;
    if factor < 0.5 || factor > 2.0 {
        return Err(CalibrationError::OutOfRange(factor));
    }
    Ok(factor)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_valid_calibration() {
        let reference = vec![100.0, 200.0, 300.0];
        let measured = vec![98.0, 196.0, 294.0];
        let factor = calibrate(&reference, &measured).unwrap();
        assert!((factor - 1.02).abs() < 0.01);
    }

    #[test]
    fn test_insufficient_points() {
        let result = calibrate(&[1.0, 2.0], &[1.0, 2.0]);
        assert_eq!(result, Err(CalibrationError::InsufficientPoints));
    }

    #[test]
    #[should_panic(expected = "يجب ألا تكون القائمة فارغة")]
    fn test_empty_input_panics() {
        // دالة أخرى تستخدم panic بدلاً من Result
        validate_readings(&[]); // تُطلق panic إذا كانت فارغة
    }
}

اختبارات التكامل في مجلد tests/

اختبارات التكامل تعيش في مجلد tests/ وتختبر تفاعل الوحدات معاً:

my_project/
├── src/
│   ├── sensor.rs
│   ├── alarm.rs
│   └── lib.rs
└── tests/
    └── alarm_integration.rs  ← اختبار تكامل
// tests/alarm_integration.rs
// يستورد المكتبة كمستخدم خارجي
use my_project::{Sensor, AlarmEngine, AlarmLevel};

#[test]
fn test_critical_temperature_triggers_alarm() {
    let sensor = Sensor::new(1, "حرارة الفرن");
    let mut engine = AlarmEngine::new();
    engine.add_rule(sensor.id, 90.0, AlarmLevel::Critical);

    // محاكاة قراءة عالية
    let reading = sensor.simulate_reading(95.0);
    let alarms = engine.evaluate(&reading);

    assert_eq!(alarms.len(), 1);
    assert_eq!(alarms[0].level, AlarmLevel::Critical);
}

#[test]
fn test_normal_reading_no_alarm() {
    let sensor = Sensor::new(1, "حرارة الفرن");
    let mut engine = AlarmEngine::new();
    engine.add_rule(sensor.id, 90.0, AlarmLevel::Critical);

    let reading = sensor.simulate_reading(70.0);
    let alarms = engine.evaluate(&reading);

    assert!(alarms.is_empty());
}

اختبارات التوثيق: توثيق يُجمَّع

أمثلة التوثيق في Rust تُنفَّذ كاختبارات — التوثيق لا يصبح قديماً أبداً:

/// تحويل ضغط من PSI إلى Bar
///
/// # أمثلة
///
/// ```
/// use my_project::psi_to_bar;
///
/// let bar = psi_to_bar(14.5);
/// assert!((bar - 1.0).abs() < 0.01);
///
/// // صفر يبقى صفراً
/// assert_eq!(psi_to_bar(0.0), 0.0);
/// ```
///
/// # ذعر
///
/// تُطلق panic إذا كانت القيمة سالبة:
/// ```should_panic
/// use my_project::psi_to_bar;
/// psi_to_bar(-1.0); // ضغط سالب غير ممكن فيزيائياً
/// ```
pub fn psi_to_bar(psi: f64) -> f64 {
    if psi < 0.0 { panic!("ضغط سالب غير صالح"); }
    psi * 0.0689476
}

شغّلها: cargo test --doc

تنظيم الاختبارات وأعلام cargo test

# تشغيل جميع الاختبارات
cargo test

# تشغيل اختبار محدد بالاسم
cargo test test_critical_temperature

# تشغيل اختبارات تحتوي كلمة "calibr"
cargo test calibr

# إظهار مخرجات println في الاختبارات
cargo test -- --nocapture

# تشغيل اختبارات الوحدة فقط (بدون التكامل)
cargo test --lib

# تشغيل اختبارات التكامل فقط
cargo test --test alarm_integration

# تشغيل الاختبارات على خيط واحد (مفيد لاختبارات تتشارك موارد)
cargo test -- --test-threads=1

مثال عملي: اختبار وحدة معايرة المستشعرات

pub struct CalibrationPoint {
    pub reference: f64,
    pub measured: f64,
}

pub struct CalibrationProfile {
    points: Vec<CalibrationPoint>,
    factor: Option<f64>,
}

impl CalibrationProfile {
    pub fn new() -> Self {
        Self { points: Vec::new(), factor: None }
    }

    pub fn add_point(&mut self, reference: f64, measured: f64) {
        self.points.push(CalibrationPoint { reference, measured });
        self.factor = None; // إعادة حساب مطلوبة
    }

    pub fn compute(&mut self) -> Result<f64, String> {
        if self.points.len() < 3 {
            return Err("نقاط غير كافية (الحد الأدنى 3)".into());
        }
        let sum_r: f64 = self.points.iter().map(|p| p.reference).sum();
        let sum_m: f64 = self.points.iter().map(|p| p.measured).sum();
        if sum_m.abs() < f64::EPSILON {
            return Err("مجموع القياسات صفر".into());
        }
        let f = sum_r / sum_m;
        self.factor = Some(f);
        Ok(f)
    }

    pub fn apply(&self, raw: f64) -> Option<f64> {
        self.factor.map(|f| raw * f)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn sample_profile() -> CalibrationProfile {
        let mut p = CalibrationProfile::new();
        p.add_point(100.0, 98.0);
        p.add_point(200.0, 196.0);
        p.add_point(300.0, 294.0);
        p
    }

    #[test]
    fn test_compute_factor() {
        let mut profile = sample_profile();
        let factor = profile.compute().unwrap();
        assert!((factor - 1.0204).abs() < 0.001);
    }

    #[test]
    fn test_apply_correction() {
        let mut profile = sample_profile();
        profile.compute().unwrap();
        let corrected = profile.apply(50.0).unwrap();
        assert!(corrected > 50.0); // المعايرة ترفع القيمة قليلاً
    }

    #[test]
    fn test_apply_before_compute() {
        let profile = CalibrationProfile::new();
        assert_eq!(profile.apply(50.0), None);
    }

    #[test]
    fn test_not_enough_points() {
        let mut profile = CalibrationProfile::new();
        profile.add_point(100.0, 98.0);
        assert!(profile.compute().is_err());
    }
}

الخلاصة

  • اختبارات الوحدة #[test] تتحقق من كل دالة على حدة
  • #[should_panic] يختبر أن الكود يرفض المدخلات الخاطئة
  • اختبارات التكامل في tests/ تتحقق من تفاعل الوحدات
  • اختبارات التوثيق تضمن أن الأمثلة في التوثيق تعمل فعلاً
  • cargo test مع أعلامه يوفر مرونة كاملة في تشغيل الاختبارات
  • في الدرس الأخير: نجمع كل ما تعلمناه في مشروع صناعي متكامل
testing unit-test integration-test assert cargo-test TDD الاختبارات اختبار الوحدة اختبار التكامل التأكيد ضمان الجودة التطوير بالاختبار