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

تنظيم الاختبارات Tests في لغة رست Rust


Naser Dakhel

يُعد اختبار الشيفرة البرمجية كما ذكرنا سابقًا في مقال كتابة الاختبارات في لغة رست Rust ممارسةً معقدة، ويستخدم الناس الاختبارات بطرق ومصطلحات مختلفة، إضافةً إلى تنظيمها، إلا أن مجتمع مبرمجي لغة رست ينظر إلى الاختبارات بكونها تنتمي إلى أحد التصنيفين الرئيسين: اختبارات الوحدة unit tests واختبارات التكامل integration tests. تعدّ اختبارات الوحدة اختبارات صغيرة ومحددة على وحدة معيّنة منعزلة ويُمكن أن تختبر الواجهات الخاصة private interfaces، بينما تكون اختبارات التكامل خارجية كليًا لمكتبتك وتستخدم شيفرتك البرمجية بالطريقة ذاتها التي تستخدم فيها شيفرة برمجية خارجية اعتيادية شيفرتك البرمجية باستخدام الواجهات العامة public interface وتتضمن غالبًا أكثر من وحدة ضمن الاختبار الواحد.

كتابة نوعَي الاختبارات مهمٌ للتأكد من أن أجزاء في مكتبتك تنجز ما هو مطلوب منها بغض النظر عن باقي الأجزاء.

اختبارات الوحدة

الهدف من اختبارات الوحدة هو اختبار كل وحدة من شيفرة برمجية بمعزل عن الشيفرة البرمجية المتبقية، وذلك لتشخيص النقطة التي لا تعمل فيها الشيفرة البرمجية بصورةٍ صحيحة وبدقة، لذلك ستضع اختبارات الوحدة في المجلد "src" في كل ملف مع الشيفرة البرمجة التي تختبرها، ويقتضي الاصطلاح بإنشاء وحدة تدعى tests في كل ملف لاحتواء دوال الاختبار وتوصيف الوحدة باستخدام cfg(test)‎.

وحدة الاختبارات وتوصيف ‎#[cfg(test)]‎

يخبر توصيف ‎#[cfg(test)]‎ على وحدة الاختبارات رست بأنه يجب تصريف وتشغيل شيفرة الاختبار البرمجية فقط عند تنفيذ cargo test وليس عند تنفيذ cargo build، مما يختصر وقتًا من عملية التصريف عندما تريد فقط بناء المكتبة وتوفير المساحة التي سيشغلها الملف المُصرَّف الناتج وذلك لأن الاختبارات غير مُضمّنة به. توضع اختبارات التكامل في مجلد مختلف ولا تستخدم التوصيف ‎#[cfg(test)]‎، إلا أنك بحاجة لاستخدام ‎#[cfg(test)]‎ لتحديد أنها لا يجب أن تُضمَّن في الملف المصرَّف الناتج لأن اختبارات الوحدة موجودة في ملف الشيفرة البرمجية ذاته.

تذكر أن كارجو Cargo ولّد الشيفرة البرمجية التالية لنا عندما ولدنا المشروع adder الجديد سابقًا:

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

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        let result = 2 + 2;
        assert_eq!(result, 4);
    }
}

تُولّد هذه الشيفرة البرمجية تلقائيًا على هيئة وحدة اختبار. تمثّل السمة cfg اختصارًا لكلمة الضبط configuration، إذ تُعلم رست أن العنصر الآتي يجب أن يُضمَّن فقط في خيار ضبط معيّن، وفي هذه الحالة فإن خيار الضبط test الموجود في رست لتصريف وتنفيذ الاختبارات. يصرّف كارجو شيفرة الاختبار البرمجية فقط في حال تنفيذ الاختبارات بفعالية باستخدام cargo test، وهذا يتضمّن أي دوال مساعدة يمكن أن تكون داخل هذه الوحدة، إضافةً للدوال الموصّفة باستخدام ‎#[test]‎.

اختبار الدوال الخاصة

