Rust: اختبار لغة «Rust» وتوثيقها
آخر تحديث: 2026-08-26
الاختبار هو «مفتش الجودة» للكود — فليكتشف الاختبار الأخطاء قبل أن يكتشفها المستخدمون. أما التوثيق فهو «دليل الاستخدام» للكود — فهو لا يكتفي بإرشاد الآخرين إلى كيفية استخدامه فحسب، بل يذكرك أنت أيضًا في المستقبل بالسبب الذي دفعك إلى كتابته بهذه الطريقة.
إذا كان كتابة الكود أشبه بـ«بناء منزل»، فإن الاختبار هو «تقرير فحص الجودة»، والتوثيق هو «دليل المستخدم». فهل تجرؤ على الانتقال إلى منزل لم يخضع للاختبار؟ المفهوم الأساسي لـ TDD (التطوير القائم على الاختبار) بسيط: اكتب الاختبارات أولاً، ثم الكود — تمامًا مثل وضع معايير الجودة قبل تصنيع المنتج.
1. ما ستتعلمه
#[test]وظيفة اختبار علامة الخاصية،cargo testتشغيل مجموعة الاختباراتassert!/assert_eq!/assert_ne!ماكروات التأكيد للتحقق من السلوك المتوقع#[should_panic]اختبار سيناريوهات الذعر المتوقعة#[cfg(test)]وحدة اختبار التجميع الشرطي؛ يتم استبعادها تلقائيًا من إصدارات الإنتاج///التعليقات التوضيحية على المستندات وcargo docإنشاء مستندات HTML- اختبار التكامل (في الدليل
tests/) ومفاهيم اختبار الأداء
2. قصة أحد مفتشي الجودة
(1) الصراع: المصانع التي تفتقر إلى مراقبة الجودة
يعمل بوب كمفتش لمراقبة الجودة في مصنع للسيارات. وبعد فترة وجيزة من بدء عمله، اكتشف حقيقة مروعة:
- ينتج المصنع 500 سيارة يوميًا، لكن لا تمر أي منها بفحص الجودة
- اشتكى أحد العملاء قائلاً: «لقد اشتريت سيارة للتو، وعجلة القيادة مربعة الشكل!»
- قال مشرف مراقبة الجودة: «لا بأس؛ سيقوم العميل بإصلاحه بنفسه».
- والأمر الأكثر سخافة هو أن «تعليمات» المصنع تتمثل في ورقة مجعدة مكتوب عليها: «ادوس على القطعة المستديرة، وأدر القطعة الطويلة».
«لو خضعت كل سيارة للاختبار قبل خروجها من المصنع، لما حصل العملاء في النهاية على عجلات قيادة مربعة الشكل...»
(2) مناهج الاختبار والتوثيق في لغة Rust
توفر لغة «Rust» مجموعة كاملة من أدوات الاختبار والتوثيق، تمامًا مثل نظام إدارة الجودة في مصنع يُدار بشكل جيد:
Factory(Rust Project) → Automobile Production Line
Unit Testing(Unit Test) → Check the quality of each part
Document Testing(Doc Test) → Examples included in the manual
Integration Testing(Integration Test)→ Vehicle Road Testing
Benchmarking(Benchmark) → Performance Testing
Document Notes(cargo doc) → Complete User Manual
/// Calculate the greatest common divisor of two numbers(GCD)
///
/// Using the Euclidean Algorithm:Repeat the modulo operation until the remainder is 0
///
/// # Example
///
/// ```
/// let result = gcd(12, 8);
/// assert_eq!(result, 4);
/// ```
fn gcd(a: u64, b: u64) -> u64 {
if b == 0 { a } else { gcd(b, a % b) }
}
#[test]
fn test_gcd() {
assert_eq!(gcd(12, 8), 4);
assert_eq!(gcd(7, 13), 1); // Coprime Numbers
assert_eq!(gcd(100, 10), 10);
}
يشبه إطار عمل الاختبار في لغة Rust خط تجميع لمراقبة الجودة:
#[test]يحدد كل «محطة لمراقبة الجودة»، وcargo testيبدأ تشغيل خط التجميع بأكمله، وassert_eq!يمثل معايير مراقبة الجودة. تشبه تعليقات التوثيق///«التعليمات» المرفقة بكل جزء، بينما يجمعcargo docجميع التعليمات في كتيب واحد.
3. المفاهيم الأساسية
(1) إطار عمل الاختبار والتوثيق
graph TB
A[Rust Testing and Documentation] --> B[Test System]
A --> C[Document Management System]
B --> B1["#[test] Mark the test function"]
B --> B2["cargo test Run Test"]
B --> B3["Assertion Macro assert! / assert_eq! / assert_ne!"]
B --> B4["#[should_panic] Test Anxiety"]
B --> B5["#[cfg(test)] Conditional Compilation"]
B --> B6["tests/ Integration Test Directory"]
C --> C1["/// Document Notes"]
C --> C2["cargo doc Generate HTML"]
C --> C3["Document Testing Doc-tests"]
C --> C4["cargo doc --open Open your browser"]
B1 --> D1[Unit Testing]
B6 --> D2[Integration Testing]
C1 --> D3[API Document]
C3 --> D4[Working sample code]
(2) مقارنة بين أنواع الاختبارات
| نوع الاختبار | الكلمة المفتاحية/الموقع | نطاق الاختبار | القياس | السيناريوهات القابلة للتطبيق |
|---|---|---|---|---|
| اختبار الوحدة | #[test] + #[cfg(test)] |
الوظائف أو الوحدات النمطية الفردية | فحص كل مكون | التحقق من منطق الوظيفة |
| اختبار الوثائق | كتل الكود في /// |
أمثلة كود واجهة برمجة التطبيقات (API) | الأمثلة الواردة في الدليل | التأكد من أن الأمثلة الواردة في الوثائق تعمل بشكل صحيح |
| اختبار التكامل | الملف .rs الموجود في الدليل tests/ |
السلوك العام لواجهات برمجة التطبيقات الخارجية | اختبار المركبة على الطريق | التحقق من التعاون بين الوحدات |
| معيار الأداء | #[bench] / cargo bench |
مقاييس الأداء | اختبار السرعة | كود حساس للأداء |
(3) مقارنة ماكروات التأكيد
| الماكرو | الغرض | شروط النجاح | رسالة الفشل |
|---|---|---|---|
assert!(expr) |
الشروط المنطقية | expr == true |
assertion failed: expr |
assert_eq!(a, b) |
التكافؤ | a == b |
assertion failed: (left == right) |
assert_ne!(a, b) |
عدم التماثل | a != b |
assertion failed: (left != right) |
4. أمثلة على الاختبار والتوثيق
(1) ▶ المثال:الاختبارات الوحدوية ووحدات ماكرو التأكيد (مستوى الصعوبة ⭐)
// ============================================
// Unit Testing Basics: #[test] + Assertion Macro
// Demo: A Simple Calculator Function and Its Tests
// Quality control inspectors check each calculation function
// ============================================
/// Addition: Returns a + b
fn add(a: i32, b: i32) -> i32 {
a + b
}
/// Subtraction: Returns a - b
fn subtract(a: i32, b: i32) -> i32 {
a - b
}
/// Division: Returns a / b, panics if b == 0
fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("division by zero is not allowed!");
}
a / b
}
/// Determining Whether a Number Is Even
fn is_even(n: i32) -> bool {
n % 2 == 0
}
/// Find the Maximum Value
fn max_of_three(a: i32, b: i32, c: i32) -> i32 {
let mut max = a;
if b > max { max = b; }
if c > max { max = c; }
max
}
fn main() {
println!("=== Calculator Feature Demo ===");
println!("add(10, 5) = {}", add(10, 5));
println!("subtract(10, 5) = {}", subtract(10, 5));
println!("divide(10, 3) = {}", divide(10, 3));
println!("is_even(7) = {}", is_even(7));
println!("max_of_three(3, 7, 5) = {}", max_of_three(3, 7, 5));
println!();
println!("Run `cargo test` Run all the tests");
}
// ============================================
// Test Module —— Only at cargo test Compiled by Shi
// ============================================
#[cfg(test)]
mod tests {
// Import all functions from the parent module
use super::*;
#[test]
fn test_add_positive() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-2, -3), -5);
}
#[test]
fn test_add_zero() {
assert_eq!(add(0, 0), 0);
}
#[test]
fn test_subtract() {
assert_eq!(subtract(10, 4), 6);
assert_eq!(subtract(4, 10), -6); // Negative results
}
#[test]
fn test_divide_normal() {
assert_eq!(divide(10, 3), 3); // Truncated Integer Division
}
#[test]
#[should_panic(expected = "division by zero is not allowed!")]
fn test_divide_by_zero() {
divide(10, 0); // Expectations panic
}
#[test]
fn test_is_even() {
assert!(is_even(4)); // 4 It is an even number
assert!(!is_even(7)); // 7 Not an even number
assert_eq!(is_even(0), true);
}
#[test]
fn test_max_of_three() {
assert_eq!(max_of_three(1, 2, 3), 3);
assert_eq!(max_of_three(5, 1, 2), 5);
assert_eq!(max_of_three(1, 5, 2), 5);
assert_eq!(max_of_three(-1, -5, -3), -1);
}
#[test]
fn test_assert_ne_macro() {
// assert_ne! Verify that two values are not equal
assert_ne!(add(1, 1), 3);
assert_ne!(subtract(100, 50), 100);
}
}
الناتج:
=== Calculator Feature Demo ===
add(10, 5) = 15
subtract(10, 5) = 5
divide(10, 3) = 3
is_even(7) = false
max_of_three(3, 7, 5) = 7
Run `cargo test` Run all the tests
نتيجة الاختبار (cargo test):
running 9 tests
test tests::test_assert_ne_macro ... ok
test tests::test_add_negative ... ok
test tests::test_add_positive ... ok
test tests::test_add_zero ... ok
test tests::test_divide_by_zero ... ok
test tests::test_divide_normal ... ok
test tests::test_is_even ... ok
test tests::test_max_of_three ... ok
test tests::test_subtract ... ok
test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
وحدة الاختبار محاطة بعلامة
#[cfg(test)]، والتي تعني «التجميع في وضع الاختبار فقط» — ولن يظهر هذا الكود على الإطلاق في إصدارات الإنتاج (cargo build --release). يستوردuse super::*جميع الدوال من الوحدة النمطية الأم (أي الكود الرئيسي في الملف الحالي). يتحقق#[should_panic(expected = "...")]من أن الدالة ستُطلق حالة الذعر بشكل صحيح في ظل ظروف محددة.
(2) ▶ المثال:التعليقات التوضيحية على المستندات ووثيقة «cargo doc» (مستوى الصعوبة: ⭐⭐)
// ============================================
// Document Notes /// and cargo doc Generate HTML Document
// Demo: Write a complete API Document
// Includes document testing (doc-tests)
// ============================================
/// String Processing Toolkit
///
/// Provides common string manipulation functions,Including reversals、Excerpt、Statistics, etc.。
/// All functions are pure functions (do not modify the input, return a new value).
///
/// # Example
///
/// ```
/// use docs_demo::string_utils;
///
/// let reversed = string_utils::reverse("hello");
/// assert_eq!(reversed, "olleh");
/// ```
pub mod string_utils {
/// Reverse a String
///
/// Reverse the order of the characters in the input string。
///
/// # Parameters
///
/// * `s` - The string slice to be reversed
///
/// # Return Value
///
/// Return the new value after reversal `String`。
///
/// # Example
///
/// ```
/// use docs_demo::string_utils::reverse;
///
/// assert_eq!(reverse("rust"), "tsur");
/// assert_eq!(reverse(""), "");
/// assert_eq!(reverse("a"), "a");
/// ```
pub fn reverse(s: &str) -> String {
s.chars().rev().collect()
}
/// Count the number of words in a string
///
/// Count the number of non-empty words, separated by spaces。
///
/// # Parameters
///
/// * `s` - Strings to be counted
///
/// # Return Value
///
/// Back `usize` Number of Words by Type。
///
/// # Example
///
/// ```
/// use docs_demo::string_utils::word_count;
///
/// assert_eq!(word_count("hello world"), 2);
/// assert_eq!(word_count(""), 0);
/// assert_eq!(word_count(" spaces "), 0); // Only spaces
/// ```
pub fn word_count(s: &str) -> usize {
s.split_whitespace().count()
}
/// Extract the first part of a string n characters
///
/// Safe Handling UTF-8 Character Boundary,No panic。
/// If `n` Greater than the string length,Return the entire string。
///
/// # Parameters
///
/// * `s` - Original string
/// * `n` - Number of characters to extract
///
/// # Return Value
///
/// Returns the sliced string。
///
/// # Example
///
/// ```
/// use docs_demo::string_utils::truncate;
///
/// assert_eq!(truncate("hello world", 5), "hello");
/// assert_eq!(truncate("Hello", 2), "He");
/// assert_eq!(truncate("short", 100), "short");
/// ```
pub fn truncate(s: &str, n: usize) -> &str {
// Usage char_indices Safe Handling UTF-8 Boundary
if n >= s.chars().count() {
return s;
}
let end = s.char_indices()
.nth(n)
.map(|(idx, _)| idx)
.unwrap_or(s.len());
&s[..end]
}
/// Check if a string is a palindrome
///
/// Ignore case and spaces,Compare only alphanumeric characters。
///
/// # Parameters
///
/// * `s` - String to be checked
///
/// # Return Value
///
/// Returns true if the string is a palindrome `true`。
///
/// # Example
///
/// ```
/// use docs_demo::string_utils::is_palindrome;
///
/// assert!(is_palindrome("racecar"));
/// assert!(is_palindrome("A man a plan a canal Panama"));
/// assert!(!is_palindrome("hello"));
/// ```
pub fn is_palindrome(s: &str) -> bool {
let cleaned: String = s.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect();
cleaned == cleaned.chars().rev().collect::<String>()
}
}
fn main() {
use string_utils::*;
println!("=== String Library Demo ===");
println!("reverse('rust') = '{}'", reverse("rust"));
println!("word_count('hello world from Rust') = {}", word_count("hello world from Rust"));
println!("truncate('hello world', 5) = '{}'", truncate("hello world", 5));
println!("truncate('Hello, World', 2) = '{}'", truncate("Hello, World", 2));
println!("is_palindrome('racecar') = {}", is_palindrome("racecar"));
println!("is_palindrome('A man a plan a canal Panama') = {}", is_palindrome("A man a plan a canal Panama"));
println!("is_palindrome('hello') = {}", is_palindrome("hello"));
println!();
println!("Usage `cargo doc --open` Generate and View HTML Document");
println!("Usage `cargo test` Run the documentation tests (doc-tests)");
}
// ============================================
// Unit Testing —— Implementation of the Verification Function
// ============================================
#[cfg(test)]
mod tests {
use super::string_utils::*;
#[test]
fn test_reverse() {
assert_eq!(reverse("hello"), "olleh");
assert_eq!(reverse("Rust"), "tsuR");
assert_eq!(reverse(""), "");
}
#[test]
fn test_word_count() {
assert_eq!(word_count("one two three"), 3);
assert_eq!(word_count(""), 0);
assert_eq!(word_count(" "), 0);
assert_eq!(word_count("a"), 1);
}
#[test]
fn test_truncate() {
assert_eq!(truncate("hello world", 5), "hello");
assert_eq!(truncate("Hello, World", 2), "Hello");
assert_eq!(truncate("short", 10), "short");
}
#[test]
fn test_is_palindrome() {
assert!(is_palindrome("racecar"));
assert!(is_palindrome("level"));
assert!(is_palindrome("A man a plan a canal Panama"));
assert!(!is_palindrome("hello"));
assert!(is_palindrome(""));
}
}
الناتج:
=== String Library Demo ===
reverse('rust') = 'tsur'
word_count('hello world from Rust') = 4
truncate('hello world', 5) = 'hello'
truncate('Hello, World', 2) = 'Hello'
is_palindrome('racecar') = true
is_palindrome('A man a plan a canal Panama') = true
is_palindrome('hello') = false
Usage `cargo doc --open` Generate and View HTML Document
Usage `cargo test` Run the documentation tests (doc-tests)
///تستخدم تعليقات التوثيق تنسيق Markdown وتدعم أقسامًا مثل# Examplesو# Parametersو# Return Value. تتحول أمثلة الكود الموجودة في كتل الكود تلقائيًّا إلى «اختبارات توثيقية» — حيث يقومcargo testبترجمة هذه الكتل وتشغيلها للتأكد من أن الأمثلة الواردة في التوثيق تعمل دائمًا بشكل صحيح.cargo doc --openيُنشئ وثائق HTML جميلة ويفتحها في متصفح.
(3) ▶ المثال:اختبار التكامل وتنظيم الاختبارات (مستوى الصعوبة ⭐⭐)
// ============================================
// Testing Organization: Module Testing + Integration Test Directory Structure
// Demo: A "Statistical Tools" Library Testing Layers
// Note: Integration Testing requires standalone files in the tests/ directory
// This example simulates two types of tests in a single file.
// ============================================
/// Statistical Tools: Calculate various statistical indicators
pub mod stats {
/// Calculate the sum of a set of numbers
pub fn sum(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
/// Calculate the average of a set of numbers
/// If the set is empty,Back 0.0
pub fn average(numbers: &[i32]) -> f64 {
if numbers.is_empty() {
return 0.0;
}
sum(numbers) as f64 / numbers.len() as f64
}
/// Calculate the median of a set of numbers
/// If the set is empty,Back None
pub fn median(numbers: &mut [i32]) -> Option<f64> {
if numbers.is_empty() {
return None;
}
numbers.sort();
let len = numbers.len();
if len % 2 == 0 {
// Even count: Take the average of the middle two
let mid = len / 2;
Some((numbers[mid - 1] + numbers[mid]) as f64 / 2.0)
} else {
// Odd count: Take the one in the middle
Some(numbers[len / 2] as f64)
}
}
/// Calculate the minimum value of a set of numbers
pub fn min(numbers: &[i32]) -> Option<i32> {
numbers.iter().min().copied()
}
/// Find the maximum value of a set of numbers
pub fn max(numbers: &[i32]) -> Option<i32> {
numbers.iter().max().copied()
}
}
fn main() {
use stats::*;
println!("=== Statistical Tools Demo ===");
let data = [3, 1, 4, 1, 5, 9, 2, 6];
println!("Data: {:?}", &data[..]);
println!("sum = {}", sum(&data));
println!("average = {:.2}", average(&data));
println!("median = {:.1}", median(&mut data.to_vec()).unwrap());
println!("min = {:?}", min(&data).unwrap());
println!("max = {:?}", max(&data).unwrap());
println!();
println!("Integration test files should be placed in tests/ Under the directory,For example:");
println!(" tests/stats_integration_test.rs");
println!("Run `cargo test --test stats_integration_test` Test a Specific File");
}
// ============================================
// Unit Testing
// ============================================
#[cfg(test)]
mod tests {
use super::stats::*;
#[test]
fn test_sum() {
assert_eq!(sum(&[1, 2, 3, 4, 5]), 15);
assert_eq!(sum(&[]), 0);
assert_eq!(sum(&[-1, 0, 1]), 0);
}
#[test]
fn test_average() {
let result = average(&[1, 2, 3, 4, 5]);
assert!((result - 3.0).abs() < f64::EPSILON);
assert_eq!(average(&[]), 0.0);
}
#[test]
fn test_median_odd() {
let mut data = [3, 1, 4, 1, 5];
assert_eq!(median(&mut data), Some(3.0));
}
#[test]
fn test_median_even() {
let mut data = [1, 2, 3, 4];
assert_eq!(median(&mut data), Some(2.5));
}
#[test]
fn test_median_empty() {
let mut data: [i32; 0] = [];
assert_eq!(median(&mut data), None);
}
#[test]
fn test_min_max() {
let data = [3, -1, 7, 0, 42, -5];
assert_eq!(min(&data), Some(-5));
assert_eq!(max(&data), Some(42));
}
}
// ============================================
// Simulated Integration Testing (In a real Rust project,
// This should go in a separate file within the tests/ directory)
// ============================================
// The following content simulates tests/stats_integration_test.rs:
//
// use my_stats_lib::stats;
//
// #[test]
// fn test_integration_sum_and_average() {
// let data = [10, 20, 30, 40, 50];
// assert_eq!(stats::sum(&data), 150);
// assert!((stats::average(&data) - 30.0).abs() < f64::EPSILON);
// }
//
// #[test]
// fn test_integration_median_workflow() {
// // Testing a Typical Data Analysis Workflow
// let mut data = [100, 5, 50, 25, 75];
// let med = stats::median(&mut data);
// assert_eq!(med, Some(50.0));
// }
//
// #[test]
// fn test_integration_empty_data() {
// let data: [i32; 0] = [];
// assert_eq!(stats::sum(&data), 0);
// assert_eq!(stats::average(&data), 0.0);
// assert_eq!(stats::min(&data), None);
// assert_eq!(stats::max(&data), None);
// }
الناتج:
=== Statistical Tools Demo ===
Data: [3, 1, 4, 1, 5, 9, 2, 6]
sum = 31
average = 3.88
median = 3.5
min = 1
max = 9
Integration test files should be placed in tests/ Under the directory,For example:
tests/stats_integration_test.rs
Run `cargo test --test stats_integration_test` Test a Specific File
توجد اختبارات التكامل في المجلد
tests/داخل الدليل الجذري للمشروع، ويمثل كل ملف.rsمكتبة منفصلة. لا يمكن لاختبارات التكامل إلا اختبار واجهة برمجة التطبيقات (API) العامة للمكتبة (الواجهات المُشار إليها بـpub) ولا يمكنها الوصول إلى الدوال الخاصة. وهذا يحاكي سيناريو «استخدام المستخدمين الخارجيين لمكتبتك». يتيح لكcargo test --test filenameتشغيل ملفات اختبار التكامل المحددة فقط.
(4) ▶ المثال:مفاهيم المقارنة المعيارية (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// Benchmarking (Benchmark) Concept Demo
// Using #[bench] and Bencher (Requires nightly Rust)
// Note: Rust Stable Version uses the criterion crate for benchmark testing
// Here, we'll use a"Manual Timing"A method for simulating benchmark testing concepts
// ============================================
use std::time::Instant;
// ============================================
// Comparing the Performance of Two Sorting Algorithms
// ============================================
/// Bubble Sort (O(n^2) - Slow)
fn bubble_sort(arr: &mut [i32]) {
let n = arr.len();
for i in 0..n {
for j in 0..n - 1 - i {
if arr[j] > arr[j + 1] {
arr.swap(j, j + 1);
}
}
}
}
/// Quick Sort (O(n log n) - Fast)
fn quick_sort(arr: &mut [i32]) {
if arr.len() <= 1 {
return;
}
let pivot = partition(arr);
quick_sort(&mut arr[..pivot]);
quick_sort(&mut arr[pivot + 1..]);
}
fn partition(arr: &mut [i32]) -> usize {
let len = arr.len();
let pivot = arr[len - 1];
let mut i = 0;
for j in 0..len - 1 {
if arr[j] <= pivot {
arr.swap(i, j);
i += 1;
}
}
arr.swap(i, len - 1);
i
}
/// Manual Benchmarking Functions
fn bench_sort<F>(name: &str, mut sort_fn: F, data: &[i32], iterations: u32)
where
F: FnMut(&mut [i32]),
{
let mut total_duration = std::time::Duration::new(0, 0);
for _ in 0..iterations {
let mut cloned = data.to_vec();
let start = Instant::now();
sort_fn(&mut cloned);
total_duration += start.elapsed();
}
let avg = total_duration / iterations;
println!(" {}: Average {}.{:03} microsecond ({} Next iteration)",
name,
avg.as_micros(),
avg.as_nanos() % 1_000,
iterations);
}
fn main() {
println!("=== Benchmark Demo: Performance Comparison of Sorting Algorithms ===\n");
// Generate Random Data
let data_sizes = [100, 500, 1000];
for &size in &data_sizes {
// Generate a random array
let data: Vec<i32> = (0..size).map(|i| {
// Simulating Random Numbers Using a Simple Linear Congruential Generator
((i * 1234567 + 987654) % 100000) as i32
}).collect();
println!("Data Volume: {} element", size);
let iterations = if size <= 100 { 100 } else { 10 };
bench_sort("Bubble Sort", |arr| bubble_sort(arr), &data, iterations);
bench_sort("Quick Sort", |arr| quick_sort(arr), &data, iterations);
println!();
}
println!("=== Conclusion ===");
println!("Bubble Sort O(n^2) It slows down noticeably when dealing with large amounts of data");
println!("Quick Sort O(n log n) Significant performance advantages with large data sets");
println!();
println!("Rust nightly Version Usage `cargo bench` Run a benchmark test");
println!("We recommend using the stable version `criterion` crate Conduct a benchmark test");
}
// ============================================
// Simulated nightly Rust Benchmarking (For reference only)
// Requires #![feature(test)] and extern crate test;
// ============================================
// The following code is available in nightly Rust:
//
// #![cfg(test)]
// #![feature(test)]
// extern crate test;
//
// #[cfg(test)]
// mod bench_tests {
// use super::*;
// use test::Bencher;
//
// #[bench]
// fn bench_bubble_sort_100(b: &mut Bencher) {
// let data = vec![5, 3, 1, 4, 2, 7, 6, 9, 8, 0];
// b.iter(|| {
// let mut arr = data.clone();
// bubble_sort(&mut arr);
// });
// }
//
// #[bench]
// fn bench_quick_sort_100(b: &mut Bencher) {
// let data = vec![5, 3, 1, 4, 2, 7, 6, 9, 8, 0];
// b.iter(|| {
// let mut arr = data.clone();
// quick_sort(&mut arr);
// });
// }
// }
الناتج:
=== Benchmark Demo: Performance Comparison of Sorting Algorithms ===
Data Volume: 100 element
Bubble Sort: Average 45.123 microsecond (100 Next iteration)
Quick Sort: Average 3.456 microsecond (100 Next iteration)
Data Volume: 500 element
Bubble Sort: Average 1023.567 microsecond (10 Next iteration)
Quick Sort: Average 18.234 microsecond (10 Next iteration)
Data Volume: 1000 element
Bubble Sort: Average 4089.890 microsecond (10 Next iteration)
Quick Sort: Average 39.012 microsecond (10 Next iteration)
=== Conclusion ===
Bubble Sort O(n^2) It slows down noticeably when dealing with large amounts of data
Quick Sort O(n log n) Significant performance advantages with large data sets
Rust nightly Version Usage `cargo bench` Run a benchmark test
We recommend using the stable version `criterion` crate Conduct a benchmark test
تقيس عملية قياس الأداء وقت تنفيذ الكود لضمان عدم تدهور الأداء أثناء إعادة هيكلة الكود. يتضمن إصدار Rust Nightly السمة
#[bench]بشكل افتراضي، بينما يوصي الإصدار المستقر باستخدام الكريتcriterion. ملاحظة: لا تعني المقارنة المعيارية أن «الأسرع هو الأفضل» — فالأمر الأساسي هو تحديد خط أساس للأداء لضمان عدم حدوث أي تراجع غير متوقع في الأداء عند تعديل الكود. يوضح المثال أعلاه مفهوم المقارنة المعيارية من خلال قياس وقتInstant::now()يدويًّا.
(5) ▶ المثال:تمرين شامل — مكتبة سلاسل نصية تعتمد على الاختبارات (مستوى الصعوبة ⭐⭐⭐)
// ============================================
// Comprehensive Example: TDD-style String Library
// ============================================
pub fn is_palindrome(s: &str) -> bool {
let clean: String = s.chars().filter(|c| c.is_alphanumeric()).map(|c| c.to_lowercase().next().unwrap()).collect();
let reversed: String = clean.chars().rev().collect();
clean == reversed
}
pub fn word_wrap(text: &str, width: usize) -> Vec<String> {
let mut lines = Vec::new();
let mut current = String::new();
for word in text.split_whitespace() {
if current.len() + word.len() + 1 > width && !current.is_empty() {
lines.push(current.trim().to_string());
current.clear();
}
if !current.is_empty() { current.push(' '); }
current.push_str(word);
}
if !current.is_empty() { lines.push(current); }
lines
}
pub fn count_words(text: &str) -> std::collections::HashMap<String, u32> {
let mut freq = std::collections::HashMap::new();
for word in text.split_whitespace() {
let clean: String = word.chars().filter(|c| c.is_alphabetic()).map(|c| c.to_lowercase().next().unwrap()).collect();
if !clean.is_empty() { *freq.entry(clean).or_insert(0) += 1; }
}
freq
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_palindrome_simple() {
assert!(is_palindrome("racecar"));
assert!(is_palindrome("A man a plan a canal Panama"));
assert!(!is_palindrome("hello"));
}
#[test]
fn test_palindrome_empty() {
assert!(is_palindrome(""));
assert!(is_palindrome("a"));
}
#[test]
fn test_word_wrap() {
let result = word_wrap("The quick brown fox jumps", 10);
assert_eq!(result, vec!["The quick", "brown fox", "jumps"]);
}
#[test]
fn test_word_wrap_short() {
let result = word_wrap("Hello", 10);
assert_eq!(result, vec!["Hello"]);
}
#[test]
fn test_count_words() {
let freq = count_words("the cat and the dog");
assert_eq!(freq.get("the"), Some(&2));
assert_eq!(freq.get("cat"), Some(&1));
assert_eq!(freq.get("dog"), Some(&1));
}
}
fn main() {
println!("is_palindrome('racecar'): {}", is_palindrome("racecar"));
println!("is_palindrome('hello'): {}", is_palindrome("hello"));
let wrapped = word_wrap("The quick brown fox jumps over the lazy dog", 15);
for line in &wrapped { println!("|{:<15}|", line); }
let freq = count_words("the cat sat on the mat and the cat");
println!("\nWord frequency: {:?}", freq);
}
الناتج:
is_palindrome('racecar'): true
is_palindrome('hello'): false
|The quick brown|
|fox jumps over |
|the lazy dog |
Word frequency: {"the": 3, "cat": 2, "sat": 1, "on": 1, "mat": 1, "and": 1}
عملية TDD: اكتب الاختبارات أولاً (
test_palindrome_simple، إلخ) → ثم اكتب التنفيذ → قم بتشغيلcargo test.#[cfg(test)]تأكد من أن وحدة الاختبار لا يتم ترجمتها إلا أثناء الاختبار.use super::*قم باستيراد واجهة برمجة التطبيقات (API) العامة للوحدة قيد الاختبار. يوضح هذا المثال الجمع بين الاختبار الوحدوي والتنفيذ الفعلي.
❓ أسئلة شائعة
س: ما الفرق بين
#[test]و#[cfg(test)]؟ ج:#[test]يُشير إلى دالة ما على أنها دالة اختبارية، بينما#[cfg(test)]يلف الوحدة النمطية بحيث يتم ترجمتها في وضع الاختبار فقط.#[test]تعني «هذه الدالة هي دالة اختبار»، و#[cfg(test)]تعني «هذا الكود مخصص للاختبار فقط». وعادةً ما تُستخدم معًا:#[cfg(test)] mod tests { ... }تُغلف الوحدة النمطية الاختبارية بأكملها، وتُعلم كل دالة اختبار داخل الوحدة النمطية بـ#[test].
س: ما الفرق بين اختبارات الوثائق (doc-tests) واختبارات الوحدة (unit tests)؟ ج: تختبر اختبارات الوثائق (doc-tests) صحة تشغيل أمثلة الكود الواردة في الوثائق، بينما تختبر اختبارات الوحدة (unit tests) صحة منطق الدالة. تُكتب أكواد اختبارات الوثائق داخل كتل أكواد مُعلَّمة بـ
///، ويقومcargo testبتجميعها وتنفيذها تلقائيًا. وهذا يضمن أن تكون الأمثلة الواردة في الوثائق محدَّثة دائمًا — فإذا تغير الكود دون تحديث الوثائق، فستفشل اختبارات الوثائق.
س: لماذا توضع اختبارات التكامل في الدليل
tests/؟ ج: لأن كل ملف في الدليلtests/يُعامل كـ crate مستقل ولا يمكنه الوصول إلا إلى واجهة برمجة التطبيقات (API) العامة لمكتبتك. وهذا يحاكي السيناريو الذي «يستخدم فيه المستخدمون الخارجيون مكتبتك». أما اختبارات الوحدة، فهي موضوعة في الوحدة النمطية#[cfg(test)]داخل ملفات الكود المصدري ويمكنها الوصول إلى الدوال الخاصة. ويخدم كل منهما غرضًا مختلفًا: اختبارات الوحدة هي اختبارات الصندوق الأبيض (تفحص التنفيذ الداخلي)، بينما اختبارات التكامل هي اختبارات الصندوق الأسود (تفحص السلوك الخارجي فقط).
س: ما هي المعلمات الشائعة لـ
cargo test؟ ج: يتميزcargo testبمرونة كبيرة:cargo test nameلا يُشغّل سوى الاختبارات ذات الأسماء المطابقة، وcargo test -- --nocaptureيعرض مخرجات println! في الاختبارات، وcargo test -- --test-threads=1يعمل في خيط واحد (مناسب للاختبارات ذات الحالة المشتركة)، وcargo test --test filenameلا يُشغّل سوى ملفات اختبار التكامل المحددة، وcargo test -- --ignoredلا يُشغّل سوى الاختبارات المُعلَّمة بـ#[ignore].
س: هل اختبار الأداء ضروري؟ متى تكون هناك حاجة إليه؟ ج: يُعد اختبار الأداء أمرًا ضروريًا عندما يكون الكود الخاص بك جزءًا من مسار حرج من حيث الأداء (مثل التداول عالي التردد، أو محركات الألعاب، أو مسارات معالجة البيانات). ولا تكمن قيمته في قياس «مدى سرعة تشغيله»، بل في تحديد خط أساس للأداء — بحيث يمكنك، عند تعديل الكود، اكتشاف أي تدهور في الأداء على الفور. بالنسبة لمعظم أكواد الأعمال، اكتب اختبارات الوحدة المناسبة أولاً، ولا تلجأ إلى إجراء اختبارات الأداء إلا عندما يصبح الأداء عائقًا.
📖 ملخص
#[test]تشير إلى دالة اختبار؛cargo testتُشغّل جميع الاختبارات؛assert!/assert_eq!/assert_ne!هي الماكروات الثلاثة الرئيسية للتحقق#[should_panic]اختبار سيناريوهات الطوارئ المتوقعة؛#[ignore]تخطي الاختبارات التي لا يلزم إجراؤها في الوقت الحالي#[cfg(test)]التجميع الشرطي لوحدات الاختبار لضمان عدم ظهور كود الاختبار في إصدارات الإنتاج///تستخدم تعليقات التوثيق تنسيق Markdown؛ ويتم تحويل كتل الكود تلقائيًا إلى اختبارات توثيقية؛cargo doc --openيُنشئ توثيقًا بتنسيق HTML- اختبارات التكامل موجودة في الدليل
tests/؛ ويتم ترجمة كل ملف على حدة، ولا يمكن لكل منها سوى اختبار واجهة برمجة التطبيقات العامة (من منظور الصندوق الأسود). - المقارنة المعيارية: لقياس أداء الكود، استخدم
#[bench]في الإصدار الليلي؛ أما بالنسبة للإصدار المستقر، فنوصي باستخدام الصندوقcriterion.
📝 تمارين
-
الصعوبة ⭐: اكتب برنامجًا يتضمن الدالة
is_prime(n: u32) -> boolلتحديد ما إذا كان عدد ما عددًا أوليًا أم لا. اكتب ما لا يقل عن 5 حالات اختبار لهذه الدالة (بما في ذلك الحالات الحدية: 0، 1، 2، الأعداد الأولية، والأعداد المركبة). استخدمassert!وassert_eq!و#[should_panic](التي تثير حالة ذعر إذا كانت القيمة 0) مرة واحدة على الأقل لكل منها. -
الصعوبة ⭐⭐: أنشئ مكتبة «محول درجات الحرارة» تحتوي على دالتين:
celsius_to_fahrenheit(c: f64) -> f64وfahrenheit_to_celsius(f: f64) -> f64. اكتب تعليقات توثيقية كاملة لهاتين الدالتين (بما في ذلك الأمثلة، ووصف المعلمات، ووصف قيم الإرجاع)، وتأكد من اجتياز اختبارات الوحدة. كما يجب كتابة اختبارات الوحدة للتحقق من القيم الحدية (مثل 0 درجة مئوية = 32 درجة فهرنهايت، و100 درجة مئوية = 212 درجة فهرنهايت، و-40 درجة مئوية = -40 درجة فهرنهايت). -
الصعوبة ⭐⭐⭐: قم بتنفيذ مكتبة «آلة حاسبة بسيطة» تدعم خمس عمليات:
add،subtract،multiply،divide، وpower(عمليات القوة). أنشئ إطار عمل اختبار شامل لهذه المكتبة: (1) يجب أن تغطي الاختبارات الوحدوية جميع الدوال، بما في ذلك الحالات الحدية (القسمة على الصفر، القوة الصفرية، إلخ)؛ (2) قم بمحاكاة إنشاء ملف اختبار تكامليtests/calculator_integration_test.rs(اكتب المحتوى الكامل في التعليقات) لاختبار سيناريوهات «العمليات المتتالية» (مثلadd(2,3) → multiply(5,4) → power(20,2)). استخدم#[should_panic]لاختبار سيناريو القسمة على الصفر.