টেস্ট কীভাবে চালানো হয় তা নিয়ন্ত্রণ করা (Controlling How Tests Are Run)

যেমন cargo run আপনার কোড কম্পাইল করে এবং তারপর ফলস্বরূপ বাইনারি চালায়, তেমনি cargo test আপনার কোডকে টেস্ট মোডে কম্পাইল করে এবং ফলস্বরূপ টেস্ট বাইনারি চালায়। cargo test দ্বারা উত্পাদিত বাইনারির ডিফল্ট আচরণ হল সমস্ত টেস্ট সমান্তরালভাবে (parallel) চালানো এবং টেস্ট চলাকালীন উত্পন্ন আউটপুট ক্যাপচার করা, আউটপুট প্রদর্শিত হওয়া রোধ করে এবং টেস্টের ফলাফলের সাথে সম্পর্কিত আউটপুট পড়া সহজ করে তোলে। তবে, আপনি এই ডিফল্ট আচরণ পরিবর্তন করতে কমান্ড লাইন অপশন নির্দিষ্ট করতে পারেন।

কিছু কমান্ড লাইন অপশন cargo test-এ যায় এবং কিছু ফলস্বরূপ টেস্ট বাইনারিতে যায়। এই দুই ধরনের আর্গুমেন্ট আলাদা করতে, আপনি cargo test-এ যাওয়া আর্গুমেন্টগুলি তালিকাভুক্ত করুন, তারপরে বিভাজক -- এবং তারপর টেস্ট বাইনারিতে যাওয়া আর্গুমেন্টগুলি দিন। cargo test --help চালালে cargo test-এর সাথে আপনি যে অপশনগুলি ব্যবহার করতে পারেন সেগুলি প্রদর্শিত হয় এবং cargo test -- --help চালালে বিভাজকের পরে আপনি যে অপশনগুলি ব্যবহার করতে পারেন সেগুলি প্রদর্শিত হয়। সেই অপশনগুলি the rustc book-এর “Tests” section-এও ডকুমেন্ট করা আছে।

সমান্তরালভাবে বা ধারাবাহিকভাবে টেস্ট চালানো (Running Tests in Parallel or Consecutively)

যখন আপনি একাধিক টেস্ট চালান, ডিফল্টরূপে সেগুলি থ্রেড ব্যবহার করে সমান্তরালভাবে চলে, যার অর্থ হল সেগুলি দ্রুত চালানো শেষ হয় এবং আপনি দ্রুত প্রতিক্রিয়া পান। যেহেতু টেস্টগুলি একই সময়ে চলছে, তাই আপনাকে অবশ্যই নিশ্চিত করতে হবে যে আপনার টেস্টগুলি একে অপরের উপর বা কোনও শেয়ার্ড স্টেটের (shared state) উপর নির্ভরশীল নয়, যার মধ্যে একটি শেয়ার্ড এনভায়রনমেন্ট, যেমন বর্তমান ওয়ার্কিং ডিরেক্টরি (working directory) বা এনভায়রনমেন্ট ভেরিয়েবল অন্তর্ভুক্ত রয়েছে।

উদাহরণস্বরূপ, ধরুন আপনার প্রতিটি টেস্ট কিছু কোড চালায় যা ডিস্কে test-output.txt নামে একটি ফাইল তৈরি করে এবং সেই ফাইলে কিছু ডেটা লেখে। তারপর প্রতিটি টেস্ট সেই ফাইলের ডেটা পড়ে এবং অ্যাসার্ট করে যে ফাইলটিতে একটি নির্দিষ্ট মান রয়েছে, যা প্রতিটি টেস্টে আলাদা। যেহেতু টেস্টগুলি একই সময়ে চলে, তাই একটি টেস্ট অন্য টেস্টের লেখার এবং পড়ার সময়ের মধ্যে ফাইলটিকে ওভাররাইট করতে পারে। দ্বিতীয় টেস্টটি তখন ব্যর্থ হবে, কোডটি ভুল হওয়ার কারণে নয়, বরং টেস্টগুলি সমান্তরালভাবে চলার সময় একে অপরের সাথে হস্তক্ষেপ করার কারণে। একটি সমাধান হল নিশ্চিত করা যে প্রতিটি টেস্ট একটি ভিন্ন ফাইলে লেখে; আরেকটি সমাধান হল টেস্টগুলি একবারে একটি করে চালানো।