هناك اختلافٌ بين المبرمجين بخصوص الاختبارات وبالأخص اختبار الدوال الخاصة، إذ يعتقد البعض أن الدوال الخاصة يجب أن تُختبر مباشرةً، بينما لا يتفق البعض الآخر مع ذلك، وتجعل لغات البرمجة اختبار الدوال الخاصة صعبًا أو مستحيلًا، وبغض النظر عمّا تعتقد بخصوص هذا الشأن، تسمح قوانين خصوصية رست لك باختبار الدوال الخاصة. ألقِ نظرةً على الشيفرة 12 التي تحتوي على الشيفرة الخاصة internal_adder.

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

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

fn internal_adder(a: i32, b: i32) -> i32 {
    a + b
}

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

    #[test]
    fn internal() {
        assert_eq!(4, internal_adder(2, 2));
    }
}

[الشيفرة 12: اختبار دالة خاصة]

لاحظ أن الدالة internal_adder ليست دالة عامة (لا تحتوي على pub). الاختبارات هي شيفرة برمجية مكتوبة بلغة رست وبالتالي تمثل وحدة tests وحدة اعتيادية، وكما ناقشنا سابقًا يمكن العناصر في الوحدات الابن استخدام العناصر الموجودة في الوحدات الأب، وفي هذا الاختبار نُضيف كل عناصر الوحدات الأب الخاصة بالوحدة test إلى النطاق بكتابة use super::*‎، بحيث يمكننا استدعاء internal_adder فيما بعد. إذا لم تكن مقتنعًا بأن الدوال الخاصة يجب أن تُختَبر فلن تجبرك رست على ذلك.

اختبارات التكامل Integration Tests

اختبارات التكامل Integration Tests في رست خارجية external كليًا بالنسبة لمكتبتك، وتستخدم هذه الاختبارات مكتبتك بالطريقة ذاتها لأي شيفرة برمجية، مما يعني أنها يمكن أن تستدعي دوال تشكل جزءًا من الواجهة البرمجية العامة Public API للمكتبة. الهدف من هذا النوع من الاختبارات هو اختبار ما إذا كانت أجزاء من مكتبتك تعمل مع بعضها بعضًا بصورةٍ صحيحة، إذ أن بعض الأجزاء من الشيفرة البرمجية قد تعمل بصورةٍ صحيحة لوحدها ولكن تواجه بعض المشاكل عند تكاملها مع أجزاء أخرى، لذا يُغطّي هذا النوع من الاختبارات الشيفرة البرمجية المتكاملة. نحتاج لإنشاء اختبارات التكامل أولًا إلى مجلد tests.

مجلد tests

نُنشئ مجلد "tests" في المستوى الأعلى لمجلد مشروعنا بجانب "src"، إذ يتعرّف كارجو على المجلد والاختبارات التي بداخله، ويمكننا إنشاء ملفات اختبار قدر ما شئنا، وسيصرّف كارجو كل ملف اختبار بدوره بكونه وحدة مصرّفة crate منفصلة.

لننشئ اختبار تكامل باستخدام الشيفرة البرمجية الموجودة في الشيفرة 12 الموجودة في الملف src/lib.rs، نبدأ أولًا بإنشاء مجلد tests ونُنشئ ملفًا جديدًا نسميه tests/integration_test.rs. يجب أن يبدو هيكل المجلد كما يلي:

adder
├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
    └── integration_test.rs

أدخل الشيفرة البرمجية في الشيفرة 13 إلى الملف tests/integration_test.rs:

اسم الملف: tests/integration_test.rs

use adder;

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

[الشيفرة 13: اختبار تكامل لدالة في الوحدة المصرَّفة adder]

يمثل كل ملف في المجلد "tests" وحدة مصرفة منعزلة، لذا يجب علينا إضافة مكتبتنا إلى نطاق وحدة الاختبار المصرَّفة، ونُضيف لهذا السبب use adder في بداية الشيفرة البرمجية وهو ما لم نحتاجه سابقًا عند استخدامنا لاختبارات الوحدة.

