【问题标题】:How do I write a Rust unit test that ensures that a panic has occurred?如何编写确保发生恐慌的 Rust 单元测试?
【发布时间】:2014-10-20 15:46:32
【问题描述】:

我有一个 panics 在某些情况下的 Rust 函数,我希望编写一个测试用例来验证该函数是否恐慌。除了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!()

【问题讨论】:

    标签: unit-testing rust


    【解决方案1】:

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

    【讨论】:

    • 值得一提的是,您可以为恐慌的文本添加检查:#[should_panic(expected = "assertion failed")]
    • 这里快速说明一下,根据您的 IDE 和环境,panic 的堆栈跟踪可能仍会出现在输出中,但测试仍将通过。我花了一分钟才意识到我的#[should_panic] 确实在工作。当你从命令行运行一个通用的cargo test 时,你会注意到它吞下了恐慌,并显示为ok
    • 我会将“可以”强化为“真的应该”。代码可能会以多种方式出现恐慌。如果您没有指定预期的文本,测试可能会因为错误的原因而通过。
    【解决方案2】:

    正如 Francis 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())。

    【讨论】:

      【解决方案3】:

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

      Rust 标准库中的Several tests 本身使用了这种技术。

      【讨论】:

      • 应该有一个assert_failsassert_panics 宏吗?
      • 你也可以使用unwrap_err
      • 有没有办法在#[no_std] 环境中做到这一点?我正在寻找一个通用的assert_panics! 宏。
      • @jhpratt 到目前为止,我为#[no_std] 找到的最好的方法是在应该恐慌之后使用#[should_panic(expected = "")] 和unreachable!()测试条件。测试为绿色,预期恐慌,否则为红色。
      【解决方案4】:

      使用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
      }
      

      【讨论】:

        【解决方案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
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-12-05
          • 2015-10-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多