اذهب إلى المحتوى

التحكم بتنفيذ الاختبارات في لغة رست Rust


Naser Dakhel

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

تنتمي بعض الخيارات في سطر الأوامر إلى cargo test بينما ينتمي بعضها لملف الاختبار الثنائي الناتج، وللفصل بين النوعين من الوسطاء نضع الوسطاء الخاصة بالأمر cargo test متبوعةً بالفاصل -- ومن ثم الوسطاء الخاصة بملف الاختبار الثنائي. يعرض تنفيذ الأمر cargo test --help الخيارات الممكن استخدامها مع cargo test، بينما يعرض تنفيذ cargo test -- --help الخيارات التي يمكنك استخدامها بعد الفاصل.

تشغيل الاختبارات على نحو متعاقب أو على التوازي

تُنفّذ الاختبارات على التوازي افتراضيًا عند تشغيل عدة اختبارات باستخدام خيوط threads، مما يعني أن تنفيذها سيكون سريعًا وستحصل على نتيجتك بصورةٍ أسرع، وبما أن الاختبارات تُنفّذ في الوقت ذاته فهذا يعني أنها يجب ألا تعتمد على بعضها بعضًا أو تحتوي على حالة مشتركة بما فيه بيئة مشتركة مثل مسار العمل الحالي current working directory أو متغيرات البيئة environment variables.

على سبيل المثال لنقل أن اختباراتك تنفذ شيفرة برمجية تُنشئ ملفًا على قرص باسم test-output.txt وتكتب بعض البيانات على هذا الملف، يقرأ عندها كل اختبار البيانات في ذلك الملف ويتأكد أن الملف يحتوي على قيمة معينة وهي قيمة مختلفة بحسب كل اختبار، ولأن الاختبارات تُشغّل في الوقت ذاته فهذا يعني أن أحد الاختبارات قد يكتب على محتويات الملف في وقت تنفيذ اختبار آخر وقراءته للملف وسيفشل عندها الاختبار الثاني ليس لعدم صحة الشيفرة البرمجية بل لأن الاختبارات أثرت على بعضها بعضًا عند تشغيلها على التوازي. يكمن أحد الحلول هنا بالتأكد أن كل اختبار يكتب إلى ملف مختلف، وهناك حلّ آخر يتمثل بتشغيل الاختبارات على التتالي كلّ حسب دوره.

إذا لم ترد تشغيل الاختبارات على التوازي، أو أردت تحكمًا أكبر على أرقام الخيوط التي تريد استخدامها فيمكنك عندئذ استخدام الراية ‎--test-threads متبوعةً بعدد الخيوط التي تريد استخدامها مع الاختبار الثنائي. ألقِ نظرةً على المثال التالي:

$ cargo test -- --test-threads=1

نضبط عدد خيوط الاختبار إلى "1"، وهذا يجعل البرنامج يعرف أننا لا نريد تشغيل الاختبارات على التوازي، إذ يستغرق تشغيل الاختبارات باستخدام خيط واحد وقتًا أكبر من تشغيلها على التوازي، إلا أن الاختبارات لن تتداخل في عمل بعضها بعضًا إذا تشاركت في حالة ما.

عرض خرج الدالة

تلتقط مكتبة اختبار رست تلقائيًا كل شيء يُطبع إلى الخرج القياسي إذا نجح الاختبار، على سبيل المثال إذا استدعينا println!‎ في اختبار ما ونجح هذا الاختبار فلن نرى خرج println!‎ في الطرفية بل سنرى فقط السطر الذي يشير إلى نجاح الاختبار، بينما سنرى ما طُبع إلى الخرج القياسي إذا فشل الاختبار مصحوبًا مع رسالة الفشل.

تحتوي الشيفرة 10 على مثال بسيط يحتوي على دالة تطبع قيمة معاملها وتُعيد 10 إضافةً إلى اختبار ينجح وآخر يفشل.

اسم الملف: src/lib.rs

fn prints_and_returns_10(a: i32) -> i32 {
    println!("I got the value {}", a);
    10
}

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

    #[test]
    fn this_test_will_pass() {
        let value = prints_and_returns_10(4);
        assert_eq!(10, value);
    }

    #[test]
    fn this_test_will_fail() {
        let value = prints_and_returns_10(8);
        assert_eq!(5, value);
    }
}

[الشيفرة 10: اختبارات للدالة التي تستدعي println!‎]

نحصل على الخرج التالي عند تشغيل هذه الاختبارات باستخدام cargo test:

$ cargo test
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished test [unoptimized + debuginfo] target(s) in 0.58s
     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)

running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok

failures:

---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `5`,
 right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::this_test_will_fail

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass '--lib'

لاحظ أن الخرج قد التُقط ولا يوجد فيه I got the value 4 وهو ما نطبعه عند تشغيل الاختبار الذي سينجح، بينما يظهر الخرج I got the value 8 من الاختبار الذي فشل في قسم خرج ملخص الاختبار الذي يوضح أيضًا سبب فشل الاختبار.

يمكننا اخبار رست بعرض خرج الاختبارات الناجحة باستخدام ‎--show-output إذا أردنا رؤية القيم المطبوعة للاختبارات الناجحة أيضًا.