ليس علينا توصّف الشيفرة البرمجية في tests/integration_test.rs باستخدام ‎#[cfg(test)]‎، إذ أنّ كارجو تُعامل المجلد "tests" على نحوٍ خاص وتُصرِّف جميع الملفات في هذا المجلد عند تنفيذ الأمر cargo test. ننفّذ cargo test فنحصل على التالي:

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

running 1 test
test tests::internal ... ok

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

     Running tests/integration_test.rs (target/debug/deps/integration_test-1082c4b063a8fbe6)

running 1 test
test it_adds_two ... ok

test result: ok. 1 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

تتضمن أقسام الخرج الثلاثة اختبارات الوحدة والتكامل والتوثيق، لاحظ أنه إذا فشل أي اختبار في قسم ما، لن يُنفَّذ القسم التالي. على سبيل المثال، إذا فشل اختبار وحدة فهذا يعني أنه لن يكون هناك أي خرج لاختبار التكامل واختبار التوثيق لأن هذه الاختبارت ستُنفَّذ فقط في حال نجاح جميع اختبارات الوحدة.

القسم الأول هو لاختبارات الوحدة وهو مشابه لما رأيناه سابقًا، إذ يُخصّص كل سطر لاختبار وحدة ما (هناك اختبار واحد يسمى internal أضفناه سابقًا في الشيفرة 12) بالإضافة إلى سطر الملخص لنتائج اختبارات الوحدة.

يبدأ قسم اختبارات التكامل بالسطر Running tests/integration_test.rs، ومن ثم نلاحظ سطرًا لكل دالة اختبار في اختبار التكامل ذلك مع سطر ملخص لنتائج اختبار التكامل قبل بدء القسم Doc-tests adder.

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

يمكننا تنفيذ دالة اختبار معيّنة بتحديد اسم دالة الاختبار مثل وسيط للأمر cargo test، ولتنفيذ جميع الاختبارات في ملف اختبار تكامل معيّن نستخدم الوسيط ‎--test في الأمر cargo test متبوعًا باسم الملف كما يلي:

$ cargo test --test integration_test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.64s
     Running tests/integration_test.rs (target/debug/deps/integration_test-82e7799c1bc62298)

running 1 test
test it_adds_two ... ok

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

ينفّذ هذا الأمر الاختبارات الموجودة في ملف "tests/integration_test.rs" فقط.

الوحدات الجزئية في اختبارات التكامل

قد تحتاج إنشاء المزيد من الملفات في المجلد "tests" لمساعدتك في تنظيم اختبارات التكامل في حال إضافتك للمزيد منها، على سبيل المثال يمكنك تجميع دوال الاختبار بناءً على الخاصية التي تفحصها، وكما ذكرنا سابقًا: يُصرَّف كل ملف في المجلد "tests" بمفرده على أنه وحدة مصرّفة، وهو أمر مفيد لإنشاء نطاقات متفرقة عن بعضها بعضًا لمحاكاة الطريقة التي يستخدم فيها المستخدمون وحدتك المصرفة، إلا أن هذا يعني أن الملفات في مجلد "tests" لن تشارك السلوك ذاته الخاص بالملفات في المجلد "src" كما تعلمت سابقًا بخصوص فصل الشيفرة البرمجية إلى وحدات وملفات.

يُلاحظ السلوك بملفات المجلد "tests" بوضوح عندما يكون لديك مجموعةً من الدوال المساعدة تريد استخدامها في ملفات اختبار تكامل مختلفة وتحاول أن تتبع الخطوات الخاصة بفصل الوحدات إلى ملفات مختلفة كما ناقشنا سابقًا لاستخلاصها إلى وحدة مشتركة. على سبيل المثال، إذا أنشأنا "tests/common.rs" ووضعنا دالةً اسمها setup في الملف، يمكننا إضافة شيفرة برمجية إلى setup لاستدعائها من دوال اختبار مختلفة في ملفات اختبار متعددة.

اسم الملف: tests/common.rs

pub fn setup() {
    // شيفرة ضبط برمجية مخصصة لاختبارات مكتبتك
}

