【问题标题】:How to assert io errors in Rust?如何在 Rust 中断言 io 错误?
【发布时间】:2019-12-05 15:08:42
【问题描述】:

有很多教程展示了如何在 Rust 中编写单元测试。我已经阅读了十几本,所有这些都专注于在成功的情况下断言价值。在error 的情况下,情况似乎并不那么简单。默认情况下,错误不会实现 PartialEq 特征,因此您不能使用 assert_eq! 宏。此外,某些函数可能会根据发生的问题类型返回多种错误变体(例如io::Error,可能是不同类型的。)我可以检查是否发生错误,但这似乎还不够。

以下示例。

fn parse_data(input: i32) -> Result<i32, io::Error> {
    match input {
        0 => Ok(0),
        _ => Err(io::Error::new(io::ErrorKind::InvalidData, "unexpected number"))
    }
}

#[test]
fn test_parsing_wrong_data() {
    let result = parse_data(1);
    assert!(result.is_err());
    let got = result.unwrap_err();
    let want = io::Error::new(io::ErrorKind::InvalidData, "unexpected number");

    // compilation error here: binary operation `==` cannot be applied to type `std::io::Error`
    assert_eq!(want, got);
}

我认为这不是惯用的方法,因为它没有编译。因此,问题是 - 在类似情况下,什么是适当且惯用的方法?

【问题讨论】:

    标签: unit-testing error-handling rust


    【解决方案1】:

    TL;DR:Error 应该实现 PartialEq

    Result&lt;T, E&gt; 仅在 TE 也实现 PartialEq 时实现 PartialEq,但 io::Error 不实现。 alex 确认导致io::Error 出现实现dyn Error 的额外错误,允许用户添加额外信息导致std 不实现PartialEq


    我看到很多答案和评论,似乎很多人使用io::Error 来创建自己的错误。这不是一个好的做法,io::Error 仅应在您与io 打交道时使用。如果您想了解并分享您对 Rust 错误的看法,请点击 Error Handling Project Group

    目前,Rust 中有一些常见的 crate 可以让您自己出错(请随意添加 crate):

    我不完全同意,但这里有一个很好的 guide 关于 Rust 中的错误。


    无论如何,您可能想要的解决方案只是比较 ErrorKind 值。由于ErrorKind 实现PartialEq,这将与assert_eq() 一起编译

    use std::io;
    
    fn parse_data(input: i32) -> Result<i32, io::Error> {
        match input {
            0 => Ok(0),
            x => Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("unexpected number {}", x),
            )),
        }
    }
    
    #[test]
    fn test_parsing_wrong_data() {
        let result = parse_data(1).map_err(|e| e.kind());
        let expected = Err(io::ErrorKind::InvalidData);
        assert_eq!(expected, result);
    }
    

    【讨论】:

    • 我有一个返回Result&lt;String, ParseError&gt;的函数,我将#[derive(PartialEq)]添加到ParseError,它成功了!
    • 我发现这篇文章非常有帮助,尽管我有一点点需要在断言中交换预期和结果 [即assert_eq!(结果,预期);]。我相信这在不同的编程语言之间有所不同!
    • @BenSmith Rust 特别将意义赋予assert_eq! 的第一个或第二个参数。这就是为什么失败文本将它们称为leftright
    • 触摸!经过仔细检查,当通过“播放按钮”运行时,IntelliJ IDE 在失败的测试中返回了预期和实际。确实,在执行标准(预期)货物运行时会看到左右。
    • 如果您的错误类型没有.kind(),即。如果使用thiserror crate,请使用@scoopr 的答案。
    【解决方案2】:

    在我的例子中,我有几个具有不同字段的结构,它们都实现了 std::error::Error 特征。与创建我需要的任何结构的实例相比,这对我来说更有效:

    let result = parse_data(1);
    let your_error = result.unwrap_err().downcast_ref::<MyCustomError>();
    assert!(your_error.is_some());
    

    这会将错误向下转换为特定类型,如果它是正确的类型,则为Some(_) 值,否则为None。但是,我是 Rust 的新手,这可能不是解决您问题的最佳方法。

    【讨论】:

    • 使用 io::Error,自己犯错误可能是个坏主意,找 snafu crate 之类的东西
    • 嗨!为什么这是个坏主意?
    • io::Error,从来没有被设计成这样使用,它应该只用于最大的 io 错误。错误看起来也很沮丧……不好。如果错误是不透明的类型,这意味着错误应该被用作不透明的,所以不要向下转换,如果你的错误必须对用户可用,那么它不应该是不透明的类型,所以不是 dyn 错误,而是错误可能性的枚举。我不完全同意lpalmieri.com/posts/error-handling-rust,但它主要是一个很好的指南。
    • 但我使用std::error::Error。会不会有同样的问题?
    • 好吧,正如我所说,如果您将所有错误都包装到不透明类型 std::error::Error 中,则您不需要做您在回答中所做的事情。导致您选择使错误不透明。我建议您阅读我链接的指南,然后选择 snafu 或 thiserror crate。如果你想让你的错误不透明,你可能会对 crate 感兴趣。
    【解决方案3】:

    如果你使用类型Result, 有一个内置方法is_err()

    这意味着您可以使用assert!(my_result.is_err()); 进行测试。

    【讨论】:

    • 问题已经包含这个建议assert!(result.is_err());问题是关于如何检查返回的预期错误
    【解决方案4】:

    我用这个解决了

    assert!(matches!(result, Err(crate::Error::InvalidType(t)) if t == "foobar"));
    
    

    此解决方案不需要 PartialEq 来代替 Error,但仍然允许我与变体内容进行比较。

    如果你不关心变体的内容,那么它只是

    assert!(matches!(result, Err(crate::Error::InvalidType(_))));
    
    

    【讨论】:

    • 这比只检查类型的答案更强大,因为它还包含内容。但是,这样做被认为是 rust 中的反模式,因为内容(如错误字符串)可以更改,并且不允许编译时验证。
    • 好吧,就我而言,我实际上想测试内容(错误来自我预期的标识符),我根本不认为这是反模式,我不是与Display 字符串进行比较。但是如果您不关心值,模式匹配当然可以省略比较。我已经用一个例子修改了答案。
    • 有什么理由不写assert!(result, Err(crate::Error::InvalidType("foobar"))); 吗?我不明白您的回答,请提供minimal reproducible example
    • 如果你的意思是assert_eq,那么这需要PartialEq Impl,我的枚举并不容易拥有它,正如我所指出的,这个解决方案不需要它。这很优雅,因为无论如何在测试之外都不需要它。
    【解决方案5】:

    使用Genaritos answer 的宏。

    macro_rules! is_error_type {
        ($result:ident,$err_type:ident) => {
            $result.unwrap_err().downcast_ref::<$err_type>().is_some()
        }
    }
    

    这样使用:

    let result = parse_data(1);
    assert!(is_error_type(result, MyCustomError));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-01-07
      • 2019-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多