在 Rust 中测试恐慌:如何为异常情况编写测试

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

恐慌并非末日:为什么要在 Rust 中测试异常情况?

\n\n

想象一下:您正在用 Rust 编写一个处理金钱的库。函数 divide_money(amount: u64, parts: u64) 应该在 parts 为零时引发恐慌——因为不能除以零,而由于架构原因您不想返回 Option 或 Result。如何确保这种恐慌确实会发生?或者您正在开发一个解析器,在传入无效数据时应该异常终止。如果没有针对恐慌的测试,您就有可能遇到只有在生产环境中才会显现的“静默”错误。

\n\n

在 Rust 中,测试恐慌不仅仅是一个“可选功能”,而是创建可靠代码的重要工具。在本文中,我们将探讨如何编写单元测试来验证您的代码是否在需要的情况下确实会引发恐慌。您将了解 #[should_panic] 宏、其细微差别、像 std::panic::catch_unwind 这样的替代方案以及最佳实践。开始吧!

\n\n

经典方法:#[should_panic] 宏

\n\n

在 Rust 中检查恐慌最简单、最常用的方法是使用 #[should_panic] 属性。它放在测试函数上方,并告诉编译器:“仅当内部发生恐慌时,此测试才算成功。”

\n\n

基本示例

\n\n
// lib.rs 或 tests/unit_tests.rs\nfn divide(a: i32, b: i32) -> i32 {\n    if b == 0 {\n        panic!("除以零!");\n    }\n    a / b\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    #[should_panic]\n    fn test_divide_by_zero() {\n        divide(10, 0);\n    }\n}
\n\n

使用命令 cargo test 运行测试——您将看到绿色结果。如果没有发生恐慌(例如,您删除了对零的检查),测试将失败并显示消息:"test test_divide_by_zero ... FAILED (expected to panic)"。

\n\n

检查确切的恐慌消息

\n\n

有时您不仅需要确认恐慌的发生,还需要确认其具体消息。这有助于避免因其他错误导致的恐慌而误报。使用附加参数 expected:

\n\n
#[test]\n#[should_panic(expected = "除以零!")]\nfn test_divide_by_zero_with_message() {\n    divide(10, 0);\n}\n\n// 此测试不会通过——消息不同\n#[test]\n#[should_panic(expected = "除以零!")]\nfn test_other_panic() {\n    panic!("完全不同的错误");\n}
\n\n

重要提示:expected 检查恐慌消息是否包含指定的字符串(而不是完全相等)。这对于长消息很方便。

\n\n

高级方法:std::panic::catch_unwind

\n\n

有时 #[should_panic] 不够用。例如,如果您想验证恐慌是否发生在特定位置而不是其他地方,或者需要测试异步代码。对于这种情况,有 std::panic::catch_unwind——一个捕获恐慌并将其作为 Result 返回的函数。

\n\n

工作原理

\n\n
use std::panic;\n\nfn risky_function(should_panic: bool) {\n    if should_panic {\n        panic!("事故!");\n    }\n}\n\n#[test]\nfn test_catch_panic() {\n    let result = panic::catch_unwind(|| {\n        risky_function(true);\n    });\n    assert!(result.is_err()); // 检查是否发生了恐慌\n}\n\n#[test]\nfn test_no_panic() {\n    let result = panic::catch_unwind(|| {\n        risky_function(false);\n    });\n    assert!(result.is_ok()); // 没有恐慌\n}
\n\n

检查恐慌类型

\n\n

使用 catch_unwind,您甚至可以检查恐慌是否包含特定值(不仅仅是字符串):

\n\n
use std::panic;\nuse std::any::Any;\n\nfn panic_with_number() {\n    panic!(42); // 是的,可以这样——用数字引发恐慌!\n}\n\n#[test]\nfn test_panic_with_number() {\n    let resu                

博客

书籍推荐