Ce face teste (#[test])
Rust are testele integrate în limbaj. Orice funcție fără parametri marcată cu #[test] este un test: trece dacă se termină normal și eșuează dacă dă panică (de exemplu un assert_eq! neîndeplinit). cargo test compilează proiectul în mod test și rulează toate testele în paralel.
Prin convenție, testele unitare stau în același fișier cu codul, într-un modul #[cfg(test)] mod tests. #[cfg(test)] face ca modulul să fie compilat doar la cargo test, nu și în programul final, iar use super::*; aduce în test funcțiile din fișier (inclusiv cele private). Testele de integrare, care folosesc biblioteca din exterior, se pun în directorul tests/.
#[should_panic] marchează un test care trebuie să dea panică (opțional cu expected = "fragment din mesaj"). #[ignore] sare peste un test lent, care se rulează doar cu cargo test -- --ignored. Un test poate returna și Result<(), E>, ca să folosească ?.
Exemple
Exemplul 1: Teste unitare care trec
Fișierul src/lib.rs al bibliotecii note conține două funcții și testele lor. Ambele teste trec.
// src/lib.rs
pub fn media(note: &[u8]) -> f64 {
let suma: u32 = note.iter().map(|&n| n as u32).sum();
suma as f64 / note.len() as f64
}
pub fn calificativ(media: f64) -> &'static str {
if media >= 9.0 {
"FB"
} else if media >= 7.0 {
"B"
} else if media >= 5.0 {
"S"
} else {
"I"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn media_corecta() {
assert_eq!(media(&[10, 9, 8]), 9.0);
}
#[test]
fn calificative() {
assert_eq!(calificativ(9.5), "FB");
assert_eq!(calificativ(7.0), "B");
assert_eq!(calificativ(4.99), "I");
}
}
$ cargo test
Rezultat în terminal
Compiling note v0.1.0 (/home/ana/proiecte/note)
Finished `test` profile [unoptimized + debuginfo] target(s) in 1.66s
Running unittests src/lib.rs (target/debug/deps/note-acd73a4da84441e9)
running 2 tests
test tests::calificative ... ok
test tests::media_corecta ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests note
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Exemplul 2: should_panic, ignore și un test care eșuează
nota_prea_mare trece pentru că funcția dă panica așteptată. toate_notele este sărit. nota_zece are o greșeală în valoarea așteptată, deci eșuează: raportul arată ambele valori (left = cea calculată, right = cea așteptată) și mesajul nostru. Numărul dintre paranteze după numele firului diferă la fiecare rulare.
// src/lib.rs
pub fn nota_din_text(text: &str) -> u8 {
let n: u8 = text.trim().parse().expect("nota trebuie să fie un număr");
assert!((1..=10).contains(&n), "nota {n} nu este între 1 și 10");
n
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spatii_ignorate() {
assert_eq!(nota_din_text(" 9\n"), 9);
}
#[test]
#[should_panic(expected = "nu este între 1 și 10")]
fn nota_prea_mare() {
nota_din_text("11");
}
#[test]
fn nota_zece() {
assert_eq!(nota_din_text("10"), 1, "zece trebuie citit ca 10");
}
#[test]
#[ignore = "durează mult"]
fn toate_notele() {
for n in 1..=10 {
assert_eq!(nota_din_text(&n.to_string()), n);
}
}
}
$ cargo test
Rezultat în terminal
Compiling note v0.1.0 (/home/ana/proiecte/note)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.44s
Running unittests src/lib.rs (target/debug/deps/note-acd73a4da84441e9)
running 4 tests
test tests::toate_notele ... ignored, durează mult
test tests::spatii_ignorate ... ok
test tests::nota_zece ... FAILED
test tests::nota_prea_mare - should panic ... ok
failures:
---- tests::nota_zece stdout ----
thread 'tests::nota_zece' (14557629) panicked at src/lib.rs:24:9:
assertion `left == right` failed: zece trebuie citit ca 10
left: 10
right: 1
failures:
tests::nota_zece
test result: FAILED. 2 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
Greșeli frecvente
- Pui testele într-un modul fără
#[cfg(test)]: ele sunt compilate și în programul final, iar importurile folosite doar de teste produc avertismenteunused import. - Uiți
use super::*;înmod tests: funcțiile testate nu sunt găsite (cannot find function), pentru că modulul de teste este un modul separat. - Te bazezi pe ordinea testelor sau pe un fișier comun. Testele rulează în paralel și în ordine nedefinită; fiecare test trebuie să fie independent (de exemplu cu nume de fișiere diferite).
- Ascunzi un test care eșuează cu
#[ignore]și uiți de el. Dă mereu un motiv:#[ignore = "așteaptă rezolvarea bug-ului X"].
Comenzi înrudite
cargo test: Compilează proiectul în mod test și rulează toate testele: cele #[test], testele din tests/ și exemplele din documentație.assert_eq! și assert_ne!: Verifică dacă două valori sunt egale (assert_eq!) sau diferite (assert_ne!); la eșec, panica afișează ambele valori.assert! și debug_assert!: Verifică o condiție la rulare și dă panică dacă este falsă. debug_assert! face verificarea doar în modul debug, nu și în release.atribute (#[derive], #[cfg], #[allow]): Informații pentru compilator scrise cu #[…] deasupra unui element: generează cod (derive), aleg codul compilat (cfg) sau schimbă avertismentele.module și vizibilitate (mod, pub, crate, super): mod împarte codul în module; tot ce este într-un modul este privat implicit, iar pub îl face vizibil. crate și super încep căile.