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

การควบคุมวิธีรันการทดสอบ (Controlling How Tests Are Run)

เช่นเดียวกับที่ cargo run คอมไพล์โค้ดของคุณแล้วรันไบนารีที่ได้ คำสั่ง cargo test จะคอมไพล์โค้ดของคุณในโหมดการทดสอบและรันไบนารีทดสอบที่ได้ พฤติกรรมเริ่มต้นของไบนารีที่สร้างโดย cargo test คือการรันการทดสอบทั้งหมดแบบขนาน (parallel) และดักจับผลลัพธ์ (capture output) ที่เกิดขึ้นระหว่างการรันการทดสอบ ซึ่งจะป้องกันไม่ให้ผลลัพธ์ถูกแสดงออกมาและช่วยให้การอ่านผลการทดสอบง่ายยิ่งขึ้น อย่างไรก็ตาม คุณสามารถระบุตัวเลือกบรรทัดคำสั่ง (command line options) เพื่อเปลี่ยนแปลงพฤติกรรมเริ่มต้นนี้ได้

ตัวเลือกบรรทัดคำสั่งบางตัวส่งไปยัง cargo test และบางตัวส่งไปยังไบนารีทดสอบที่ได้ เพื่อแยกแยะอาร์กิวเมนต์สองประเภทนี้ คุณจะต้องแสดงรายการอาร์กิวเมนต์ที่ส่งไปยัง cargo test ตามด้วยตัวแยก -- จากนั้นตามด้วยอาร์กิวเมนต์ที่จะส่งไปยังไบนารีทดสอบ การรัน cargo test --help จะแสดงตัวเลือกที่คุณสามารถใช้กับ cargo test ได้ และการรัน cargo test -- --help จะแสดงตัวเลือกที่คุณสามารถใช้หลังตัวแยก อ็อปชันเหล่านี้ยังมีอธิบายในเอกสาร ส่วน “Tests” ของหนังสือ The rustc Book

การรันการทดสอบแบบขนานหรือแบบตามลำดับ (Running Tests in Parallel or Consecutively)

เมื่อคุณรันการทดสอบหลายรายการ ตามค่าเริ่มต้น การทดสอบจะถูกรันแบบขนานโดยใช้เธรด (threads) ซึ่งหมายความว่าจะทำงานเสร็จเร็วขึ้นและคุณจะได้รับข้อติชม (feedback) เร็วยิ่งขึ้น เนื่องจากรายการทดสอบกำลังรันพร้อมๆ กัน คุณต้องตรวจสอบให้แน่ใจว่าการทดสอบของคุณไม่ได้พึ่งพากันและกัน หรือพึ่งพาสถานะที่ใช้ร่วมกัน (shared state) รวมถึงสภาพแวดล้อมที่ใช้ร่วมกัน เช่น ไดเรกทอรีทำงานปัจจุบันหรือตัวแปรสภาพแวดล้อม (environment variables)

ตัวอย่างเช่น สมมติว่าการทดสอบแต่ละตัวของคุณรันโค้ดที่สร้างไฟล์บนดิสก์ชื่อ test-output.txt และเขียนข้อมูลลงในไฟล์นั้น จากนั้นการทดสอบแต่ละตัวอ่านข้อมูลในไฟล์นั้นและยืนยันว่าไฟล์นั้นมีค่าตามที่กำหนด ซึ่งแตกต่างกันในการทดสอบแต่ละตัว เนื่องจากรายการทดสอบรันพร้อมกัน การทดสอบหนึ่งอาจเขียนทับไฟล์ในระหว่างเวลาที่อีกการทดสอบหนึ่งกำลังเขียนและอ่านไฟล์ การทดสอบที่สองก็จะล้มเหลว ไม่ใช่เพราะโค้ดไม่ถูกต้อง แต่เป็นเพราะการทดสอบแทรกแซงกันเองขณะรันแบบขนาน ทางแก้หนึ่งคือทำให้แน่ใจว่าแต่ละการทดสอบเขียนลงไฟล์คนละไฟล์กัน ทางแก้อีกทางหนึ่งคือการรันการทดสอบทีละตัว

