টেস্ট কীভাবে লিখতে হয় (How to Write Tests)

টেস্ট হলো Rust-এর ফাংশন যা যাচাই করে যে নন-টেস্ট কোড প্রত্যাশিতভাবে কাজ করছে কিনা। টেস্ট ফাংশনের বডিতে সাধারণত এই তিনটি কাজ করা হয়:

  • প্রয়োজনীয় ডেটা বা স্টেট সেট আপ করা।
  • যে কোডটি টেস্ট করতে চান সেটি রান করা।
  • ফলাফল আপনার প্রত্যাশা অনুযায়ী হয়েছে কিনা, তা অ্যাসার্ট (assert) করা।

আসুন, Rust-এর সেই ফিচারগুলো দেখি যেগুলো এই কাজগুলো করার জন্য টেস্ট লিখতে বিশেষভাবে সাহায্য করে। এর মধ্যে রয়েছে test অ্যাট্রিবিউট, কিছু ম্যাক্রো, এবং should_panic অ্যাট্রিবিউট।

একটি টেস্ট ফাংশনের গঠন (The Anatomy of a Test Function)

Rust-এ, সবচেয়ে সহজভাবে বলতে গেলে, একটি টেস্ট হল একটি ফাংশন যাকে test অ্যাট্রিবিউট দিয়ে চিহ্নিত করা হয়। অ্যাট্রিবিউট হল Rust কোডের অংশবিশেষ সম্পর্কে মেটাডেটা; একটি উদাহরণ হল derive অ্যাট্রিবিউট, যা আমরা চ্যাপ্টার ৫-এ স্ট্রাক্ট (struct)-এর সাথে ব্যবহার করেছি। একটি ফাংশনকে টেস্ট ফাংশনে পরিণত করতে, fn-এর আগে লাইনে #[test] যোগ করুন। যখন আপনি cargo test কমান্ড দিয়ে আপনার টেস্টগুলো রান করবেন, Rust একটি টেস্ট রানার বাইনারি তৈরি করবে যা চিহ্নিত ফাংশনগুলো রান করে এবং প্রতিটি টেস্ট ফাংশন পাস (pass) করেছে নাকি ফেইল (fail) করেছে তা রিপোর্ট করে।

যখনই আমরা Cargo দিয়ে একটি নতুন লাইব্রেরি প্রোজেক্ট তৈরি করি, আমাদের জন্য স্বয়ংক্রিয়ভাবে একটি টেস্ট মডিউল তৈরি হয়ে যায়, যার মধ্যে একটি টেস্ট ফাংশন থাকে। এই মডিউলটি আপনাকে টেস্ট লেখার জন্য একটি টেমপ্লেট দেয়, তাই আপনাকে প্রতিবার নতুন প্রোজেক্ট শুরু করার সময় সঠিক গঠন এবং সিনট্যাক্স (syntax) খুঁজতে হবে না। আপনি যত খুশি অতিরিক্ত টেস্ট ফাংশন এবং টেস্ট মডিউল যোগ করতে পারেন!

আমরা আসলে কোনো কোড টেস্ট করার আগে, টেমপ্লেট টেস্ট নিয়ে পরীক্ষা-নিরীক্ষা করে টেস্ট কীভাবে কাজ করে তার কিছু দিক অনুসন্ধান করব। তারপর আমরা কিছু বাস্তব-বিশ্বের (real-world) টেস্ট লিখব, যেগুলো আমাদের লেখা কিছু কোড কল করবে এবং অ্যাসার্ট (assert) করবে যে এটির আচরণ (behavior) সঠিক।

আসুন adder নামে একটি নতুন লাইব্রেরি প্রোজেক্ট তৈরি করি, যা দুটি সংখ্যা যোগ করবে:

$ cargo new adder --lib
     Created library `adder` project
$ cd adder

আপনার adder লাইব্রেরির src/lib.rs ফাইলের কনটেন্ট (contents) লিস্টিং 11-1-এর মতো হওয়া উচিত।

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

ফাইলটি একটি উদাহরণ add ফাংশন দিয়ে শুরু হয়, যাতে আমাদের টেস্ট করার মতো কিছু থাকে।

আপাতত, আসুন শুধুমাত্র it_works ফাংশনটির উপর ফোকাস করি। #[test] অ্যানোটেশনটি (annotation) লক্ষ্য করুন: এই অ্যাট্রিবিউটটি নির্দেশ করে যে এটি একটি টেস্ট ফাংশন, তাই টেস্ট রানার জানবে যে এই ফাংশনটিকে একটি টেস্ট হিসাবে বিবেচনা করতে হবে। tests মডিউলে আমাদের নন-টেস্ট ফাংশনও থাকতে পারে, যা সাধারণ পরিস্থিতি সেট আপ করতে বা সাধারণ অপারেশনগুলো সম্পাদন করতে সাহায্য করে, তাই আমাদেরকে সবসময় নির্দেশ করতে হয় যে কোন ফাংশনগুলো টেস্ট।

উদাহরণ ফাংশন বডিটি assert_eq! ম্যাক্রো ব্যবহার করে এটা অ্যাসার্ট করে যে result, যার মধ্যে 2 এবং 2 সহ add কল করার ফলাফল রয়েছে, 4 এর সমান। এই অ্যাসারশনটি (assertion) একটি সাধারণ টেস্টের ফরম্যাটের (format) উদাহরণ হিসাবে কাজ করে। আসুন এটি রান করে দেখি যে এই টেস্টটি পাস করে কিনা।