আপনি যদি সমান্তরালভাবে টেস্ট চালাতে না চান বা আপনি যদি ব্যবহৃত থ্রেডের সংখ্যার উপর আরও সূক্ষ্ম-নিয়ন্ত্রণ (fine-grained control) চান, তাহলে আপনি --test-threads ফ্ল্যাগ এবং আপনি যে সংখ্যক থ্রেড ব্যবহার করতে চান তা টেস্ট বাইনারিতে পাঠাতে পারেন। নিম্নলিখিত উদাহরণটি দেখুন:

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

আমরা টেস্ট থ্রেডের সংখ্যা 1-এ সেট করি, প্রোগ্রামটিকে কোনও প্যারালেলিজম (parallelism) ব্যবহার না করতে বলি। একটি থ্রেড ব্যবহার করে টেস্ট চালানো সমান্তরালভাবে চালানোর চেয়ে বেশি সময় নেবে, কিন্তু টেস্টগুলি একে অপরের সাথে হস্তক্ষেপ করবে না যদি তারা স্টেট শেয়ার করে।

ফাংশন আউটপুট দেখানো (Showing Function Output)

ডিফল্টরূপে, যদি একটি টেস্ট পাস করে, তাহলে Rust-এর টেস্ট লাইব্রেরি স্ট্যান্ডার্ড আউটপুটে প্রিন্ট করা যেকোনো কিছু ক্যাপচার করে। উদাহরণস্বরূপ, যদি আমরা একটি টেস্টে println! কল করি এবং টেস্টটি পাস করে, তাহলে আমরা টার্মিনালে println! আউটপুট দেখতে পাব না; আমরা শুধুমাত্র সেই লাইনটি দেখতে পাব যা নির্দেশ করে যে টেস্টটি পাস করেছে। যদি একটি টেস্ট ব্যর্থ হয়, তাহলে আমরা ব্যর্থতার বার্তার বাকি অংশের সাথে স্ট্যান্ডার্ড আউটপুটে যা প্রিন্ট করা হয়েছিল তা দেখতে পাব।

একটি উদাহরণ হিসাবে, লিস্টিং 11-10-এ একটি নিরীহ (silly) ফাংশন রয়েছে যা তার প্যারামিটারের মান প্রিন্ট করে এবং 10 রিটার্ন করে, পাশাপাশি একটি টেস্ট যা পাস করে এবং একটি টেস্ট যা ব্যর্থ হয়।

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!(value, 10);
    }

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

যখন আমরা cargo test দিয়ে এই টেস্টগুলি চালাই, তখন আমরা নিম্নলিখিত আউটপুট দেখতে পাব:

