Как написать юнит-тест в Rust, проверяющий, что функция паникует?
Основной способ: атрибут #[should_panic]
Самый простой и распространённый способ проверить, что функция вызывает панику — использовать атрибут #[should_panic]. Он указывает тестовому фреймворку, что тест считается успешным только в том случае, если внутри него произошла паника.
Вот как это выглядит для вашего примера:
#[test]
#[should_panic]
fn test_divide_by_zero() {
divide(10, 0);
}
Если функция divide вызовет панику — тест пройдёт. Если нет — тест упадёт с сообщением о том, что ожидалась паника, но её не произошло.
Проверка сообщения паники
Часто бывает полезно убедиться не просто в факте паники, но и в том, что сообщение паники содержит определённый текст. Для этого используется #[should_panic(expected = "...")]:
#[test]
#[should_panic(expected = "Division by zero!")]
fn test_divide_by_zero_with_message() {
divide(10, 0);
}
Теперь тест пройдёт только если паника произошла и её сообщение содержит подстроку "Division by zero!". Это помогает избежать ложных срабатываний, когда паника происходит по другой причине.
Альтернатива: std::panic::catch_unwind
Иногда нужно сделать более сложную проверку — например, проверить несколько сценариев в одном тесте или выполнить дополнительную логику после паники. Для этого можно использовать catch_unwind:
use std::panic;
#[test]
fn test_divide_by_zero_catch() {
let result = panic::catch_unwind(|| {
divide(10, 0);
});
assert!(result.is_err()); // Проверяем, что паника произошла
}
Этот метод возвращает Result: Ok(()) если паники не было, и Err(Box<dyn Any>) если была. Вы даже можете извлечь сообщение паники:
#[test]
fn test_divide_by_zero_catch_with_message() {
let result = panic::catch_unwind(|| {
divide(10, 0);
});
let err = result.unwrap_err();
let msg = err.downcast_ref::<String>().unwrap();
assert_eq!(msg, "Division by zero!");
}
Best practices
- Используйте
#[should_panic]для простых случаев — это самый лаконичный и читаемый способ. - Указывайте
expected— это делает тесты более точными и документирует ожидаемое поведение. - Не злоупотребляйте паникой — в Rust паника обычно означает неисправимую ошибку. Для ожидаемых ошибок лучше использовать
Result<T, E>. - Используйте
catch_unwindтолько когда нужно — например, для тестирования сложной логики или при работе с асинхронным кодом.
Надеюсь, это поможет! Удачи с изучением Rust!
<<Дополнительный подход: тестирование через Result
Иногда вместо паники лучше использовать возврат Result. Это делает код более идиоматичным для Rust и упрощает тестирование. Например, можно переписать вашу функцию так:
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
return Err("Division by zero!".to_string());
}
Ok(a / b)
}
#[test]
fn test_divide_by_zero_result() {
let result = divide(10, 0);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Division by zero!");
}
Этот подход более гибкий: вы можете обрабатывать ошибки без паники, а тесты становятся чище и не требуют специальных атрибутов. Однако если вы работаете с чужой библиотекой, которая паникует, то #[should_panic] или catch_unwind остаются лучшим выбором.
Ещё один совет: в тестах можно комбинировать оба подхода. Например, сначала протестировать нормальное поведение, а потом — панику в отдельном тесте. Это улучшает читаемость и поддержку кода.
Ответов пока нет.
Войдите чтобы оставить ответ