cargo test কমান্ড আমাদের প্রোজেক্টের সমস্ত টেস্ট রান করে, যেমনটি লিস্টিং 11-2-তে দেখানো হয়েছে।

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

running 1 test
test tests::it_works ... 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

Cargo টেস্টটি কম্পাইল (compile) এবং রান করেছে। আমরা running 1 test লাইনটি দেখতে পাচ্ছি। পরের লাইনটি জেনারেট হওয়া টেস্ট ফাংশনের নাম দেখায়, যাকে বলা হয় tests::it_works, এবং সেই টেস্টটি চালানোর ফলাফল হল ok। সার্বিক সারাংশ test result: ok. মানে হল যে সমস্ত টেস্ট পাস করেছে, এবং 1 passed; 0 failed অংশটি পাস বা ফেইল করা টেস্টের সংখ্যা গণনা করে।

একটি টেস্টকে উপেক্ষা (ignored) হিসাবে চিহ্নিত করা সম্ভব যাতে এটি কোনও নির্দিষ্ট দৃষ্টান্তে (instance) চালানো না হয়; আমরা এই অধ্যায়ের ["Ignoring Some Tests Unless Specifically Requested"][ignoring] বিভাগে এটি কভার করব। যেহেতু আমরা এখানে তা করিনি, সারাংশটি 0 ignored দেখায়। আমরা cargo test কমান্ডে একটি আর্গুমেন্ট (argument) পাস করতে পারি শুধুমাত্র সেই টেস্টগুলি চালানোর জন্য যাদের নাম একটি স্ট্রিং (string)-এর সাথে মেলে; এটিকে ফিল্টারিং (filtering) বলা হয় এবং আমরা ["Running a Subset of Tests by Name"][subset] বিভাগে এটি কভার করব। এখানে আমরা যে টেস্টগুলি চালানো হচ্ছে সেগুলি ফিল্টার করিনি, তাই সারাংশের শেষে 0 filtered out দেখাচ্ছে।

0 measured স্ট্যাটিস্টিকটি (statistic) বেঞ্চমার্ক (benchmark) টেস্টগুলোর জন্য যা পারফরম্যান্স (performance) পরিমাপ করে। বেঞ্চমার্ক টেস্টগুলো, এই লেখার সময় পর্যন্ত, শুধুমাত্র নাই‌টলি রাস্ট (nightly Rust)-এ উপলব্ধ। আরও জানতে [the documentation about benchmark tests][bench] দেখুন।

টেস্ট আউটপুটের (output) পরবর্তী অংশ Doc-tests adder থেকে শুরু করে যেকোনো ডকুমেন্টেশন (documentation) টেস্টের ফলাফলের জন্য। আমাদের এখনও কোনো ডকুমেন্টেশন টেস্ট নেই, তবে Rust আমাদের API ডকুমেন্টেশনে প্রদর্শিত যেকোনো কোড উদাহরণ কম্পাইল করতে পারে। এই ফিচারটি আপনার ডক্স (docs) এবং আপনার কোডকে সিঙ্ক (sync)-এ রাখতে সাহায্য করে! আমরা চ্যাপ্টার ১৪-এর ["Documentation Comments as Tests"][doc-comments] বিভাগে কীভাবে ডকুমেন্টেশন টেস্ট লিখতে হয় তা নিয়ে আলোচনা করব। আপাতত, আমরা Doc-tests আউটপুট উপেক্ষা করব।

আসুন আমাদের নিজেদের প্রয়োজনে টেস্টটি কাস্টমাইজ (customize) করা শুরু করি। প্রথমে, it_works ফাংশনের নাম পরিবর্তন করে অন্য একটি নাম দিন, যেমন exploration, এইভাবে:

Filename: src/lib.rs

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

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

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

তারপর আবার cargo test রান করুন। আউটপুট এখন it_works-এর পরিবর্তে exploration দেখাবে:

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

running 1 test
test tests::exploration ... 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

এখন আমরা আরেকটি টেস্ট যোগ করব, কিন্তু এবার আমরা এমন একটি টেস্ট তৈরি করব যা ফেইল করবে! যখন টেস্ট ফাংশনের মধ্যে কোনো কিছু প্যানিক (panic) করে তখন টেস্টগুলো ফেইল করে। প্রতিটি টেস্ট একটি নতুন থ্রেডে (thread) চালানো হয়, এবং যখন প্রধান থ্রেড (main thread) দেখে যে একটি টেস্ট থ্রেড মারা গেছে, তখন টেস্টটিকে ফেইল (failed) হিসাবে চিহ্নিত করা হয়। চ্যাপ্টার ৯-এ, আমরা আলোচনা করেছি যে কীভাবে প্যানিক করার সবচেয়ে সহজ উপায় হল panic! ম্যাক্রো কল করা। another নামের একটি ফাংশন হিসাবে নতুন টেস্টটি লিখুন, যাতে আপনার src/lib.rs ফাইলটি লিস্টিং 11-3-এর মতো দেখায়।

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

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

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

    #[test]
    fn another() {
        panic!("Make this test fail");
    }
}

cargo test ব্যবহার করে আবার টেস্টগুলো রান করুন। আউটপুট লিস্টিং 11-4-এর মতো হওয়া উচিত, যেখানে দেখা যাবে যে আমাদের exploration টেস্ট পাস করেছে এবং another ফেইল করেছে।

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

