Как написать Rust unit test, который гарантирует, что произошла паника?
У меня есть функция Rust, которая panic
при некоторых условиях, и я хочу написать тестовый пример, чтобы проверить, паникует ли функция или нет. Я не мог найти ничего, кроме макросов assert!
и assert_eq!
. Есть ли какой-нибудь механизм для тестирования этого?
Я мог бы создать новую задачу и проверить, паникует ли эта задача или нет. Имеет ли это смысл?
Возврат Result<T, E>
не подходит в моем случае.
Я хочу добавить поддержку признака Add
к типу Matrix
, который я реализую. Идеальный синтаксис для такого добавления будет выглядеть так:
let m = m1 + m2 + m3;
где m1
, m2
, m3
- все матрицы. Следовательно, тип результата Add
должен быть Matrix
. Что-то вроде следующего было бы слишком загадочным:
let m = ((m1 + m2).unwrap() + m3).unwrap()
В то же время функция add()
должна проверять, что две добавляемые матрицы имеют одинаковую размерность. Таким образом, add()
должен паниковать, если размеры не совпадают. Доступная опция panic!()
.
Ответы
Ответ 1
Вы можете найти ответ в разделе testing в книге Rust. Более конкретно, вам нужен атрибут #[should_panic]
:
#[test]
#[should_panic]
fn test_invalid_matrices_multiplication() {
let m1 = Matrix::new(3, 4); // assume these are dimensions
let m2 = Matrix::new(5, 6);
m1 * m2
}
Ответ 2
Как упомянул Фрэнсис Гань в своем ответе, я также нахожу, что атрибут #[should_panic]
недостаточно детализирован для более сложных тестов - например, если по какой-то причине моя установка теста не удалась (т.е. я написал плохой тест Я хочу, чтобы паника считалась неудачей!
Начиная с Rust std::panic::catch_unwind()
, доступна std::panic::catch_unwind()
. Это позволяет вам поместить код, который вы ожидаете, чтобы запаниковать, в замыкание, и только паника, испускаемая этим кодом, будет считаться ожидаемой (то есть проходящим тестом).
#[test]
fn test_something() {
... //<-- Any panics here will cause test failure (good)
let result = std::panic::catch_unwind(|| <expected_to_panic_operation_here>);
assert!(result.is_err()); //probe further for specific error type here, if desired
}
Обратите внимание, что он не может поймать не раскручивающиеся паники (например, std::process::abort()
).
Ответ 3
Если вы хотите утверждать, что только определенная часть тестовой функции дает сбой, используйте std::panic::catch_unwind()
и проверьте, что он возвращает Err
, например, с помощью is_err()
. В сложных тестовых функциях это помогает гарантировать, что тест не пройдет ошибочно из-за раннего сбоя.
Несколько тестов в самой стандартной библиотеке Rust используют эту технику.
Ответ 4
В качестве дополнения: решение, предложенное @U007D, также работает в doctests:
/// My identity function that panic for an input of 42.
///
/// '''
/// assert_eq!(my_crate::my_func(23), 23);
///
/// let result = std::panic::catch_unwind(|| my_crate::my_func(42));
/// assert!(result.is_err());
/// '''
pub fn my_func(input: u32) -> u32 {
if input == 42 {
panic!("Error message.");
} else {
input
}
}