如何对Serde(deserialize_with)中使用的反序列化功能进行单元测试?

pix*_*nil 5 unit-testing rust serde

我有一个在字段上实现Deserialize和使用的结构serde(deserialize_with)

#[derive(Debug, Deserialize)]
struct Record {
    name: String,
    #[serde(deserialize_with = "deserialize_numeric_bool")]
    is_active: bool,
}
Run Code Online (Sandbox Code Playgroud)

deserialize_numeric_bool字符串“ 0”或“ 1”反序列化为相应的布尔值的实现:

pub fn deserialize_numeric_bool<'de, D>(deserializer: D) -> Result<bool, D::Error>
    where D: Deserializer<'de>
{
    struct NumericBoolVisitor;

    impl<'de> Visitor<'de> for NumericBoolVisitor {
        type Value = bool;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("either 0 or 1")
        }

        fn visit_u64<E>(self, value: u64) -> Result<bool, E>
            where E: DeserializeError
        {
            match value {
                0 => Ok(false),
                1 => Ok(true),
                _ => Err(E::custom(format!("invalid bool: {}", value))),
            }
        }
    }

    deserializer.deserialize_u64(NumericBoolVisitor)
}
Run Code Online (Sandbox Code Playgroud)

(我感谢有关代码改进的评论)

我想为反序列化功能(如)编写单元测试deserialize_numeric_bool。当然,我友好的搜索框显示了serde_test板条箱和有关单元测试文档页面。但是这些资源对我而言无济于事,因为板条箱测试了直接实现的结构Deserialize

我的一个想法是创建一个仅包含反序列化函数的输出的新类型,并对其进行测试。但这对我来说似乎是不必要的间接方式。

#[derive(Deserialize)]
NumericBool {
    #[serde(deserialize_with = "deserialize_numeric_bool")]
    value: bool
};
Run Code Online (Sandbox Code Playgroud)

如何为此编写惯用测试?

pix*_*nil 5

我当前的解决方案仅使用 已提供的结构serde。\n在我的用例中,我只想测试给定的字符串是否成功反序列化为 bool 或存在特定错误。为基本数据类型提供serde::de::value简单的反序列化器,例如U64Deserializer保存u64. 它还具有一个Error 结构,为错误特征 \xe2\x80\x93 提供最小表示,可用于模拟错误。

\n\n

我的测试目前看起来是这样的:我用反序列化器模拟输入并将其传递给我的正在测试的函数。我喜欢的是我不需要在那里间接,并且我没有额外的依赖项。它不如assert_tokens*提供的那么好serde_test,因为它需要错误结构并且感觉不太精致。但对于我的情况,只有一个值被反序列化,它满足了我的需求。

\n\n
use serde::de::IntoDeserializer;\nuse serde::de::value::{U64Deserializer, StrDeserializer, Error as ValueError};\n\n#[test]\nfn test_numeric_true() {\n    let deserializer: U64Deserializer<ValueError> = 1u64.into_deserializer();\n    assert_eq!(numeric_bool(deserializer), Ok(true));\n}\n\n#[test]\nfn test_numeric_false() {\n    let deserializer: U64Deserializer<ValueError> = 0u64.into_deserializer();\n    assert_eq!(numeric_bool(deserializer), Ok(false));\n}\n\n#[test]\nfn test_numeric_invalid_number() {\n    let deserializer: U64Deserializer<ValueError> = 2u64.into_deserializer();\n    let error = numeric_bool(deserializer).unwrap_err();\n    assert_eq!(error.description(), "invalid bool: 2");\n}\n\n#[test]\nfn test_numeric_empty() {\n    let deserializer: StrDeserializer<ValueError> = "".into_deserializer();\n    let error = numeric_bool(deserializer).unwrap_err();\n    assert_eq!(error.description(), "invalid type: string \\"\\", expected either 0 or 1");\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我希望它也能帮助其他人或激励其他人找到更精美的版本。

\n


Jos*_*uth 1

我最近在尝试解决类似问题时多次遇到这个问题。对于未来的读者来说,pixunil 的答案很好,很直接,而且效果很好。但是,我想提供一个使用serde_test 的解决方案,如单元测试文档中提到的那样。

我研究了如何在几个箱子中使用serde_test ,这些箱子是通过其对lib.rs 的反向依赖关系找到的。正如您在原始帖子中提到的,其中一些定义了用于测试反序列化或序列化的小型结构或枚举。我想这样做是惯用的,否则测试会太冗长。

这里有几个例子;这是一个非详尽的列表:

不管怎样,假设我有一个从 a反序列化 a 的函数和另一个boolu8将 a 序列化为a 的函数boolu8

use serde::{
    de::{Error as DeError, Unexpected},
    Deserialize, Deserializer, Serialize, Serializer,
};

fn bool_from_int<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
    D: Deserializer<'de>,
{
    match u8::deserialize(deserializer)? {
        0 => Ok(false),
        1 => Ok(true),
        wrong => Err(DeError::invalid_value(
            Unexpected::Unsigned(wrong.into()),
            &"zero or one",
        )),
    }
}

#[inline]
fn bool_to_int<S>(a_bool: &bool, serializer: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    if *a_bool {
        serializer.serialize_u8(1)
    } else {
        serializer.serialize_u8(0)
    }
}
Run Code Online (Sandbox Code Playgroud)

struct我可以通过在我的模块中定义 a 来测试这些函数test。这允许将测试专门限制在这些函数上,而不是序列化/反序列化更大的对象。

#[cfg(test)]
mod tests {
    use super::{bool_from_int, bool_to_int};
    use serde::{Deserialize, Serialize};
    use serde_test::{assert_de_tokens_error, assert_tokens, Token};

    #[derive(Debug, PartialEq, Deserialize, Serialize)]
    #[serde(transparent)]
    struct BoolTest {
        #[serde(deserialize_with = "bool_from_int", serialize_with = "bool_to_int")]
        a_bool: bool,
    }

    const TEST_TRUE: BoolTest = BoolTest { a_bool: true };
    const TEST_FALSE: BoolTest = BoolTest { a_bool: false };

    #[test]
    fn test_true() {
        assert_tokens(&TEST_TRUE, &[Token::U8(1)])
    }

    #[test]
    fn test_false() {
        assert_tokens(&TEST_FALSE, &[Token::U8(0)])
    }

    #[test]
    fn test_de_error() {
        assert_de_tokens_error::<BoolTest>(
            &[Token::U8(14)],
            "invalid value: integer `14`, expected zero or one",
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

BoolTest位于按通常方式tests门控的模块内。#[cfg(test)]这意味着它BoolTest只是为了测试而编译,而不是增加膨胀。我不是 Rust 专家,但我认为如果程序员希望用作serde_test工具,这是一个很好的选择。