如何编写Rust单元测试以确保发生恐慌?

时间:2014-10-20 15:46:33

标签: unit-testing rust

我有一个在某些条件下panic的Rust函数,我希望编写一个测试用例来验证函数是否恐慌。除了assert!assert_eq!宏之外,我找不到任何其他内容。有没有一些机制来测试这个?

我可以产生一个新任务并检查该任务是否会引起恐慌。是否有意义?


在我的情况下,退回Result<T, E>并不合适。

我希望将Add特征的支持添加到我正在实施的Matrix类型中。这种添加的理想语法如下:

let m = m1 + m2 + m3;

其中m1m2m3都是矩阵。因此,add的结果类型应为Matrix。像下面这样的东西太神秘了:

let m = ((m1 + m2).unwrap() + m3).unwrap()

同时,add()函数需要验证添加的两个矩阵是否具有相同的维度。因此,add()如果尺寸不匹配则需要恐慌。可用选项为panic!()

5 个答案:

答案 0 :(得分:67)

您可以在Rust书的testing部分找到答案。更具体地说,您需要#[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
}

答案 1 :(得分:23)

正如FrancisGagné在他的回答中所提到的,我也发现#[should_panic]属性对于更复杂的测试来说不够精细 - 例如,如果我的测试设置由于某种原因失败(即我已经写了一个糟糕的测试),我希望恐慌被视为失败!

从Rust 1.9.0开始,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())。

答案 2 :(得分:12)

如果您想声明只有测试函数的特定部分失败,请使用std::panic::catch_unwind()并检查它是否返回Err,例如is_err()。在复杂的测试功能中,这有助于确保测试不会因为早期故障而错误地通过。

Rust标准库本身中的

Several tests使用了这种技术。

答案 3 :(得分:3)

作为附录:@ 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
    }
}

答案 4 :(得分:0)

使用以下catch_unwind_silent代替常规的catch_unwind,以实现预期异常的输出静音:

use std::panic;

fn catch_unwind_silent<F: FnOnce() -> R + panic::UnwindSafe, R>(f: F) -> std::thread::Result<R> {
    let prev_hook = panic::take_hook();
    panic::set_hook(Box::new(|_| {}));
    let result = panic::catch_unwind(f);
    panic::set_hook(prev_hook);
    result
}