running 2 tests
test tests::another ... FAILED
test tests::exploration ... ok

failures:

---- tests::another stdout ----

thread 'tests::another' panicked at src/lib.rs:17:9:
Make this test fail
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::another

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`

ok-এর পরিবর্তে, test tests::another লাইনে FAILED দেখাচ্ছে। আলাদা আলাদা ফলাফল এবং সারাংশের মধ্যে দুটি নতুন অংশ (section) দেখা যাচ্ছে: প্রথমটি প্রতিটি টেস্ট ফেইলের বিস্তারিত কারণ প্রদর্শন করে। এই ক্ষেত্রে, আমরা বিস্তারিত পাচ্ছি যে another ফেইল করেছে কারণ এটি src/lib.rs ফাইলের ১৭ নম্বর লাইনে 'Make this test fail'-এ panicked at করেছে। পরের অংশে শুধুমাত্র সমস্ত ফেইলিং (failing) টেস্টের নাম তালিকাভুক্ত করা হয়েছে, যা দরকারী যখন অনেকগুলি টেস্ট থাকে এবং অনেকগুলি বিস্তারিত ফেইলিং টেস্ট আউটপুট থাকে। আমরা একটি ফেইলিং টেস্টের নাম ব্যবহার করতে পারি শুধুমাত্র সেই টেস্টটি চালানোর জন্য যাতে আরও সহজে ডিবাগ (debug) করা যায়; আমরা ["Controlling How Tests Are Run"][controlling-how-tests-are-run] বিভাগে টেস্ট চালানোর উপায় সম্পর্কে আরও কথা বলব।

সারাংশ লাইনটি শেষে প্রদর্শিত হয়: সামগ্রিকভাবে, আমাদের টেস্টের ফলাফল হল FAILED। আমাদের একটি টেস্ট পাস করেছে এবং একটি টেস্ট ফেইল করেছে।

এখন আপনি বিভিন্ন পরিস্থিতিতে টেস্টের ফলাফল দেখতে কেমন হয় তা দেখেছেন, আসুন panic! ছাড়া অন্য কিছু ম্যাক্রো দেখি যা টেস্টে দরকারি।

assert! ম্যাক্রো দিয়ে ফলাফল পরীক্ষা করা (Checking Results with the assert! Macro)

assert! ম্যাক্রো, স্ট্যান্ডার্ড লাইব্রেরি (standard library) দ্বারা সরবরাহ করা, তখন দরকারি যখন আপনি নিশ্চিত করতে চান যে একটি টেস্টের কোনো শর্ত (condition) true কিনা। আমরা assert! ম্যাক্রোকে একটি আর্গুমেন্ট দিই যা একটি বুলিয়ান (Boolean)-এ মূল্যায়ন করে। যদি মানটি true হয়, তবে কিছুই ঘটে না এবং টেস্ট পাস করে। যদি মানটি false হয়, তবে assert! ম্যাক্রো panic! কল করে টেস্টটিকে ফেইল করানোর জন্য। assert! ম্যাক্রো ব্যবহার করা আমাদের কোড আমাদের ইচ্ছামতো কাজ করছে কিনা তা পরীক্ষা করতে সাহায্য করে।

চ্যাপ্টার ৫-এ, লিস্টিং 5-15-তে, আমরা একটি Rectangle স্ট্রাক্ট এবং একটি can_hold মেথড ব্যবহার করেছি, যা এখানে লিস্টিং 11-5-এ পুনরাবৃত্তি করা হয়েছে। আসুন এই কোডটি src/lib.rs ফাইলে রাখি, তারপর assert! ম্যাক্রো ব্যবহার করে এটির জন্য কিছু টেস্ট লিখি।

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

can_hold মেথড একটি বুলিয়ান রিটার্ন করে, যার মানে হল এটি assert! ম্যাক্রোর জন্য একটি উপযুক্ত ব্যবহারের ক্ষেত্র। লিস্টিং 11-6-এ, আমরা একটি টেস্ট লিখি যা can_hold মেথডটি পরীক্ষা করে। একটি Rectangle ইন্সট্যান্স তৈরি করা হয় যার প্রস্থ (width) 8 এবং উচ্চতা (height) 7, এবং অ্যাসার্ট (assert) করা হয় যে এটি অন্য একটি Rectangle ইন্সট্যান্স ধারণ করতে পারে যার প্রস্থ 5 এবং উচ্চতা 1।

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

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

    #[test]
    fn larger_can_hold_smaller() {
        let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };

        assert!(larger.can_hold(&smaller));
    }
}

tests মডিউলের ভিতরে use super::*; লাইনটি লক্ষ করুন। tests মডিউল হল একটি সাধারণ মডিউল যা সাধারণ ভিজিবিলিটি (visibility) নিয়মগুলি অনুসরণ করে যা আমরা চ্যাপ্টার ৭-এ ["Paths for Referring to an Item in the Module Tree"][paths-for-referring-to-an-item-in-the-module-tree] বিভাগে কভার করেছি। কারণ tests মডিউলটি একটি অভ্যন্তরীণ মডিউল (inner module), আমাদেরকে বাইরের মডিউলের টেস্ট করা কোডটিকে অভ্যন্তরীণ মডিউলের স্কোপে (scope) আনতে হবে। আমরা এখানে একটি গ্লোব (glob) ব্যবহার করি, তাই বাইরের মডিউলে আমরা যা কিছু সংজ্ঞায়িত (define) করি তা এই tests মডিউলের জন্য উপলব্ধ।

আমরা আমাদের টেস্টের নাম দিয়েছি larger_can_hold_smaller, এবং আমরা দুটি প্রয়োজনীয় Rectangle ইন্সট্যান্স তৈরি করেছি। তারপর আমরা assert! ম্যাক্রো কল করেছি এবং এটিকে larger.can_hold(&smaller) কল করার ফলাফল পাস করেছি। এই এক্সপ্রেশনটির true রিটার্ন করার কথা, তাই আমাদের টেস্ট পাস করা উচিত। আসুন দেখা যাক!

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

running 1 test
test tests::larger_can_hold_smaller ... ok

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

   Doc-tests rectangle

running 0 tests

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

এটি পাস করে! আসুন আরেকটি টেস্ট যোগ করি, এবার অ্যাসার্ট করি যে একটি ছোট আয়তক্ষেত্র একটি বড় আয়তক্ষেত্র ধারণ করতে পারে না:

Filename: src/lib.rs

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

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

    #[test]
    fn larger_can_hold_smaller() {
        // --snip--
        let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };

        assert!(larger.can_hold(&smaller));
    }

    #[test]
    fn smaller_cannot_hold_larger() {
        let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };

        assert!(!smaller.can_hold(&larger));
    }
}

কারণ এই ক্ষেত্রে can_hold ফাংশনের সঠিক ফলাফল হল false, তাই assert! ম্যাক্রোতে পাস করার আগে আমাদের সেই ফলাফলটিকে নেগেট (negate) করতে হবে। ফলস্বরূপ, আমাদের টেস্ট পাস করবে যদি can_hold false রিটার্ন করে:

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

running 2 tests
test tests::larger_can_hold_smaller ... ok
test tests::smaller_cannot_hold_larger ... ok

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

   Doc-tests rectangle

running 0 tests

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

দুটি টেস্ট পাস করেছে! এখন দেখা যাক আমাদের টেস্টের ফলাফলের কী হয় যখন আমরা আমাদের কোডে একটি বাগ (bug) আনি। আমরা can_hold মেথডের ইমপ্লিমেন্টেশন (implementation) পরিবর্তন করব, প্রস্থ তুলনা করার সময় বৃহত্তর-দেন (greater-than) চিহ্নটিকে ছোট-দেন (less-than) চিহ্ন দিয়ে প্রতিস্থাপন করে:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

// --snip--
impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width < other.width && self.height > other.height
    }
}

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

    #[test]
    fn larger_can_hold_smaller() {
        let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };

        assert!(larger.can_hold(&smaller));
    }

    #[test]
    fn smaller_cannot_hold_larger() {
        let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };

        assert!(!smaller.can_hold(&larger));
    }
}

এখন টেস্টগুলো চালালে নিম্নলিখিত আউটপুট পাওয়া যাবে:

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

running 2 tests
test tests::larger_can_hold_smaller ... FAILED
test tests::smaller_cannot_hold_larger ... ok

failures:

---- tests::larger_can_hold_smaller stdout ----

thread 'tests::larger_can_hold_smaller' panicked at src/lib.rs:28:9:
assertion failed: larger.can_hold(&smaller)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::larger_can_hold_smaller

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`

