如何编写Rust单元测试以确保发生了死机?

8yoxcaq7  于 2022-11-12  发布在  其他
关注(0)|答案(8)|浏览(146)

我有一个Rust函数,panic在某种情况下会出错,我想写一个测试用例来验证这个函数是否会出错。除了assert!assert_eq!宏之外,我找不到任何东西。有什么机制可以测试这个吗?
我可以生成一个新任务,然后检查该任务是否会出现混乱。这样做有意义吗?
返回一个Result<T, E>不适合我的情况。
我希望在我实现的Matrix类型中添加对Add特征的支持。

let m = m1 + m2 + m3;

其中,m1m2m3都是矩阵。因此,add的结果类型应为Matrix。类似于以下内容的结果类型太过晦涩:

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

与此同时,add()函数需要验证两个相加的矩阵是否具有相同的维数。因此,如果维数不匹配,add()需要紧急处理。可用的选项是panic!()

lvjbypge

lvjbypge1#

你可以在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
}
yvfmudvl

yvfmudvl2#

正如弗朗西斯Gagné在他的回答中提到的,我还发现#[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())。

5anewei6

5anewei63#

如果你想Assert测试函数的特定部分失败,使用std::panic::catch_unwind()并检查它是否返回Err,例如is_err()。在复杂的测试函数中,这有助于确保测试不会因为早期的失败而错误地通过。
Rust标准库中的Severaltests本身使用此技术。

wnvonmuf

wnvonmuf4#

使用以下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
}
pdtvr36n

pdtvr36n5#

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

vsaztqbk6#

使用#[should_panic]属性的公认答案的主要问题是:

  • 不相关的恐慌可能会导致测试通过
  • 它不会禁止将紧急消息打印到控制台,从而导致测试执行日志不完整
  • 发生死机后无法添加其他检查

作为更好的替代方案,我强烈建议查看名为fluent-asserter的库
通过使用它,您可以轻松地编写一个Assert来检查是否发生了死机,如下所示:


# [test]

fn assert_that_code_panics() {
    let panicking_action = || panic!("some panic message");

    assert_that_code!(panicking_action)
        .panics()
        .with_message("some panic message");
}

这样做的好处是:

  • 它使用一个流畅的接口,产生一个可读的Assert
  • 它禁止向控制台打印紧急消息,从而产生干净的测试执行日志
  • 可以在死机检查后添加其他Assert
hjzp0vay

hjzp0vay7#

来自单元测试文档中的“测试恐慌”部分

pub fn divide_non_zero_result(a: u32, b: u32) -> u32 {
    if b == 0 {
        panic!("Divide-by-zero error");
    } else if a < b {
        panic!("Divide result is zero");
    }
    a / b
}

# [cfg(test)]

mod tests {
    use super::*;

    #[test]
    fn test_divide() {
        assert_eq!(divide_non_zero_result(10, 2), 5);
    }

    #[test]
    #[should_panic]
    fn test_any_panic() {
        divide_non_zero_result(1, 0);
    }

    #[test]
    #[should_panic(expected = "Divide result is zero")]
    fn test_specific_panic() {
        divide_non_zero_result(1, 10);
    }
}

运行cargo test时的输出为

$ cargo test

running 2 tests
test tests::test_bad_add ... FAILED
test tests::test_add ... ok

failures:

---- tests::test_bad_add stdout ----
        thread 'tests::test_bad_add' panicked at 'assertion failed: `(left == right)`
  left: `-1`,
 right: `3`', src/lib.rs:21:8
note: Run with `RUST_BACKTRACE=1` for a backtrace.

failures:
    tests::test_bad_add

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
wsewodh2

wsewodh28#

当使用 rust 板条箱test_case时,使用panics习语。

extern crate test_case;
use test_case::test_case;

# [test_case(0 => panics)]

# [test_case(1)]

fn test_divisor(divisor: usize) {
    let _result = 1 / divisor;
}

相关问题