عند تشغيل الاختبارات مجددًا سنرى قسمًا جديدًا في خرج الاختبار للملف "common.rs"، على الرغم من أننا لا نستدعي الدالة setup من أي مكان كما أن هذا الملف لا يحتوي على أي دوال اختبار:

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

running 1 test
test tests::internal ... ok

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

     Running tests/common.rs (target/debug/deps/common-92948b65e88960b4)

running 0 tests

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

     Running tests/integration_test.rs (target/debug/deps/integration_test-92948b65e88960b4)

running 1 test
test it_adds_two ... ok

test result: ok. 1 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

العثور على common في نتائج الاختبار مع running 0 tests ليس ما أردنا رؤيته، إذا أننا أردنا مشاركة جزء من شيفرة برمجية مع ملفات اختبار التكامل الأخرى.

لتجنب الحصول على common في خرج الاختبار، نُنشئ "tests/common/mod.rs" بدلًا من "tests/common.rs"، بحيث يبدو هيكل مجلد المشروع كما يلي:

├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
    ├── common
    │   └── mod.rs
    └── integration_test.rs

هذا هو اصطلاح التسمية القديم في رست وقد ذكرناه سابقًا في فصل الحزم والوحدات، إذ تخبر تسمية الملف بهذا الاسم رست بعدم التعامل مع الوحدة common على أنها ملف اختبار تكامل، وبالتالي لن يظهر هذا القسم في خرج الاختبار بعد أن ننقل شيفرة الدالة البرمجية setup إلى "tests/common/mod.rs" ونحذف الملف "tests/common.rs". لا تُصرَّف الملفات الموجودة في المجلدات الفرعية في المجلد tests مثل وحدات مصرّفة متفرقة أو تحتوي على أقسام متفرقة في خرج الاختبار.

يمكننا استخدام أي من ملفات اختبار التكامل مثل وحدة بعد إنشاء الملف "tests/common/mod.rs"، إليك مثالًا على استدعاء الدالة setup من الاختبار it_adds_two في "tests/integration_test.rs":

اسم الملف: tests/integration_tests.rs

use adder;

mod common;

#[test]
fn it_adds_two() {
    common::setup();
    assert_eq!(4, adder::add_two(2));
}

لاحظ أن التصريح mod common;‎ مماثلٌ لتصريح الوحدة التي شرحنا عنها سابقًا في الشيفرة 21 فصل المسارات paths والنطاق الخاص بها في لغة رست Rust. يمكننا بعد ذلك استدعاء الدالة common::setup()‎ من دالة الاختبار.

اختبارات التكامل للوحدات الثنائية المصرفة

لا يمكننا إنشاء اختبارات تكامل في المجلد "tests" وإضافة الدوال المعرفة في الملف "src/main.rs" إلى النطاق باستخدام تعليمة use إذا كان مشروعنا يمثّل وحدة ثنائية مصرفة binary crate تحتوي على ملف "src/main.rs" فقط ولا تحتوي على ملف "src/lib.rs"، إذ أن وحدات المكتبة المصرفة وحدها قادرة على كشف الدوال التي يمكن للوحدات المصرّفة الأخرى استخدامها؛ لأن الهدف من الوحدات الثنائية المصرفة هو تنفيذها بصورةٍ مستقلة.

هذا واحدٌ من الأسباب لكون مشاريع رست التي تدعم ملفات ثنائية تأتي بملف "src/main.rs"، الذي يستدعي المنطق البرمجي الموجود في الملف src/lib.rs. يمكن لاختبارات التكامل باستخدام هذا التنظيم بأن تختبر صندوق المكتبة المصرف باستخدام use لجعل الدوال المهمة متاحة، فإذا عملت الوظيفة الأساسية، عنى ذلك أن الأجزاء الصغيرة من الشيفرة البرمجية في الملف src/main.rs ستعمل أيضًا ويجب اختبار هذه الأجزاء الصغيرة.

ترجمة -وبتصرف- لقسم من الفصل 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.


×
×
  • أضف...