আমাদের টেস্ট বাগটি ধরে ফেলেছে! কারণ larger.width হল 8 এবং smaller.width হল 5, can_hold-এ প্রস্থের তুলনা এখন false রিটার্ন করে: 8, 5-এর চেয়ে ছোট নয়।

assert_eq! এবং assert_ne! ম্যাক্রো ব্যবহার করে সমতা পরীক্ষা করা (Testing Equality with the assert_eq! and assert_ne! Macros)

কার্যকারিতা যাচাই করার একটি সাধারণ উপায় হল টেস্ট করা কোডের ফলাফল এবং কোড থেকে প্রত্যাশিত মানের মধ্যে সমতা পরীক্ষা করা। আপনি assert! ম্যাক্রো ব্যবহার করে এবং == অপারেটর ব্যবহার করে একটি এক্সপ্রেশন পাস করে এটি করতে পারেন। তবে, এটি এত সাধারণ একটি টেস্ট যে স্ট্যান্ডার্ড লাইব্রেরি এই টেস্টটি আরও সুবিধাজনকভাবে সম্পাদন করার জন্য দুটি ম্যাক্রো সরবরাহ করে—assert_eq! এবং assert_ne!। এই ম্যাক্রোগুলি যথাক্রমে দুটি আর্গুমেন্টকে সমতা বা অসমতার জন্য তুলনা করে। অ্যাসারশন ব্যর্থ হলে তারা দুটি মানও প্রিন্ট করবে, যার ফলে টেস্টটি কেন ব্যর্থ হয়েছে তা দেখা সহজ হয়; অপরপক্ষে, assert! ম্যাক্রো শুধুমাত্র নির্দেশ করে যে এটি == এক্সপ্রেশনের জন্য একটি false মান পেয়েছে, false মানের দিকে পরিচালিত মানগুলি প্রিন্ট না করেই।

লিস্টিং 11-7-এ, আমরা add_two নামে একটি ফাংশন লিখি যা তার প্যারামিটারে 2 যোগ করে, তারপর আমরা assert_eq! ম্যাক্রো ব্যবহার করে এই ফাংশনটি টেস্ট করি।

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

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

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

