Основы тестирования в Rust
Rust имеет встроенную поддержку тестов через cargo test. Тесты — это функции с аннотацией #[test].
▸Простой тест
1// src/lib.rs2pub fn add(a: i32, b: i32) -> i32 {3 a + b4}56#[cfg(test)]7mod tests {8 use super::*;910 #[test]11 fn test_add() {12 assert_eq!(add(2, 3), 5);13 }1415 #[test]16 fn test_add_negative() {17 assert_eq!(add(-1, -1), -2);18 }19}
▸Запуск тестов
1# Все тесты2cargo test34# Конкретный тест5cargo test test_add67# С verbose выводом8cargo test -- --nocapture910# Тесты с паниками11cargo test -- --ignored
Макросы утверждений
▸assert!, assert_eq!, assert_ne!
1#[test]2fn test_assertions() {3 assert!(true); // Проверка истинности4 assert_eq!(2 + 2, 4); // Равенство5 assert_ne!(2 + 2, 5); // Неравенство6 assert!(add(2, 3) > 4); // Условие7}
▸Сообщения об ошибках
1#[test]2fn test_with_message() {3 let result = add(2, 3);4 assert_eq!(5 result, 5,6 "add(2, 3) should be 5, got {}", result7 );8}
Тестирование ошибок
1#[derive(Debug, PartialEq)]2enum AppError {3 NotFound,4 InvalidInput(String),5}67fn process(input: &str) -> Result<i32, AppError> {8 if input.is_empty() {9 return Err(AppError::InvalidInput("empty input".to_string()));10 }11 input.parse().map_err(|_| AppError::InvalidInput("not a number".to_string()))12}1314#[cfg(test)]15mod tests {16 use super::*;1718 #[test]19 fn test_process_valid() {20 assert_eq!(process("42").unwrap(), 42);21 }2223 #[test]24 fn test_process_empty() {25 assert_eq!(26 process(""),27 Err(AppError::InvalidInput("empty input".to_string()))28 );29 }3031 #[test]32 fn test_process_invalid() {33 match process("abc") {34 Err(AppError::InvalidInput(msg)) => {35 assert!(msg.contains("not a number"));36 }37 _ => panic!("Expected InvalidInput error"),38 }39 }40}
Интеграционные тесты
▸Структура проекта
1mylib/2├── src/3│ └── lib.rs4├── tests/5│ ├── integration_test.rs6│ └── common/7│ └── mod.rs
▸Интеграционный тест
1// tests/integration_test.rs2use mylib;34#[test]5fn test_integration() {6 let result = mylib::add(2, 3);7 assert_eq!(result, 5);8}910// tests/common/mod.rs — общие утилиты11pub fn setup() {12 // Настройка тестового окружения13}
Тестирование с параметрами
1#[test]2fn test_add_various() {3 let test_cases = vec![4 (2, 3, 5),5 (-1, -1, -2),6 (0, 0, 0),7 (100, 200, 300),8 ];910 for (a, b, expected) in test_cases {11 assert_eq!(add(a, b), expected, "Failed for {} + {}", a, b);12 }13}
Тестирование async кода
1use tokio;23#[tokio::test]4async fn test_async_function() {5 let result = async_function().await;6 assert_eq!(result, 42);7}89async fn async_function() -> i32 {10 tokio::time::sleep(Duration::from_millis(10)).await;11 4212}
Бенчмарки
1#![feature(test)]2extern crate test;34use test::Bencher;56#[bench]7fn bench_add(b: &mut Bencher) {8 b.iter(|| {9 add(2, 3)10 });11}
Тестирование с mock
1// Определение трейта2trait Database {3 fn get_user(&self, id: u32) -> Option<User>;4}56// Мок7struct MockDatabase {8 users: Vec<User>,9}1011impl Database for MockDatabase {12 fn get_user(&self, id: u32) -> Option<User> {13 self.users.iter().find(|u| u.id == id).cloned()14 }15}1617#[cfg(test)]18mod tests {19 use super::*;2021 #[test]22 fn test_with_mock() {23 let mock = MockDatabase {24 users: vec![User { id: 1, name: "Alice".to_string() }],25 };2627 let result = mock.get_user(1);28 assert!(result.is_some());29 assert_eq!(result.unwrap().name, "Alice");30 }31}
Тестирование с данными
1// tests/data/2// test_users.json3// test_orders.json45use std::fs;67#[test]8fn test_with_fixture() {9 let data = fs::read_to_string("tests/data/test_users.json")10 .expect("Failed to read test data");1112 let users: Vec<User> = serde_json::from_str(&data)13 .expect("Failed to parse test data");1415 assert!(!users.is_empty());16}
Best Practices
▸Именование тестов
1#[test]2fn test_add_returns_sum_of_two_numbers() {3 assert_eq!(add(2, 3), 5);4}56#[test]7fn test_add_handles_negative_numbers() {8 assert_eq!(add(-1, -1), -2);9}
▸Тестирование edge cases
1#[test]2fn test_add_edge_cases() {3 assert_eq!(add(i32::MAX, 1), i32::MIN + 1); // Overflow4 assert_eq!(add(i32::MIN, -1), i32::MAX); // Underflow5}
Заключение
Тестирование — обязательная часть разработки на Rust. Встроенные инструменты cargo test и макросы утверждений делают тестирование удобным. На собеседовании спрашивают про organization тестов, async тестирование и как тестировать ошибки.