Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

টেস্ট কিভাবে চালানো হয় তা নিয়ন্ত্রণ করা

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

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

টেস্ট প্যারালালি বা পরপর চালানো

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

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

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

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

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

ফাংশন আউটপুট দেখানো

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

উদাহরণস্বরূপ, তালিকা ১১-১০-এ একটি সাধারণ ফাংশন রয়েছে যা তার প্যারামিটারের মান প্রিন্ট করে এবং ১০ রিটার্ন করে, সাথে একটি পাস করা টেস্ট এবং একটি ফেইল করা টেস্ট রয়েছে।

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);
    }
}```

</Listing>

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

```console
$ 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 ফ্ল্যাগ দিয়ে তালিকা ১১-১০-এর টেস্টগুলো আবার চালাই, আমরা নিম্নলিখিত আউটপুট দেখতে পাই:

$ 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`

নাম অনুসারে টেস্টের একটি অংশ চালানো

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

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

pub fn add_two(a: u64) -> u64 {
    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

একক টেস্ট চালানো

আমরা যেকোনো টেস্ট ফাংশনের নাম 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-কে দেওয়া শুধুমাত্র প্রথম মানটি ব্যবহার করা হবে। কিন্তু একাধিক টেস্ট চালানোর একটি উপায় আছে।

একাধিক টেস্ট চালানোর জন্য ফিল্টারিং

আমরা একটি টেস্ট নামের অংশ নির্দিষ্ট করতে পারি, এবং যে কোনো টেস্টের নাম সেই মানের সাথে মিলবে তা চালানো হবে। উদাহরণস্বরূপ, যেহেতু আমাদের দুটি টেস্টের নামে 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 নামের টেস্টটি ফিল্টার করে বাদ দিয়েছে। আরও লক্ষ্য করুন যে একটি টেস্ট যে মডিউলে উপস্থিত থাকে তা টেস্টের নামের অংশ হয়ে যায়, তাই আমরা মডিউলের নামের উপর ফিল্টার করে একটি মডিউলের সমস্ত টেস্ট চালাতে পারি।

নির্দিষ্টভাবে অনুরোধ না করা পর্যন্ত কিছু টেস্ট উপেক্ষা করা

কখনও কখনও কিছু নির্দিষ্ট টেস্ট চালাতে অনেক সময় লাগতে পারে, তাই আপনি 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 হিসাবে তালিকাভুক্ত হয়েছে। যদি আমরা শুধুমাত্র উপেক্ষা করা টেস্টগুলো চালাতে চাই, আমরা 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 tests::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 চালাতে পারেন।