আসুন পরীক্ষা করি যে এটি পাস করে কিনা!

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

running 1 test
test tests::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

আমরা result নামে একটি ভেরিয়েবল তৈরি করি যেখানে add_two(2) কল করার ফলাফল থাকে। তারপর আমরা result এবং 4 কে assert_eq! এর আর্গুমেন্ট হিসেবে পাস করি। এই টেস্টের জন্য আউটপুট লাইনটি হল test tests::it_adds_two ... ok, এবং ok লেখাটি নির্দেশ করে যে আমাদের টেস্ট পাস করেছে!

আসুন আমাদের কোডে একটি বাগ ঢুকিয়ে দেখি assert_eq! ব্যর্থ হলে দেখতে কেমন হয়। add_two ফাংশনের ইমপ্লিমেন্টেশন পরিবর্তন করে 3 যোগ করি:

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

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

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

আবার টেস্ট রান করুন:

$ cargo test
   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::it_adds_two ... FAILED

failures:

---- tests::it_adds_two stdout ----

thread 'tests::it_adds_two' panicked at src/lib.rs:12:9:
assertion `left == right` failed
  left: 5
 right: 4
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::it_adds_two

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

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

আমাদের টেস্ট বাগটি ধরে ফেলেছে! it_adds_two টেস্টটি ব্যর্থ হয়েছে, এবং মেসেজটি আমাদের বলছে যে ব্যর্থ হওয়া অ্যাসারশনটি হল assertion `left == right` failed এবং left এবং right-এর মান কী কী। এই মেসেজটি আমাদের ডিবাগিং শুরু করতে সাহায্য করে: left আর্গুমেন্ট, যেখানে আমাদের add_two(2) কল করার ফলাফল ছিল, সেটি ছিল 5 কিন্তু right আর্গুমেন্টটি ছিল 4। আপনি কল্পনা করতে পারেন যে এটি বিশেষভাবে সহায়ক হবে যখন আমাদের অনেকগুলি টেস্ট চলতে থাকবে।

মনে রাখবেন যে কিছু ভাষা এবং টেস্ট ফ্রেমওয়ার্কে, সমতা অ্যাসারশন ফাংশনের প্যারামিটারগুলিকে expected এবং actual বলা হয় এবং আমরা যে ক্রমে আর্গুমেন্টগুলি নির্দিষ্ট করি তা গুরুত্বপূর্ণ। যাইহোক, Rust-এ, সেগুলিকে left এবং right বলা হয়, এবং আমরা যে ক্রমে প্রত্যাশিত মান এবং কোড যে মান তৈরি করে তা নির্দিষ্ট করি, তাতে কিছু যায় আসে না। আমরা এই টেস্টের অ্যাসারশনটিকে assert_eq!(4, result) হিসাবে লিখতে পারতাম, যার ফলে একই ব্যর্থতার মেসেজ আসবে যা assertion failed: `(left == right)` প্রদর্শন করে।

assert_ne! ম্যাক্রো পাস করবে যদি আমরা যে দুটি মান দিই তা সমান না হয় এবং যদি সেগুলি সমান হয় তবে ব্যর্থ হবে। এই ম্যাক্রোটি সেইসব ক্ষেত্রের জন্য সবচেয়ে দরকারী যখন আমরা নিশ্চিত নই যে একটি মান কী হবে, কিন্তু আমরা জানি মানটি নিশ্চিতভাবে কী হওয়া উচিত নয়। উদাহরণস্বরূপ, যদি আমরা এমন একটি ফাংশন পরীক্ষা করি যা তার ইনপুটকে কোনওভাবে পরিবর্তন করার গ্যারান্টিযুক্ত, কিন্তু ইনপুটটি কীভাবে পরিবর্তন করা হয়েছে তা সপ্তাহের দিনের উপর নির্ভর করে যেদিন আমরা আমাদের টেস্টগুলি চালাই, তাহলে সম্ভবত সেরা জিনিসটি অ্যাসার্ট করা হল যে ফাংশনের আউটপুট ইনপুটের সমান নয়।

ভিতরে ভিতরে, assert_eq! এবং assert_ne! ম্যাক্রোগুলি যথাক্রমে == এবং != অপারেটর ব্যবহার করে। যখন অ্যাসারশনগুলি ব্যর্থ হয়, তখন এই ম্যাক্রোগুলি তাদের আর্গুমেন্টগুলিকে ডিবাগ ফরম্যাটিং ব্যবহার করে প্রিন্ট করে, যার অর্থ হল যে মানগুলির তুলনা করা হচ্ছে সেগুলিতে অবশ্যই PartialEq এবং Debug ট্রেইট (trait) ইমপ্লিমেন্ট (implement) করা থাকতে হবে। সমস্ত প্রিমিটিভ টাইপ (primitive type) এবং স্ট্যান্ডার্ড লাইব্রেরির বেশিরভাগ টাইপ এই ট্রেইটগুলি ইমপ্লিমেন্ট করে। আপনার নিজের সংজ্ঞায়িত করা স্ট্রাক্ট এবং এনামগুলির (enum) জন্য, আপনাকে সেই টাইপগুলির সমতা অ্যাসার্ট করার জন্য PartialEq ইমপ্লিমেন্ট করতে হবে। অ্যাসারশন ব্যর্থ হলে মানগুলি প্রিন্ট করার জন্য আপনাকে Debug ও ইমপ্লিমেন্ট করতে হবে। যেহেতু উভয় ট্রেইটই ডিরাইভেবল (derivable) ট্রেইট, যেমনটি চ্যাপ্টার ৫-এর লিস্টিং 5-12-তে উল্লেখ করা হয়েছে, তাই এটি সাধারণত আপনার স্ট্রাক্ট বা এনাম সংজ্ঞাতে #[derive(PartialEq, Debug)] অ্যানোটেশন যোগ করার মতোই সহজ। এই এবং অন্যান্য ডিরাইভেবল ট্রেইট সম্পর্কে আরও বিশদ বিবরণের জন্য অ্যাপেন্ডিক্স সি, ["ডেরাইভেবল ট্রেইট"][derivable-traits] দেখুন।