$ cargo test -- --show-output

نحصل على الخرج التالي عند تشغيل الاختبارات الموجودة في الشيفرة 10 مجددًا باستخدام الراية ‎--show-output:

$ cargo test -- --show-output
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished test [unoptimized + debuginfo] target(s) in 0.60s
     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)

running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok

successes:

---- tests::this_test_will_pass stdout ----
I got the value 4


successes:
    tests::this_test_will_pass

failures:

---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `5`,
 right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::this_test_will_fail

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass '--lib'

تشغيل مجموعة من الاختبارات باستخدام اسم

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

لتوضيح كيفية تشغيل مجموعة من الاختبارات نُنشئ أولًا ثلاث اختبارات للدالة add_two كما هو موضح في الشيفرة 11 ونختار أي الاختبارات التي نريد تشغيلها.

اسم الملف: src/lib.rs

pub fn add_two(a: i32) -> i32 {
    a + 2
}

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

    #[test]
    fn add_two_and_two() {
        assert_eq!(4, add_two(2));
    }

    #[test]
    fn add_three_and_two() {
        assert_eq!(5, add_two(3));
    }

    #[test]
    fn one_hundred() {
        assert_eq!(102, add_two(100));
    }
}

[الشيفرة 11: ثلاثة اختبارات مع ثلاثة أسماء مختلفة]

ستُنفّذ جميع الاختبارات على التوازي إذا شغّلنا الاختبارات دون تمرير أي وسطاء كما فعلنا سابقًا:

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.62s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 3 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok
test tests::one_hundred ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

تشغيل الاختبارات بصورة فردية

يمكننا تمرير اسم أي دالة اختبار للأمر cargo test لتنفيذ الاختبار بصورةٍ فردية:

$ cargo test one_hundred
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.69s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 1 test
test tests::one_hundred ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s

جرى تشغيل الاختبار بالاسم one_hundred فقط، إذ لم يطابق الاختباران الآخران هذا الاسم. يسمح لنا الخرج بمعرفة أن هناك المزيد من الاختبارات التي لم ننفذها بعرض 2 filtered out في النهاية.

لا يمكننا تحديد أسماء اختبارات متعددة بهذه الطريقة، إذ تُستخدم القيمة الأولى المُعطاة للأمر cargo test فقط، إلا أن هناك وسيلة أخرى لتنفيذ عدة اختبارات.

تنفيذ عدة اختبارات عن طريق الترشيح

يمكننا تحديد جزء من اسم اختبار بحيث يُنفّذ أي اختبار يطابق اسمه هذه القيمة. على سبيل المثال، يمكننا تنفيذ اختبارين من الاختبارات الثلاثة السابقة عن طريق add وذلك بكتابة الأمر cargo test add:

$ cargo test add
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.61s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 2 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

يشغّل هذا الأمر جميع الاختبارات التي تحتوي على add في اسمها، ويستثني هنا الاختبار one_hundred، كما يجب ملاحظة أن الوحدة التي تحتوي على الاختبار بداخلها تصبح جزءًا من اسم الاختبار، أي يمكننا تشغيل جميع الاختبارات الموجودة في وحدة معينة عن طريق استخدام اسمها.

تجاهل بعض الاختبارات إلا في حال طلبها

قد يكون لدينا في بعض الأحيان اختبارات معينة تستغرق وقتًا طويلًا لتنفيذها وقد ترغب باستثنائها من التشغيل عند كتابة الأمر cargo test. يمكنك هنا استخدام توصيف الاختبارات التي تستغرق وقتًا طويلًا باستخدام السمة ignore لاستثنائها بدلًا من كتابة جميع الاختبارات التي تريد تشغيلها مثل وسطاء باستثناء تلك الاختبارات، كما هو موضح هنا:

اسم الملف: src/lib.rs

#[test]
fn it_works() {
    assert_eq!(2 + 2, 4);
}

#[test]
#[ignore]
fn expensive_test() {
    // شيفرة برمجية تستغرق عدة ساعات للتنفيذ
}

نضيف ‎#[ignore]‎ بعد سطر ‎#[test]‎ ضمن الاختبار الذي نريد استثناءه، والآن عند تشغيل الاختبارات يُنفّذ الاختبار it_works دون expensive_test:

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.60s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 2 tests
test expensive_test ... ignored
test it_works ... ok

test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

تُدرج الدالة expensive_test تحت ignored، وإذا أردنا تشغيل الاختبارات التي تجاهلناها فقط نكتب cargo test -- --ignored:

$ cargo test -- --ignored
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.61s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 1 test
test expensive_test ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

   Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

نتأكد من خلال التحكم بالاختبارات التي تُنفَّذ من أن نتائج cargo test ستكون سريعة، يمكنك تنفيذ cargo test -- --ignored عندما تكون في حالة تريد فيها التحقق من الاختبارات التي تندرج تحت ignored ولديك الوقت لانتظار النتائج، بينما تستطيع تنفيذ الأمر التالي إذا أردت تشغيل جميع الاختبارات المتجاهلة وغير المتجاهلة دفعةً واحدة.

cargo test -- --include-ignored

ترجمة -وبتصرف- لقسم من الفصل Writing Automated Tests من كتاب The Rust Programming Language.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...