کنترل نحوه اجرای تستها
دقیقاً همانطور که cargo run
کد شما را کامپایل کرده و باینری حاصل را اجرا میکند، cargo test
کد شما را در حالت تست کامپایل کرده و باینری تست حاصل را اجرا میکند. رفتار پیشفرض باینری تولیدشده توسط cargo test
این است که تمام تستها را به صورت موازی اجرا کرده و خروجی تولید شده در طول اجرای تستها را ضبط کند. این کار از نمایش خروجی جلوگیری کرده و خواندن خروجی مرتبط با نتایج تست را آسانتر میکند. با این حال، میتوانید با مشخص کردن گزینههای خط فرمان این رفتار پیشفرض را تغییر دهید.
برخی گزینههای خط فرمان به cargo test
میروند و برخی دیگر به باینری تست حاصل ارسال میشوند. برای جدا کردن این دو نوع آرگومان، آرگومانهایی که به cargo test
میروند را ذکر کنید و سپس جداکننده --
و آرگومانهایی که به باینری تست میروند را بیاورید. اجرای cargo test --help
گزینههایی را نمایش میدهد که میتوانید با cargo test
استفاده کنید، و اجرای cargo test -- --help
گزینههایی را که میتوانید پس از جداکننده استفاده کنید نمایش میدهد. این گزینهها همچنین در بخش “تستها” از کتاب rustc مستند شدهاند.
اجرای تستها به صورت موازی یا متوالی
وقتی چندین تست را اجرا میکنید، به طور پیشفرض این تستها به صورت موازی با استفاده از نخها (threads) اجرا میشوند، به این معنی که سریعتر به پایان میرسند و بازخورد سریعتری دریافت میکنید. از آنجا که تستها به صورت همزمان اجرا میشوند، باید اطمینان حاصل کنید که تستهای شما به یکدیگر یا به هیچ حالت مشترکی، از جمله یک محیط مشترک مانند دایرکتوری کاری جاری یا متغیرهای محیطی، وابسته نیستند.
برای مثال، فرض کنید هر یک از تستهای شما کدی را اجرا میکند که یک فایل به نام 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);
}
}
println!
استفاده میکندوقتی این تستها را با 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
، در بخش خلاصه خروجی تست ظاهر میشود که علت شکست تست را نیز نشان میدهد.
اگر بخواهیم مقادیر چاپشده برای تستهای پاسشده را نیز ببینیم، میتوانیم به Rust بگوییم که خروجی تستهای موفق را با استفاده از --show-output
نیز نمایش دهد:
$ 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`
اجرای زیرمجموعهای از تستها با نام
گاهی اوقات، اجرای یک مجموعه کامل از تستها میتواند زمان زیادی ببرد. اگر در حال کار روی کدی در یک بخش خاص هستید، ممکن است بخواهید فقط تستهای مربوط به آن کد را اجرا کنید. میتوانید با پاس دادن نام یا نامهای تستهایی که میخواهید اجرا کنید به cargo test
، انتخاب کنید که کدام تستها اجرا شوند.
برای نشان دادن نحوه اجرای یک زیرمجموعه از تستها، ابتدا سه تست برای تابع add_two
خود ایجاد میکنیم، همانطور که در لیست ۱۱-۱۱ نشان داده شده است، و انتخاب میکنیم کدامیک را اجرا کنیم.
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
اجرای تستهای منفرد
میتوانیم نام هر تابع تست را به 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 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
را اجرا کنید.