Тестування (Testing)
Як ми знаємо, тестування є невід’ємною частиною будь-якого програмного забезпечення! Rust має підтримку на рівні першого класу для модульного та інтеграційного тестування (див. цей розділ у TRPL).
Із розділів про тестування, наведених вище, ми бачимо, як писати модульні тести та інтеграційні тести. З організаційної точки зору, ми можемо розміщувати модульні тести в модулях, які вони тестують, а інтеграційні тести — у власному каталозі tests/:
foo
├── Cargo.toml
├── src
│ └── main.rs
│ └── lib.rs
└── tests
├── my_test.rs
└── my_other_test.rs
Кожен файл у tests є окремим інтеграційним тестом, тобто тестом, який призначений для тестування вашої бібліотеки так, ніби її викликають із залежного крейту.
Розділ Тестування докладніше описує три різні стилі тестування: модульне тестування, документаційне тестування та інтеграційне тестування.
cargo природно надає простий спосіб запускати всі ваші тести!
$ cargo test
Ви повинні побачити такий вивід:
$ cargo test
Compiling blah v0.1.0 (file:///nobackup/blah)
Finished dev [unoptimized + debuginfo] target(s) in 0.89 secs
Running target/debug/deps/blah-d3b32b97275ec472
running 4 tests
test test_bar ... ok
test test_baz ... ok
test test_foo_bar ... ok
test test_foo ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Ви також можете запускати тести, назва яких відповідає шаблону:
$ cargo test test_foo
$ cargo test test_foo
Compiling blah v0.1.0 (file:///nobackup/blah)
Finished dev [unoptimized + debuginfo] target(s) in 0.35 secs
Running target/debug/deps/blah-d3b32b97275ec472
running 2 tests
test test_foo ... ok
test test_foo_bar ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out
Одне застереження: Cargo може запускати кілька тестів одночасно, тож переконайтеся, що вони не створюють стан гонки даних один з одним.
Один приклад того, як ця конкурентність може спричиняти проблеми, — якщо два тести виводять у файл, як показано нижче:
#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
// Import the necessary modules
use std::fs::OpenOptions;
use std::io::Write;
// This test writes to a file
#[test]
fn test_file() {
// Opens the file ferris.txt or creates one if it doesn't exist.
let mut file = OpenOptions::new()
.append(true)
.create(true)
.open("ferris.txt")
.expect("Failed to open ferris.txt");
// Print "Ferris" 5 times.
for _ in 0..5 {
file.write_all("Ferris\n".as_bytes())
.expect("Could not write to ferris.txt");
}
}
// This test tries to write to the same file
#[test]
fn test_file_also() {
// Opens the file ferris.txt or creates one if it doesn't exist.
let mut file = OpenOptions::new()
.append(true)
.create(true)
.open("ferris.txt")
.expect("Failed to open ferris.txt");
// Print "Corro" 5 times.
for _ in 0..5 {
file.write_all("Corro\n".as_bytes())
.expect("Could not write to ferris.txt");
}
}
}
}
Хоча задум полягає в тому, щоб отримати таке:
$ cat ferris.txt
Ferris
Ferris
Ferris
Ferris
Ferris
Corro
Corro
Corro
Corro
Corro
Насправді в ferris.txt потрапляє ось це:
$ cargo test test_file && cat ferris.txt
Corro
Ferris
Corro
Ferris
Corro
Ferris
Corro
Ferris
Corro
Ferris