কাস্টম ব্যর্থতার বার্তা যোগ করা (Adding Custom Failure Messages)

আপনি assert!, assert_eq!, এবং assert_ne! ম্যাক্রোতে ঐচ্ছিক আর্গুমেন্ট হিসাবে ব্যর্থতার বার্তার সাথে প্রিন্ট করার জন্য একটি কাস্টম মেসেজও যোগ করতে পারেন। প্রয়োজনীয় আর্গুমেন্টগুলির পরে নির্দিষ্ট করা যেকোনো আর্গুমেন্ট format! ম্যাক্রোতে পাঠানো হয় (চ্যাপ্টার ৮-এ ["কনক্যাটেনেশন উইথ দ্যা + অপারেটর অর দ্যা format! ম্যাক্রো"][concatenation-with-the--operator-or-the-format-macro] তে আলোচনা করা হয়েছে), তাই আপনি একটি ফরম্যাট স্ট্রিং পাস করতে পারেন যাতে {} প্লেসহোল্ডার (placeholder) এবং সেই প্লেসহোল্ডারগুলিতে যাওয়ার জন্য মান রয়েছে। কাস্টম মেসেজগুলি একটি অ্যাসারশনের অর্থ কী তা ডকুমেন্ট করার জন্য দরকারী; যখন একটি টেস্ট ব্যর্থ হয়, তখন কোডের সমস্যাটি কী তা সম্পর্কে আপনার আরও ভাল ধারণা থাকবে।

উদাহরণস্বরূপ, ধরা যাক আমাদের কাছে একটি ফাংশন রয়েছে যা লোকেদের নাম দিয়ে অভিবাদন জানায় এবং আমরা পরীক্ষা করতে চাই যে আমরা ফাংশনে যে নামটি পাস করি তা আউটপুটে প্রদর্শিত হবে:

Filename: src/lib.rs

pub fn greeting(name: &str) -> String {
    format!("Hello {name}!")
}

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

    #[test]
    fn greeting_contains_name() {
        let result = greeting("Carol");
        assert!(result.contains("Carol"));
    }
}

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

এখন আসুন name বাদ দেওয়ার জন্য greeting পরিবর্তন করে এই কোডে একটি বাগ ঢুকিয়ে দেখি যে ডিফল্ট টেস্ট ব্যর্থতা দেখতে কেমন হয়:

pub fn greeting(name: &str) -> String {
    String::from("Hello!")
}

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

    #[test]
    fn greeting_contains_name() {
        let result = greeting("Carol");
        assert!(result.contains("Carol"));
    }
}

এই টেস্ট চালালে নিম্নলিখিত ফলাফল পাওয়া যাবে:

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

running 1 test
test tests::greeting_contains_name ... FAILED

failures:

---- tests::greeting_contains_name stdout ----

thread 'tests::greeting_contains_name' panicked at src/lib.rs:12:9:
assertion failed: result.contains("Carol")
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::greeting_contains_name

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

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

এই ফলাফলটি কেবল নির্দেশ করে যে অ্যাসারশনটি ব্যর্থ হয়েছে এবং অ্যাসারশনটি কোন লাইনে রয়েছে। একটি আরও দরকারী ব্যর্থতার বার্তা greeting ফাংশন থেকে প্রাপ্ত মানটি প্রিন্ট করবে। আসুন একটি কাস্টম ব্যর্থতার বার্তা যোগ করি যা একটি ফরম্যাট স্ট্রিং নিয়ে গঠিত এবং যাতে greeting ফাংশন থেকে আমরা যে প্রকৃত মান পেয়েছি তার সাথে একটি প্লেসহোল্ডার পূরণ করা হয়েছে:

pub fn greeting(name: &str) -> String {
    String::from("Hello!")
}

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

    #[test]
    fn greeting_contains_name() {
        let result = greeting("Carol");
        assert!(
            result.contains("Carol"),
            "Greeting did not contain name, value was `{result}`"
        );
    }
}

এখন যখন আমরা টেস্টটি চালাব, তখন আমরা একটি আরও তথ্যপূর্ণ ত্রুটির বার্তা পাব:

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

running 1 test
test tests::greeting_contains_name ... FAILED

failures:

---- tests::greeting_contains_name stdout ----

thread 'tests::greeting_contains_name' panicked at src/lib.rs:12:9:
Greeting did not contain name, value was `Hello!`
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::greeting_contains_name

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

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

আমরা টেস্টের আউটপুটে প্রকৃত মানটি দেখতে পাচ্ছি, যা আমাদের কী ঘটার কথা ছিল তার পরিবর্তে কী ঘটেছে তা ডিবাগ করতে সাহায্য করবে।

