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

Sha*_*mar 58 unit-testing rust

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

我可以生成一个新任务并检查该任务是否会发生恐慌.是否有意义?


返回a Result<T, E>不适合我的情况.

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

let m = m1 + m2 + m3;
Run Code Online (Sandbox Code Playgroud)

其中m1,m2,m3都是矩阵.因此,结果类型add应该是Matrix.像下面这样的东西会太神秘:

let m = ((m1 + m2).unwrap() + m3).unwrap()
Run Code Online (Sandbox Code Playgroud)

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

Vla*_*eev 87

您可以在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
}
Run Code Online (Sandbox Code Playgroud)

  • 值得一提的是,您可以添加对恐慌文本的检查:`#[should_panic(expected ="assertion failed")]` (26认同)
  • 我会将“可以”强化为“*真的应该*”。代码可能会以多种方式出现恐慌。如果您没有指定预期的文本,测试可能会因错误的原因而最终通过。 (8认同)
  • 这里需要注意的是,根据您的 IDE 和环境,恐慌引起的堆栈跟踪可能仍会出现在输出中,但测试仍会通过。我花了一分钟才意识到我的“#[should_panic]”实际上正在工作。当您从命令行运行通用的“cargo test”时,您会注意到它会消除恐慌并仅显示为“ok”。 (4认同)

U00*_*07D 32

正如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
}
Run Code Online (Sandbox Code Playgroud)

请注意,它无法捕获非展开恐慌(例如std::process::abort()).


Fra*_*gné 14

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

Rust标准库本身的几个测试使用这种技术.

  • 应该有一个`assert_fails`或`assert_panics`宏吗? (2认同)
  • 你也可以使用 [`unwrap_err`](https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_err)。 (2认同)

m00*_*0am 5

作为附录:@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
    }
}
Run Code Online (Sandbox Code Playgroud)


k06*_*06a 5

使用以下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
}
Run Code Online (Sandbox Code Playgroud)