$ cargo test
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished `test` profile [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 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:
assertion `left == right` failed
  left: 10
 right: 5
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 দিয়ে Rust কে সফল টেস্টের আউটপুটও দেখাতে বলতে পারি:

$ cargo test -- --show-output

যখন আমরা --show-output ফ্ল্যাগ সহ লিস্টিং 11-10-এর টেস্টগুলি আবার চালাই, তখন আমরা নিম্নলিখিত আউটপুট দেখতে পাই:

$ cargo test -- --show-output
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished `test` profile [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 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:
assertion `left == right` failed
  left: 10
 right: 5
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`

নামের মাধ্যমে টেস্টের একটি উপসেট চালানো (Running a Subset of Tests by Name)

কখনও কখনও, একটি সম্পূর্ণ টেস্ট স্যুট (suite) চালাতে অনেক সময় লাগতে পারে। আপনি যদি কোনও নির্দিষ্ট এলাকার কোডে কাজ করেন, তাহলে আপনি সম্ভবত শুধুমাত্র সেই কোডের সাথে সম্পর্কিত টেস্টগুলি চালাতে চাইতে পারেন। আপনি cargo test-কে একটি আর্গুমেন্ট হিসাবে যে টেস্ট(গুলি) চালাতে চান তার নাম বা নামগুলি পাস করে কোন টেস্টগুলি চালাতে হবে তা বেছে নিতে পারেন।

টেস্টের একটি উপসেট কীভাবে চালানো যায় তা প্রদর্শন করতে, আমরা প্রথমে আমাদের add_two ফাংশনের জন্য তিনটি টেস্ট তৈরি করব, যেমনটি লিস্টিং 11-11-তে দেখানো হয়েছে, এবং কোনটি চালাতে হবে তা বেছে নেব।

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

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

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

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

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

যদি আমরা কোনো আর্গুমেন্ট পাস না করে টেস্ট চালাই, যেমনটি আমরা আগে দেখেছি, সমস্ত টেস্ট সমান্তরালভাবে চলবে:

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [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

একক টেস্ট চালানো (Running Single Tests)

আমরা cargo test-এ যেকোনো টেস্ট ফাংশনের নাম পাস করতে পারি শুধুমাত্র সেই টেস্টটি চালানোর জন্য:

$ cargo test one_hundred
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [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-কে দেওয়া শুধুমাত্র প্রথম মানটি ব্যবহার করা হবে। কিন্তু একাধিক টেস্ট চালানোর একটি উপায় আছে।

একাধিক টেস্ট চালানোর জন্য ফিল্টারিং (Filtering to Run Multiple Tests)

আমরা একটি টেস্টের নামের অংশ নির্দিষ্ট করতে পারি, এবং সেই মানের সাথে মেলে এমন নামের যেকোনো টেস্ট চালানো হবে। উদাহরণস্বরূপ, যেহেতু আমাদের দুটি টেস্টের নামে add রয়েছে, তাই আমরা cargo test add চালিয়ে সেই দুটি চালাতে পারি:

$ cargo test add
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [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 নামের টেস্টটিকে ফিল্টার করেছে। এছাড়াও লক্ষ্য করুন যে একটি টেস্ট যে মডিউলে প্রদর্শিত হয় সেটি টেস্টের নামের অংশ হয়ে যায়, তাই আমরা মডিউলের নামে ফিল্টার করে একটি মডিউলের সমস্ত টেস্ট চালাতে পারি।

নির্দিষ্টভাবে অনুরোধ না করা পর্যন্ত কিছু টেস্ট উপেক্ষা করা (Ignoring Some Tests Unless Specifically Requested)

কখনও কখনও কয়েকটি নির্দিষ্ট টেস্ট চালানো খুব সময়সাপেক্ষ হতে পারে, তাই আপনি cargo test-এর বেশিরভাগ চালানোর সময় সেগুলিকে বাদ দিতে চাইতে পারেন। আপনি যে সমস্ত টেস্ট চালাতে চান সেগুলিকে আর্গুমেন্ট হিসাবে তালিকাভুক্ত করার পরিবর্তে, আপনি সেগুলিকে বাদ দেওয়ার জন্য ignore অ্যাট্রিবিউট ব্যবহার করে সময়সাপেক্ষ টেস্টগুলিকে চিহ্নিত করতে পারেন, যেমনটি এখানে দেখানো হয়েছে:

Filename: src/lib.rs

pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

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

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }

    #[test]
    #[ignore]
    fn expensive_test() {
        // code that takes an hour to run
    }
}

#[test]-এর পরে, আমরা যে টেস্টটিকে বাদ দিতে চাই তাতে #[ignore] লাইন যুক্ত করি। এখন যখন আমরা আমাদের টেস্টগুলি চালাই, তখন it_works চলে, কিন্তু expensive_test চলে না:

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

running 2 tests
test tests::expensive_test ... ignored
test tests::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 হিসাবে তালিকাভুক্ত করা হয়েছে। যদি আমরা শুধুমাত্র উপেক্ষিত (ignored) টেস্টগুলি চালাতে চাই, তাহলে আমরা cargo test -- --ignored ব্যবহার করতে পারি:

$ cargo test -- --ignored
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [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-এর ফলাফলগুলি দ্রুত পাওয়া যাবে। আপনি যখন এমন একটি বিন্দুতে থাকবেন যেখানে ignored টেস্টগুলির ফলাফলগুলি পরীক্ষা করা অর্থপূর্ণ এবং আপনার ফলাফলের জন্য অপেক্ষা করার সময় আছে, তখন আপনি পরিবর্তে cargo test -- --ignored চালাতে পারেন। আপনি যদি সমস্ত টেস্ট চালাতে চান, সেগুলি উপেক্ষিত হোক বা না হোক, আপনি cargo test -- --include-ignored চালাতে পারেন।