should_panic দিয়ে প্যানিক পরীক্ষা করা (Checking for Panics with should_panic)

রিটার্ন মান পরীক্ষা করার পাশাপাশি, আমাদের কোড প্রত্যাশিতভাবে ত্রুটির শর্তগুলি (error conditions) পরিচালনা করে কিনা তা পরীক্ষা করা গুরুত্বপূর্ণ। উদাহরণস্বরূপ, চ্যাপ্টার ৯, লিস্টিং 9-13-এ তৈরি করা Guess টাইপটি বিবেচনা করুন। Guess ব্যবহার করে এমন অন্যান্য কোড এই গ্যারান্টির উপর নির্ভর করে যে Guess ইন্সট্যান্সগুলিতে কেবল 1 থেকে 100-এর মধ্যে মান থাকবে। আমরা একটি টেস্ট লিখতে পারি যা নিশ্চিত করে যে সেই সীমার বাইরের মান সহ একটি Guess ইন্সট্যান্স তৈরি করার চেষ্টা করলে প্যানিক (panic) হয়।

আমরা আমাদের টেস্ট ফাংশনে should_panic অ্যাট্রিবিউট যোগ করে এটি করি। ফাংশনের ভিতরের কোড প্যানিক করলে টেস্ট পাস করে; ফাংশনের ভিতরের কোড প্যানিক না করলে টেস্ট ব্যর্থ হয়।

লিস্টিং 11-8 একটি টেস্ট দেখায় যা পরীক্ষা করে যে Guess::new-এর ত্রুটির শর্তগুলি তখনই ঘটে যখন আমরা সেগুলি প্রত্যাশা করি।

pub struct Guess {
    value: i32,
}

impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess value must be between 1 and 100, got {value}.");
        }

        Guess { value }
    }
}

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

    #[test]
    #[should_panic]
    fn greater_than_100() {
        Guess::new(200);
    }
}

আমরা #[should_panic] অ্যাট্রিবিউটটি #[test] অ্যাট্রিবিউটের পরে এবং এটি যে টেস্ট ফাংশনটিতে প্রযোজ্য তার আগে রাখি। আসুন এই টেস্টটি পাস করার সময় ফলাফলটি দেখি:

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

running 1 test
test tests::greater_than_100 - should panic ... ok

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

   Doc-tests guessing_game

running 0 tests

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

দারুণ দেখাচ্ছে! এখন আসুন আমাদের কোডে একটি বাগ ঢুকিয়ে দিই, new ফাংশনটি 100-এর বেশি হলে যে প্যানিক করবে সেই শর্তটি সরিয়ে দিয়ে:

pub struct Guess {
    value: i32,
}

// --snip--
impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 {
            panic!("Guess value must be between 1 and 100, got {value}.");
        }

        Guess { value }
    }
}

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

    #[test]
    #[should_panic]
    fn greater_than_100() {
        Guess::new(200);
    }
}

যখন আমরা লিস্টিং 11-8-এর টেস্টটি চালাই, তখন এটি ব্যর্থ হবে:

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

running 1 test
test tests::greater_than_100 - should panic ... FAILED

failures:

---- tests::greater_than_100 stdout ----
note: test did not panic as expected

failures:
    tests::greater_than_100

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

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

আমরা এই ক্ষেত্রে খুব সহায়ক বার্তা পাই না, কিন্তু যখন আমরা টেস্ট ফাংশনটি দেখি, তখন আমরা দেখতে পাই যে এটি #[should_panic] দিয়ে চিহ্নিত করা হয়েছে। আমরা যে ব্যর্থতা পেয়েছি তার অর্থ হল টেস্ট ফাংশনের কোডটি প্যানিকের কারণ হয়নি।

should_panic ব্যবহার করে এমন টেস্টগুলি সুনির্দিষ্ট (precise) নাও হতে পারে। একটি should_panic টেস্ট পাস করবে এমনকি যদি টেস্টটি আমাদের প্রত্যাশিত কারণ থেকে ভিন্ন কারণে প্যানিক করে। should_panic টেস্টগুলিকে আরও সুনির্দিষ্ট করতে, আমরা should_panic অ্যাট্রিবিউটে একটি ঐচ্ছিক expected প্যারামিটার যোগ করতে পারি। টেস্ট হার্নেস (harness) নিশ্চিত করবে যে ব্যর্থতার বার্তায় প্রদত্ত টেক্সট রয়েছে। উদাহরণস্বরূপ, লিস্টিং 11-9-এ Guess-এর জন্য সংশোধিত কোডটি বিবেচনা করুন যেখানে new ফাংশনটি মানের উপর নির্ভর করে বিভিন্ন বার্তা সহ প্যানিক করে, মানটি খুব ছোট বা খুব বড় কিনা।

pub struct Guess {
    value: i32,
}

// --snip--

impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 {
            panic!(
                "Guess value must be greater than or equal to 1, got {value}."
            );
        } else if value > 100 {
            panic!(
                "Guess value must be less than or equal to 100, got {value}."
            );
        }

        Guess { value }
    }
}

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

    #[test]
    #[should_panic(expected = "less than or equal to 100")]
    fn greater_than_100() {
        Guess::new(200);
    }
}