หากคุณไม่อยากรันการทดสอบแบบขนาน หรือต้องการการควบคุมจำนวนเธรดที่ใช้อย่างละเอียดมากขึ้น คุณสามารถส่งแฟล็ก --test-threads และจำนวนเธรดที่คุณต้องการใช้ไปยังไบนารีทดสอบได้ ลองดูตัวอย่างต่อไปนี้:

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

เราตั้งค่าจำนวนเธรดทดสอบเป็น 1 เพื่อบอกโปรแกรมว่าไม่ต้องใช้การทำงานแบบขนาน การรันการทดสอบโดยใช้เธรดเดียวจะใช้เวลานานกว่าการรันแบบขนาน แต่การทดสอบจะไม่แทรกแซงกันและกันหากมีการใช้สถานะร่วมกัน

การแสดงผลลัพธ์ของฟังก์ชัน (Showing Function Output)

ตามค่าเริ่มต้น หากการทดสอบผ่าน ไลบรารีการทดสอบของ Rust จะดักจับ (capture) สิ่งใดก็ตามที่พิมพ์ไปยัง standard output ตัวอย่างเช่น หากเราเรียกใช้ println! ในการทดสอบและการทดสอบนั้นผ่าน เราจะไม่เห็นผลลัพธ์ของ println! ในเทอร์มินัล เราจะเห็นเพียงบรรทัดที่ระบุว่าการทดสอบผ่าน หากการทดสอบล้มเหลว เราจะเห็นสิ่งที่ถูกพิมพ์ไปยัง standard output พร้อมกับส่วนที่เหลือของข้อความความล้มเหลว

ตัวอย่างเช่น Listing 11-10 มีฟังก์ชันสมมติที่พิมพ์ค่าของพารามิเตอร์และคืนค่า 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' (6019863) 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 จะปรากฏในส่วนสรุปผลการทดสอบ ซึ่งแสดงสาเหตุของการล้มเหลวด้วย

หากเราต้องการเห็นค่าที่พิมพ์สำหรับการทดสอบที่ผ่านด้วย เราสามารถบอกให้ Rust แสดงผลลัพธ์ของการทดสอบที่สำเร็จได้ด้วย --show-output:

$ cargo test -- --show-output

เมื่อเรารันการทดสอบใน Listing 11-10 อีกครั้งพร้อมกับแฟล็ก --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' (6022313) 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)

การรันชุดการทดสอบทั้งหมด (full test suite) ในบางครั้งอาจใช้เวลานาน หากคุณกำลังทำงานกับโค้ดในส่วนใดส่วนหนึ่ง คุณอาจต้องการรันเฉพาะการทดสอบที่เกี่ยวข้องกับโค้ดส่วนนั้น คุณสามารถเลือกการทดสอบที่จะรันได้โดยการส่งชื่อการทดสอบที่คุณต้องการรันเป็นอาร์กิวเมนต์ไปยัง cargo test

เพื่อสาธิตวิธีรันส่วนย่อยของการทดสอบ ขั้นแรกเราจะสร้างการทดสอบสามรายการสำหรับฟังก์ชัน add_two ของเรา ดังแสดงใน Listing 11-11 และเลือกตัวที่จะรัน

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

การรันการทดสอบเดี่ยว (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 Tests Unless Specifically Requested)

บางครั้งการทดสอบบางรายการอาจใช้เวลารันนานมาก คุณจึงอาจต้องการยกเว้นรายการเหล่านั้นในระหว่างการรัน cargo test ตามปกติ แทนที่จะระบุรายการทดสอบทั้งหมดที่คุณต้องการรันเป็นอาร์กิวเมนต์ คุณสามารถระบุ attribute 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