Testlarni qanday yozish kerak
Testlar - bu sinovdan tashqari kod kutilgan tarzda ishlayotganligini tasdiqlovchi Rust funksiyalari. Test funksiyalari organlari odatda ushbu uchta harakatni bajaradi:
- Har qanday kerakli ma'lumotlarni yoki holatni o'rnating.
- Test qilmoqchi bo'lgan kodni ishga tushiring.
- Natijalar siz kutgan narsa ekanligini tasdiqlang.
Keling, Rust ushbu amallarni bajaradigan testlarni yozish uchun taqdim etgan xususiyatlarni ko'rib chiqaylik, ular orasida test
atributi, bir nechta makroslar va should_panic
atributi mavjud.
Test funksiyasining anatomiyasi
Eng sodda qilib aytganda, Rust-dagi test test
atributi bilan izohlangan funksiyadir. Atributlar Rust kodining bo'laklari haqidagi metama'lumotlardir; bir misol, biz 5-bobda structlar bilan ishlatgan derive
atributidir. Funksiyani test funksiyasiga oʻzgartirish uchun fn
oldidan qatorga #[test]
qoʻshing. cargo test
buyrug'i bilan testlarni o'tkazganingizda, Rust izohli funksiyalarni ishga tushiradigan test dasturining binaryrini yaratadi va har bir test funksiyasidan o'tgan yoki muvaffaqiyatsizligi haqida hisobot beradi.
Har safar biz Cargo bilan yangi kutubxona loyihasini yaratganimizda, biz uchun test funksiyasi bo'lgan test moduli avtomatik ravishda yaratiladi. Ushbu modul sizga testlarni yozish uchun shablonni taqdim etadi, shuning uchun har safar yangi loyihani boshlaganingizda aniq struktura va sintaksisni izlashga hojat qolmaydi. Siz xohlagancha qo'shimcha test funksiyalari va test modullarini qo'shishingiz mumkin!
Har qanday kodni sinab ko'rishdan oldin shablon testi bilan tajriba o'tkazish orqali testlar qanday ishlashining ba'zi jihatlarini o'rganamiz. Keyin biz yozgan ba'zi kodlarni chaqiradigan va uning xatti-harakati to'g'riligini tasdiqlaydigan haqiqiy dunyo testlarini yozamiz.
Keling, ikkita raqamni qo'shadigan qoshuvchi
nomli yangi kutubxona loyihasini yarataylik:
$ cargo new qoshuvchi --lib
Created library `qoshuvchi` project
$ cd qoshuvchi
qoshuvchi
kutubxonangizdagi src/lib.rs faylining mazmuni 11-1 roʻyxatdagi kabi koʻrinishi kerak.
Fayl nomi: src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn ishlaydi() {
let natija = 2 + 2;
assert_eq!(natija, 4);
}
}
Hozircha, keling, yuqoridagi ikkita qatorga e'tibor bermaylik va funksiyaga e'tibor qarataylik. #[test]
izohiga e'tibor bering: bu atribut bu test funksiyasi ekanligini bildiradi, shuning uchun test ishtirokchisi bu funksiyani test sifatida ko'rishni biladi. Umumiy stsenariylarni oʻrnatish yoki umumiy operatsiyalarni bajarishda yordam beradigan tests
modulida testdan tashqari funksiyalar ham boʻlishi mumkin, shuning uchun biz har doim qaysi funksiyalar test ekanligini koʻrsatishimiz kerak.
Misol funksiya tanasi 2 va 2 qo‘shilishi natijasini o‘z ichiga olgan natija
4 ga teng ekanligini tasdiqlash uchun assert_eq!
makrosidan foydalanadi. Ushbu tasdiq odatiy test formatiga misol bo'lib xizmat qiladi. Ushbu sinovdan o'tishini ko'rish uchun uni ishga tushiramiz.
cargo test
buyrug'i 11-2 ro'yxatda ko'rsatilganidek, loyihamizdagi barcha testlarni amalga oshiradi.
$ cargo test
Compiling qoshuvchi v0.1.0 (file:///projects/qoshuvchi)
Finished test [unoptimized + debuginfo] target(s) in 0.57s
Running unittests src/lib.rs (target/debug/deps/qoshuvchi-92948b65e88960b4)
running 1 test
test tests::ishlaydi ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests qoshuvchi
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Cargo kompilyatsiya qilindi va sinovdan o'tdi. Biz running 1 test
qatorini ko'ramiz. Keyingi qatorda ishlaydi
deb nomlangan yaratilgan test funksiyasining nomi va bu testni bajarish natijasi ok
ekanligini ko'rsatadi. Umumiy xulosa test natijasi test result: ok.
barcha testlardan muvaffaqiyatli oʻtganligini va 1 passed;
deb yozilgan qismi muvaffaqiyatli oʻtganligini bildiradi; 0 failed
muvaffaqiyatsiz boʻlgan testlar sonini ifodalaydi.
Muayyan misolda ishlamasligi uchun testni e'tiborsiz(ignor) deb belgilash mumkin; Biz buni ushbu bobning keyingi qismida "Agar aniq talab qilinmasa, ba'zi testlarni e'tiborsiz qoldirish" bo'limida ko'rib chiqamiz. Bu yerda biz buni qilmaganimiz sababli, xulosada 0 ignored
0-ta eʼtibor berilmagan koʻrsatiladi. Shuningdek, biz argumentni faqat nomi satrga mos keladigan testlarni o'tkazish uchun cargo test
buyrug'iga o'tkazishimiz mumkin; bu filtrlash deb ataladi va biz buni "Testlar to'plamini nomi bo'yicha ishga tushirish" bo'limida ko'rib chiqamiz. Shuningdek, biz bajarilayotgan testlarni filtrlamadik, shuning uchun xulosa oxirida 0 filtered out
0-ta filtrlangan deb ko‘rsatiladi.
0 measured
statistikasi samaradorlikni o'lchaydigan benchmark testlari uchundir.
Benchmark testlari, ushbu yozuvdan boshlab, faqat nightly Rust-da mavjud. Batafsil ma'lumot olish uchun benchmark testlari haqidagi hujjatlarga qarang.
Doc-tests adder
(Hujjat testlari qoʻshuvchisi) dan boshlanadigan test natijasining keyingi qismi har qanday hujjat sinovlari natijalariga moʻljallangan. Bizda hali hech qanday hujjat sinovlari yo'q, lekin Rust API hujjatlarida ko'rinadigan har qanday kod misollarini to'plashi mumkin.
Bu xususiyat hujjatlaringiz va kodingizni sinxronlashtirishga yordam beradi! Hujjat testlarini qanday yozishni 14-bobning “Hujjatlarga sharhlar test sifatida” bo‘limida muhokama qilamiz. Hozircha biz Doc-tests
chiqishini e'tiborsiz qoldiramiz.
Keling, testni o'z ehtiyojlarimizga moslashtirishni boshlaylik. Avval ishlaydi
funksiyasining nomini tadqiqot
kabi boshqa nomga o'zgartiring, masalan:
Fayl nomi: src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn tadqiqot() {
assert_eq!(2 + 2, 4);
}
}
Keyin yana cargo test
bajaring. Chiqish(output) endi ishlaydi
o‘rniga tadqiqot
ni ko‘rsatadi:
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.59s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 1 test
test tests::tadqiqot ... 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
Endi biz yana bir test qo'shamiz, lekin bu safar muvaffaqiyatsiz bo'lgan testni qilamiz! Test funktsiyasidagi biror narsa panic qo'zg'atganda, testlar muvaffaqiyatsiz tugaydi. Har bir test yangi threadda o'tkaziladi va asosiy(main) thread sinov chizig'i o'lganini ko'rsa, test muvaffaqiyatsiz deb belgilanadi. 9-bobda biz panic qo'zg'ashning eng oddiy yo'li panic!
makrosini chaqirish haqida gapirdik. Yangi testni boshqa
funksiya sifatida kiriting, shunda src/lib.rs faylingiz 11-3 roʻyxatiga oʻxshaydi.
Fayl nomi: src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn tadqiqot() {
assert_eq!(2 + 2, 4);
}
#[test]
fn boshqa() {
panic!("Ushbu test muvaffaqiyatsizlikka uchradil");
}
}
cargo test
yordamida testlarni qaytadan test qiling. Chiqish 11-4 ro'yxatga o'xshash bo'lishi kerak, bu bizning tadqiqot
sinovimizdan o'tganligini va boshqa
muvaffaqiyatsiz ekanligini ko'rsatadi.
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.72s
Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)
running 2 tests
test tests::boshqa ... FAILED
test tests::tadqiqot ... ok
failures:
---- tests::boshqa stdout ----
thread 'tests::boshqa' panicked at 'Make this test fail', src/lib.rs:10:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::boshqa
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
o'rniga test tests::boshqa
qatori FAILED
ni koʻrsatadi. Shaxsiy natijalar va xulosa o'rtasida ikkita yangi bo'lim paydo bo'ladi: birinchisida har bir sinov muvaffaqiyatsizligining batafsil sababi ko'rsatiladi. Bunday holda, biz src/lib.rs faylidagi 10-qatordagi panicked at 'Make this test fail'
da panic qo'ygani uchun boshqa
muvaffaqiyatsizlikka uchraganligi haqidagi tafsilotlarni olamiz. Keyingi bo'limda barcha muvaffaqiyatsiz testlarning nomlari keltirilgan, bu juda ko'p sinovlar va ko'plab batafsil muvaffaqiyatsiz sinov natijalari mavjud bo'lganda foydalidir. Muvaffaqiyatsiz test nomidan uni osonroq debug qilish uchun ishlatishimiz mumkin; testlarni o'tkazish usullari haqida ko'proq "Testlar qanday o'tkazilishini nazorat qilish" section bo'limida gaplashamiz.
Xulosa qatori oxirida ko'rsatiladi: umuman olganda, bizning test natijasimiz FAILED
muvaffaqiyatsiz. Bizda bitta test sinovi bor edi va bitta sinov muvaffaqiyatsiz tugadi.
Sinov natijalari turli stsenariylarda qanday ko‘rinishini ko‘rganingizdan so‘ng, keling, testlarda foydali bo‘lgan panic!
dan tashqari ba’zi makrolarni ko‘rib chiqaylik.
Natijalarni assert!
makrosi bilan tekshirish!
Standart kutubxona tomonidan taqdim etilgan assert!
makrosi testdagi baʼzi shartlar true
(toʻgʻri) boʻlishini taʼminlashni istasangiz foydali boʻladi. Biz assert!
makrosiga mantiqiy(boolean) qiymatga baholovchi argument beramiz. Qiymat true
bo'lsa, hech narsa sodir bo'lmaydi va sinovdan o'tadi. Agar qiymat false
bo‘lsa, assert!
makros testning muvaffaqiyatsiz bo‘lishiga olib kelishi uchun panic!
chaqiradi. assert!
makrosidan foydalanish bizning kodimiz biz rejalashtirgan tarzda ishlayotganligini tekshirishga yordam beradi.
5-bob, 5-15-ro'yxarda biz 11-5-ro'yxardada takrorlangan Kvadrat
strukturasi va ushlab_tur
metodidan foydalandik. Keling, ushbu kodni src/lib.rs fayliga joylashtiramiz, so'ngra assert!
makrosidan foydalanib, u uchun testlarni yozamiz.
Fayl nomi: src/lib.rs
#[derive(Debug)]
struct Kvadrat {
kenglik: u32,
balandlik: u32,
}
impl Kvadrat {
fn ushlab_tur(&self, boshqa: &Kvadrat) -> bool {
self.kenglik > other.kenglik && self.balandlik > boshqa.balandlik
}
}
ushlab_tur
metodi mantiqiy(boolean) qiymatini qaytaradi, ya'ni bu assert!
makrosi uchun mukammal foydalanish holati. 11-6 ro'yxatda biz kengligi 8 va balandligi 7 bo'lgan Kvadrat
misolini yaratish va uning kengligi 5 va balandligi 1 bo'lgan boshqa Kvadrat
misolini ushlab turishi mumkinligini tekshirish orqali ushlab_tur
metodini bajaradigan testni yozamiz.
Fayl nomi: src/lib.rs
#[derive(Debug)]
struct Kvadrat {
kenglik: u32,
balandlik: u32,
}
impl Kvadrat {
fn ushlab_tur(&self, boshqa: &Kvadrat) -> bool {
self.kenglik > boshqa.kenglik && self.balandlik > boshqa.balandlik
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn katta_kichikni_ushlab_turadi() {
let kattaroq = Kvadrat {
kenglik: 8,
balandlik: 7,
};
let kichikroq = Kvadrat {
kenglik: 5,
balandlik: 1,
};
assert!(kattaroq.ushlab_tur(&kichikroq));
}
}
E'tibor bering, biz tests
moduliga yangi qator qo'shdik: use super::*;
. tests
moduli odatiy modul bo'lib, biz 7-bobda "Modul daraxtidagi elementga murojaat qilish yo'llari" bo'limida ko'rib chiqqan odatiy ko'rinish qoidalariga amal qiladi. tests
moduli ichki modul bo'lgani uchun biz tashqi moduldagi sinovdan o'tayotgan kodni ichki modul doirasiga kiritishimiz kerak. Biz bu yerda globdan foydalanamiz, shuning uchun tashqi modulda biz aniqlagan har qanday narsa ushbu tests
modulida mavjud bo'ladi.
Biz sinovimizga katta_kichikni_ushlab_turadi
deb nom berdik va o‘zimizga kerak bo‘lgan ikkita Kvadrat
misolini yaratdik.
Keyin biz assert!
makrosini chaqirdik va uni kattaroq.ushlab_tur(&kichikroq)
deb chaqirish natijasini berdik. Bu ifoda true
ni qaytarishi kerak, shuning uchun testimiz muvaffaqiyatli o'tishi kerak. Keling, bilib olaylik!
$ cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished test [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
Test muvaffaqiyatli o'tadi! Keling, yana bir sinovni qo'shamiz, bu safar kichikroq kvadrat kattaroq kvadratni ushlab turolmaydi:
Fayl nomi: src/lib.rs
#[derive(Debug)]
struct Kvadrat {
kenglik: u32,
balandlik: u32,
}
impl Kvadrat {
fn ushlab_tur(&self, boshqa: &Kvadrat) -> bool {
self.kenglik > boshqa.kenglik && self.balandlik > boshqa.balandlik
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn katta_kichikni_ushlab_turadi() {
// --snip--
let kattaroq = Kvadrat {
kenglik: 8,
balandlik: 7,
};
let kichikroq = Kvadrat {
kenglik: 5,
balandlik: 1,
};
assert!(kattaroq.ushlab_tur(&kichikroq));
}
#[test]
fn kichik_kattani_ushlolmaydi() {
let kattaroq = Kvadrat {
kenglik: 8,
balandlik: 7,
};
let kichikroq = Kvadrat {
kenglik: 5,
balandlik: 1,
};
assert!(!kichikroq.ushlab_tur(&kattaroq));
}
}
Chunki bu holda ushlab_tur
funksiyasining to'g'ri natijasi false
bo'lsa, biz uni assert!
makrosiga o'tkazishdan oldin bu natijani inkor etishimiz kerak. Natijada, agar ushlab_tur
false
qiymatini qaytarsa, testimiz o'tadi:
$ cargo test
Compiling kvadrat v0.1.0 (file:///projects/kvadrat)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/kvadrat-6584c4561e48942e)
running 2 tests
test tests::katta_kichikni_ushlab_turadi ... ok
test tests::kichik_kattani_ushlolmaydi ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests kvadrat
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Ikkita sinovdan o'tadi! Keling, kodimizga xatolik kiritganimizda test natijalarimiz bilan nima sodir bo'lishini ko'rib chiqaylik. Kengliklarni solishtirganda katta belgisini kichikroq belgisi bilan almashtirish orqali ushlab_tur
metodini amalga oshirishni o‘zgartiramiz:
#[derive(Debug)]
struct Kvadrat {
kenglik: u32,
balandlik: u32,
}
// --snip--
impl Kvadrat {
fn ushlab_tur(&self, boshqa: &Kvadrat) -> bool {
self.kenglik < boshqa.kenglik && self.balandlik > boshqa.balandlik
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn katta_kichikni_ushlab_turadi() {
let kattaroq = Kvadrat {
kenglik: 8,
balandlik: 7,
};
let kichikroq = Kvadrat {
kenglik: 5,
balandlik: 1,
};
assert!(kattaroq.ushlab_tur(&kichikroq));
}
#[test]
fn kichik_kattani_ushlolmaydi() {
let kattaroq = Kvadrat {
kenglik: 8,
balandlik: 7,
};
let kichikroq = Kvadrat {
kenglik: 5,
balandlik: 1,
};
assert!(!kichikroq.ushlab_tur(&kattaroq));
}
}
Sinovlarni o'tkazish endi quyidagilarga olib keladi:
$ cargo test
Compiling rectangle v0.1.0 (file:///projects/rectangle)
Finished test [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 'assertion failed: larger.can_hold(&smaller)', src/lib.rs:28:9
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`
Sinovlarimiz xatoni aniqladi! kattaroq.kenglik
8 va kichikroq.kenglik
5 bo'lganligi sababli, ushlab_tur
da kengliklarni taqqoslash endi false
ni qaytaradi: 8 5-dan kichik emas.
Tenglikni assert_eq!
va assert_ne!
makroslari bilan tekshirish
Funksionallikni tekshirishning keng tarqalgan usuli - bu testdan o'tayotgan kod natijasi va kod qaytarilishini kutayotgan qiymat o'rtasidagi tenglikni tekshirish. Buni assert!
makrosidan foydalanib, unga ==
operatori yordamida ifoda o'tkazishingiz mumkin. Biroq, bu shunday keng tarqalgan testki, standart kutubxona ushbu testni yanada qulayroq bajarish uchun bir juft makros-assert_eq!
va assert_ne!
-ni taqdim etadi. Ushbu makrolar mos ravishda tenglik yoki tengsizlik uchun ikkita argumentni solishtiradi. Agar tasdiqlash muvaffaqiyatsiz bo'lsa, ular ikkita qiymatni chop etadilar, bu esa nima uchun sinov muvaffaqiyatsiz tugaganini ko'rishni osonlashtiradi; aksincha, assert!
makros false
qiymatiga olib kelgan qiymatlarni chop etmasdan, ==
ifodasi uchun false
qiymatini olganligini bildiradi.
11-7 ro'yxatda biz o'z parametriga 2
qo'shadigan ikkita_qoshish
nomli funksiyani yozamiz, so'ngra bu funksiyani assert_eq!
makrosidan foydalanib tekshiramiz.
Fayl nomi: src/lib.rs
pub fn ikkita_qoshish(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ikkita_qosh() {
assert_eq!(4, ikkita_qoshish(2));
}
}
Keling test o'tganligini tekshirib ko'raylik!
$ cargo test
Compiling qoshuvchi v0.1.0 (file:///projects/qoshuvchi)
Finished test [unoptimized + debuginfo] target(s) in 0.58s
Running unittests src/lib.rs (target/debug/deps/qoshuvchi-92948b65e88960b4)
running 1 test
test tests::ikkita_qosh ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests qoshuvchi
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Argument sifatida 4
ni assert_eq!
ga o'tkazamiz, bu esa ikkita_qoshish(2)
ni chaqirish natijasiga teng. Ushbu test qatori test tests::it_adds_two ... ok
va ok
matni testimiz muvaffaqiyatli o'tganligini bildiradi!
assert_eq!
muvaffaqiyatsiz bo'lganda qanday ko'rinishini ko'rish uchun kodimizga xato kiritamiz. ikkita_qoshish
funksiyasining bajarilishini o'rniga 3
qo'shish uchun o`zgartiramiz:
pub fn ikkita_qoshish(a: i32) -> i32 {
a + 3
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ikkita_qosh() {
assert_eq!(4, ikkita_qoshish(2));
}
}
Testlarni qayta ishga tushiring:
$ cargo test
Compiling qoshuvchi v0.1.0 (file:///projects/qoshuvchi)
Finished test [unoptimized + debuginfo] target(s) in 0.61s
Running unittests src/lib.rs (target/debug/deps/qoshuvchi-92948b65e88960b4)
running 1 test
test tests::ikkita_qosh ... FAILED
failures:
---- tests::ikkita_qosh stdout ----
thread 'tests::ikkita_qosh' panicked at 'assertion failed: `(left == right)`
left: `4`,
right: `5`', src/lib.rs:11:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::ikkita_qosh
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`
Bizning sinovimiz xatoni aniqladi! ikkita_qosh
testi muvaffaqiyatsiz tugadi va xabarda muvaffaqiyatsizlikka uchragan tasdiqlash assertion failed: `(left == right)`
va left
va right
qiymatlari nima. Bu xabar nosozliklarni(debugging) tuzatishni boshlashimizga yordam beradi: left
(chap) argumenti 4
edi, lekin ikkita_qoshish(2)
bo'lgan right
(o'ng) argumenti 5
edi. Tasavvur qilishingiz mumkinki, bu bizda juda ko'p sinovlar o'tkazilayotganda ayniqsa foydali bo'ladi.
E'tibor bering, ba'zi dasturlash tillarda va test tizimlarida(framework) tenglikni tasdiqlash funksiyalari parametrlari expected
va actual
deb nomlanadi va biz argumentlarni ko'rsatish tartibi muhim ahamiyatga ega. Biroq, Rustda ular left
va right
deb nomlanadi va biz kutgan qiymat va kod ishlab chiqaradigan qiymatni belgilash tartibi muhim emas. Biz ushbu testdagi tasdiqni assert_eq!(ikkita_qoshish(2), 4)
deb yozishimiz mumkin, natijada assertion failed: `(left == right)`
ko'rsatiladigan bir xil xato xabari paydo bo'ladi.
assert_ne!
makros biz bergan ikkita qiymat teng bo'lmasa o'tadi va teng bo'lsa muvaffaqiyatsiz bo'ladi. Ushbu makro biz qiymat nima bo'lishini amin bo'lmagan holatlar uchun juda foydali bo'ladi, lekin biz qiymat nima bo'lmasligi kerakligini bilamiz.
Misol uchun, agar biz biron-bir tarzda uning kiritilishini o'zgartirishi kafolatlangan funksiyani sinab ko'rayotgan bo'lsak, lekin kirishni o'zgartirish metodi testlarimizni o'tkazadigan hafta kuniga bog'liq bo'lsa, tasdiqlash uchun eng yaxshi narsa, funksiyaning chiqishi kirishga teng emasligi bo'lishi mumkin.
Sirt ostida assert_eq!
va assert_ne!
makroslari mos ravishda ==
va !=
operatorlaridan foydalanadi. Tasdiqlar bajarilmasa, bu makroslar debug formati yordamida o‘z argumentlarini chop etadi, ya’ni solishtirilayotgan qiymatlar PartialEq
va Debug
traitlarini bajarishi kerak. Barcha primitiv turlar va standart kutubxona turlarining aksariyati bu traittlarni amalga oshiradi. O'zingiz belgilagan structlar va enumlar uchun ushbu turlarning tengligini tasdiqlash uchun PartialEq
ni qo'llashingiz kerak bo'ladi. Tasdiqlash muvaffaqiyatsizlikka uchraganida qiymatlarni chop etish uchun Debug
ni ham qo'llashingiz kerak bo'ladi. 5-bobdagi 5-12 roʻyxatda aytib oʻtilganidek, ikkala trait ham derivable traitli boʻlganligi sababli, bu odatda struct yoki enum taʼrifiga #[derive(PartialEq, Debug)]
izohini qoʻshishdek oddiy. Ushbu va boshqa "Derivable Trait"lari haqida batafsil ma'lumot olish uchun C ilovasiga qarang.
Maxsus nosozlik xabarlarini qo'shish
Shuningdek, assert!
, assert_eq!
va assert_ne!
makroslariga ixtiyoriy argumentlar sifatida xato xabari bilan chop etiladigan maxsus xabarni qo'shishingiz mumkin. Kerakli argumentlardan so‘ng ko‘rsatilgan har qanday argumentlar format!
makrosiga uzatiladi (8-bobda "+
operatori yoki format!
makrosi bilan birlashtirish" bo‘limida muhokama qilingan), shuning uchun siz {}
to'ldirgichlar va qiymatlarni o'z ichiga olgan format qatorini o'tkazishingiz mumkin. Maxsus xabarlar tasdiqlash nimani anglatishini hujjatlashtirish uchun foydalidir; test muvaffaqiyatsiz tugagach, kod bilan bog'liq muammo nimada ekanligini yaxshiroq tushunasiz.
Masalan, bizda odamlarni ism bilan kutib oladigan funksiya bor va biz funksiyaga kiritgan ism chiqishda(output) paydo bo‘lishini sinab ko‘rmoqchimiz:
Fayl nomi: src/lib.rs
pub fn salomlashish(name: &str) -> String {
format!("Salom {}!", name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn salomlash() {
let natija = salomlashish("Azizbek");
assert!(natija.contains("Azizbek"));
}
}
Ushbu dasturga qoʻyiladigan talablar hali kelishib olinmagan va salomlashish boshidagi Salom
matni oʻzgarishiga ishonchimiz komil. Talablar o'zgarganda testni yangilashni xohlamasligimizga qaror qildik, shuning uchun salomlashish
funksiyasidan qaytarilgan qiymatga aniq tenglikni tekshirish o‘rniga, biz faqat chiqishda kirish parametrining matni borligini tasdiqlaymiz.
Endi standart sinov xatosi qanday koʻrinishini koʻrish uchun name
ni chiqarib tashlash uchun salomlashish
ni oʻzgartirish orqali ushbu kodga xatolik kiritamiz:
pub fn salomlashish(name: &str) -> String {
String::from("Salom!")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn salomlash() {
let natija = salomlashish("Azizbek");
assert!(natija.contains("Azizbek"));
}
}
Ushbu testni bajarish quyidagi natijalarni beradi:
$ cargo test
Compiling greeter v0.1.0 (file:///projects/greeter)
Finished test [unoptimized + debuginfo] target(s) in 0.91s
Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a)
running 1 test
test tests::salomlash ... FAILED
failures:
---- tests::salomlash stdout ----
thread 'tests::salomlash' panicked at 'assertion failed: result.contains(\"Azizbek\")', src/lib.rs:12:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::salomlash
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`
Bu natija faqat tasdiqlash(assertion) muvaffaqiyatsizligini va tasdiqlash qaysi qatorda ekanligini ko'rsatadi. Foydaliroq xato xabari salomlashish
funksiyasidan qiymatni chop etadi. Keling, salomlashish
funksiyasidan olingan haqiqiy qiymat bilan toʻldirilgan maxsus xabar to'ldiruvchisi(plaseholder) bilan format qatoridan iborat maxsus xato xabarini qoʻshamiz:
pub fn salomlashish(name: &str) -> String {
String::from("Salom!")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn salomlash() {
let natija = salomlashish("Azizbek");
assert!(
natija.contains("Azizbek"),
"Salomlashishda ism yo'q, qiymat `{}` edi",
natija
);
}
}
Endi sinovni o'tkazganimizda, biz ko'proq ma'lumot beruvchi xato xabarini olamiz:
$ cargo test
Compiling greeter v0.1.0 (file:///projects/greeter)
Finished test [unoptimized + debuginfo] target(s) in 0.93s
Running unittests src/lib.rs (target/debug/deps/greeter-170b942eb5bf5e3a)
running 1 test
test tests::salomlash ... FAILED
failures:
---- tests::salomlash stdout ----
thread 'tests::salomlash' panicked at 'Greeting did not contain name, value was `Salom!`', src/lib.rs:12:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::salomlash
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`
Sinov natijasida biz haqiqatda olgan qiymatni ko'rishimiz mumkin, bu biz kutgan narsaning o'rniga nima sodir bo'lganligini aniqlashga yordam beradi.
should_panic
yordamida panic tekshirish
Qaytish(return) qiymatlarini tekshirishdan tashqari, bizning kodimiz xato holatlarini biz kutganidek hal qilishini tekshirish muhimdir. Misol uchun, biz 9-bob, 9-13 ro'yxatda yaratgan Taxmin
turini ko'rib chiqaylik. Taxmin
dan foydalanadigan boshqa kod Taxmin
misollarida faqat 1 dan 100 gacha bo'lgan qiymatlarni o'z ichiga olishi kafolatiga bog'liq. Ushbu diapazondan(chegaradan) tashqaridagi qiymatga ega Taxmin
misolini yaratishga urinish panic qo'yishini ta'minlaydigan test yozishimiz mumkin.
Buni test funksiyamizga should_panic
atributini qo‘shish orqali qilamiz. Funktsiya ichidagi kod panic qo'zg'atsa, test o'tadi;funksiya ichidagi kod panic qo'ymasa, test muvaffaqiyatsiz tugaydi.
11-8 ro'yxatda Taxmin::new
xatolik holatlari biz kutgan vaqtda sodir bo'lishini tekshiradigan test ko'rsatilgan.
Fayl nomi: src/lib.rs
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);
}
}
Biz #[should_panic]
atributini #[test]
atributidan keyin va u amal qiladigan test funksiyasidan oldin joylashtiramiz. Keling, ushbu testdan o'tgan natijani ko'rib chiqaylik:
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished test [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
Yaxshi ko'rinadi! Endi shartni olib tashlash orqali kodimizga xatolik kiritamiz,
agar qiymat 100 dan katta bo'lsa, new
funksiya panic qo'zg'atadi:
pub struct Taxmin {
qiymat: i32,
}
// --snip--
impl Taxmin {
pub fn new(qiymat: i32) -> Taxmin {
if qiymat < 1 {
panic!("Taxmin qilingan qiymat 1 dan 100 gacha bo'lishi kerak, {} qabul qilinmaydi.", qiymat);
}
Taxmin { qiymat }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn _100_dan_ortiq() {
Taxmin::new(200);
}
}
Sinovni 11-8 ro'yxatda o'tkazganimizda, u muvaffaqiyatsiz bo'ladi:
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished test [unoptimized + debuginfo] target(s) in 0.62s
Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
running 1 test
test tests::_100_dan_ortiq - should panic ... FAILED
failures:
---- tests::_100_dan_ortiq stdout ----
note: test did not panic as expected
failures:
tests::_100_dan_ortiq
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`
Biz bu holatda unchalik foydali xabar olmaymiz, lekin test funksiyasini ko‘rib chiqsak, u #[should_panic]
bilan izohlanganini ko‘ramiz. Biz erishgan muvaffaqiyatsizlik test funksiyasidagi kod panic qo'zg'atmaganligini anglatadi.
should_panic
ishlatadigan testlar noaniq bo'lishi mumkin. Agar test biz kutgandan boshqa sababga ko'ra panic qo'zg'atsa ham, should_panic
testi o'tadi. should_panic
testlarini aniqroq qilish uchun biz should_panic
atributiga ixtiyoriy expected
parametrini qo'shishimiz mumkin. Test dasturi xato xabarida taqdim etilgan matn mavjudligiga ishonch hosil qiladi. Masalan, 11-9 ro'yxatdagi Taxmin
uchun o'zgartirilgan kodni ko'rib chiqing, bu erda new
funksiya qiymat juda kichik yoki juda kattaligiga qarab turli xabarlar bilan panicga tushadi.
Fayl nomi: src/lib.rs
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);
}
}
Bu testdan o‘tadi, chunki biz should_panic
atributining expected
parametriga qo‘ygan qiymat Taxmin::new
funksiyasi panicga tushadigan xabarning substringi hisoblanadi. Biz kutgan vahima haqidagi xabarni toʻliq koʻrsatishimiz mumkin edi, bu holda Taxmin qilingan qiymat 1 dan 100 gacha bo'lishi kerak, 200 qabul qilinmaydi.
. Siz belgilashni tanlagan narsa panic xabarining qanchalik noyob yoki dinamik ekanligiga va testingiz qanchalik aniq bo'lishini xohlayotganingizga bog'liq. Bunday holda, test funksiyasidagi kod else if qiymat > 100
holatini bajarishini ta`minlash uchun panic xabarining substringi kifoya qiladi.
expected
xabari bilan should_panic
testi muvaffaqiyatsiz tugashi bilan nima sodir bo'lishini ko'rish uchun if qiymat < 1
va else if qiymat > 100
bloklarini almashtirish orqali kodimizga yana xato kiritamiz:
pub struct Taxmin {
qiymat: i32,
}
impl Taxmin {
pub fn new(qiymat: i32) -> Taxmin {
if qiymat < 1 {
panic!(
"Taxmin qilingan qiymat 1 dan 100 gacha bo'lishi kerak, {} qabul qilinmaydi.",
qiymat
);
} else if qiymat > 100 {
panic!(
"Taxmin qilingan qiymat 1 dan katta yoki teng bo'lishi kerak, {} qabul qilinmaydi.",
qiymat
);
}
Taxmin { qiymat }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "100 dan kichik yoki teng")]
fn _100_dan_ortiq() {
Taxmin ::new(200);
}
}
Bu safar biz should_panic
testini o'tkazsak, u muvaffaqiyatsiz bo'ladi:
$ cargo test
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished test [unoptimized + debuginfo] target(s) in 0.66s
Running unittests src/lib.rs (target/debug/deps/guessing_game-57d70c3acb738f4d)
running 1 test
test tests::_100_dan_ortiq - should panic ... FAILED
failures:
---- tests::_100_dan_ortiq stdout ----
thread 'tests::_100_dan_ortiq' panicked at 'Taxmin qilingan qiymat 1 dan katta yoki teng bo'lishi kerak, 200 qabul qilinmaydi.', src/lib.rs:13:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: panic did not contain expected string
panic message: `"Taxmin qilingan qiymat 1 dan katta yoki teng bo'lishi kerak, 200 qabul qilinmaydi."`,
expected substring: `"100 dan kichik yoki teng"`
failures:
tests::_100_dan_ortiq
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`
Muvaffaqiyatsizlik xabari shuni ko'rsatadiki, bu test biz kutgandek panic qo'zg'atdi, lekin panic xabarida kutilgan Taxmin qilingan qiymat 100 dan kichik yoki unga teng bo'lishi kerak
qatori yo'q edi. Bu holatda biz olgan vahima xabari: Taxmin qilingan qiymat 1 dan katta yoki teng bo'lishi kerak, 200 qabul qilinmaydi.
. Endi biz xatomiz qayerda ekanligini aniqlashni boshlashimiz mumkin!
Testlarda Result<T, E>
dan foydalanish
Bizning testlarimiz muvaffaqiyatsiz bo'lganda panic qo'zg'atadi. Biz Result<T, E>
dan foydalanadigan testlarni ham yozishimiz mumkin! 11-1 roʻyxatidagi test Result<T, E>
dan foydalanish va panic oʻrniga Err
ni qaytarish uchun qayta yozilgan:
#[cfg(test)]
mod tests {
#[test]
fn ishlaydi() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("ikki qo'shish ikki to'rtga teng emas"))
}
}
}
ishlaydi
funksiyasi endi Result<(), String>
qaytish(return) turiga ega. Funksiya tanasida assert_eq!
makrosini chaqirishdan ko'ra, testdan o'tganda Ok(())
va test muvaffaqiyatsiz bo'lganda ichida String
bilan Err
ni qaytaramiz.
Testlarni Result<T, E>
qaytaradigan qilib yozish testlar matnida savol belgisi operatoridan foydalanish imkonini beradi, bu testlarni yozishning qulay usuli bo'lishi mumkin, agar ulardagi har qanday operatsiya Err
variantini qaytarsa, muvaffaqiyatsiz bo'lishi mumkin.
Result<T, E>
ishlatadigan testlarda #[should_panic]
izohidan(annotation) foydalana olmaysiz. Amaliyot Err
variantini qaytarishini tasdiqlash uchun Result<T, E>
qiymatida savol belgisi operatoridan foydalanmang. Buning oʻrniga assert!(value.is_err())
dan foydalaning.
Endi siz testlarni yozishning bir necha usullarini bilganingizdan so'ng, keling, testlarimizni o'tkazganimizda nima sodir bo'layotganini ko'rib chiqamiz va cargo test
bilan foydalanishimiz mumkin bo'lgan turli xil variantlarni ko'rib chiqamiz.