এই টেস্টটি পাস করবে কারণ আমরা should_panic অ্যাট্রিবিউটের expected প্যারামিটারে যে মানটি রেখেছি তা হল Guess::new ফাংশন যে বার্তার সাথে প্যানিক করে তার একটি সাবস্ট্রিং। আমরা সম্পূর্ণ প্যানিক বার্তাটি নির্দিষ্ট করতে পারতাম যা আমরা আশা করি, যা এই ক্ষেত্রে Guess value must be less than or equal to 100, got 200 হবে। আপনি কী নির্দিষ্ট করতে চান তা নির্ভর করে প্যানিক বার্তার কতটা অনন্য বা ডায়নামিক এবং আপনি আপনার টেস্টটি কতটা সুনির্দিষ্ট করতে চান তার উপর। এই ক্ষেত্রে, প্যানিক বার্তার একটি সাবস্ট্রিং যথেষ্ট যাতে নিশ্চিত করা যায় যে টেস্ট ফাংশনের কোডটি else if value > 100 কেসটি সম্পাদন করে।

একটি should_panic টেস্ট যখন একটি expected মেসেজ সহ ব্যর্থ হয় তখন কী ঘটে তা দেখতে, আসুন আবার আমাদের কোডে একটি বাগ ঢুকিয়ে দিই if value < 1 এবং else if value > 100 ব্লকের বডি অদলবদল করে:

pub struct Guess {
    value: i32,
}

impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 {
            panic!(
                "Guess value must be less than or equal to 100, got {value}."
            );
        } else if value > 100 {
            panic!(
                "Guess value must be greater than or equal to 1, got {value}."
            );
        }

        Guess { value }
    }
}

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

    #[test]
    #[should_panic(expected = "less than or equal to 100")]
    fn greater_than_100() {
        Guess::new(200);
    }
}

এবার যখন আমরা should_panic টেস্ট চালাব, তখন এটি ব্যর্থ হবে:

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

running 1 test
test tests::greater_than_100 - should panic ... FAILED

failures:

---- tests::greater_than_100 stdout ----

thread 'tests::greater_than_100' panicked at src/lib.rs:12:13:
Guess value must be greater than or equal to 1, got 200.
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: panic did not contain expected string
      panic message: `"Guess value must be greater than or equal to 1, got 200."`,
 expected substring: `"less than or equal to 100"`

failures:
    tests::greater_than_100

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

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

ব্যর্থতার বার্তাটি নির্দেশ করে যে এই টেস্টটি সত্যিই আমাদের প্রত্যাশা অনুযায়ী প্যানিক করেছে, কিন্তু প্যানিক বার্তায় প্রত্যাশিত স্ট্রিং less than or equal to 100 অন্তর্ভুক্ত ছিল না। আমরা এই ক্ষেত্রে যে প্যানিক বার্তাটি পেয়েছি তা হল Guess value must be greater than or equal to 1, got 200. এখন আমরা বের করতে শুরু করতে পারি আমাদের বাগটি কোথায়!

টেস্টে Result<T, E> ব্যবহার করা (Using Result<T, E> in Tests)

আমাদের এখনও পর্যন্ত সমস্ত টেস্ট ব্যর্থ হলে প্যানিক করে। আমরা Result<T, E> ব্যবহার করে এমন টেস্টও লিখতে পারি! এখানে লিস্টিং 11-1-এর টেস্টটি রয়েছে, Result<T, E> ব্যবহার করার জন্য পুনরায় লেখা হয়েছে এবং প্যানিক করার পরিবর্তে একটি Err রিটার্ন করে:

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

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

    #[test]
    fn it_works() -> Result<(), String> {
        let result = add(2, 2);

        if result == 4 {
            Ok(())
        } else {
            Err(String::from("two plus two does not equal four"))
        }
    }
}

it_works ফাংশনটির এখন Result<(), String> রিটার্ন টাইপ রয়েছে। ফাংশনের বডিতে, assert_eq! ম্যাক্রো কল করার পরিবর্তে, আমরা টেস্ট পাস করলে Ok(()) এবং টেস্ট ব্যর্থ হলে ভিতরে একটি String সহ একটি Err রিটার্ন করি।

টেস্টগুলি যাতে Result<T, E> রিটার্ন করে লেখা আপনাকে টেস্টের বডিতে কোয়েশ্চন মার্ক অপারেটর (?) ব্যবহার করতে সক্ষম করে, যা এমন টেস্ট লেখার একটি সুবিধাজনক উপায় হতে পারে যেগুলির মধ্যে কোনও অপারেশন যদি একটি Err ভেরিয়েন্ট রিটার্ন করে তবে ব্যর্থ হওয়া উচিত।

আপনি Result<T, E> ব্যবহার করে এমন টেস্টগুলিতে #[should_panic] অ্যানোটেশন ব্যবহার করতে পারবেন না। একটি অপারেশন একটি Err ভেরিয়েন্ট রিটার্ন করে তা অ্যাসার্ট করতে, Result<T, E> মানের উপর কোয়েশ্চন মার্ক অপারেটর (?) ব্যবহার করবেন না। পরিবর্তে, assert!(value.is_err()) ব্যবহার করুন।

এখন আপনি টেস্ট লেখার বিভিন্ন উপায় জানেন, আসুন দেখি যখন আমরা আমাদের টেস্টগুলি চালাই তখন কী ঘটে এবং cargo test-এর সাথে আমরা যে বিভিন্ন অপশন ব্যবহার করতে পারি সেগুলি অন্বেষণ করি।