【问题标题】:Is there any point Unit testing serialization?单元测试序列化有什么意义吗?
【发布时间】:2010-11-09 07:28:59
【问题描述】:

我有一个类可以序列化一组我想要进行单元测试的对象(使用 XML 序列化)。

我的问题是感觉我要测试 XML 序列化的 .NET 实现,而不是任何有用的东西。我也有一个先有鸡还是先有蛋的场景,为了测试 Reader,我需要一个 Writer 生成的文件。

我认为我最终要寻求反馈的问题(有 3 个,但它们都相关)是:

  1. 是否可以在不使用 Reader 的情况下测试 Writer?
  2. 测试阅读器的最佳策略是什么(XML 文件?使用记录/播放进行模拟)?是不是您真正要做的就是测试已反序列化的对象的属性值?
  3. 测试作家的最佳策略是什么!

Xml 序列化的背景信息

我没有使用模式,所以所有 XML 元素和属性都匹配对象的属性。由于没有架构,与每个对象的属性中找到的标签/属性不匹配的标签/属性会被 XmlSerializer 简单地忽略(因此属性的值为 null 或默认值)。这是一个例子

<MyObject Height="300">
    <Name>Bob</Name>
    <Age>20</Age>
<MyObject>

将映射到

public class MyObject
{
  public string Name { get;set; }
  public int Age { get;set; }

  [XmlAttribute]
  public int Height { get;set; }
}

反之亦然。如果对象更改为下面的 XML 仍会成功反序列化,但 FirstName 将为空。

public class MyObject
{
  public string FirstName { get;set; }
  public int Age { get;set; }

  [XmlAttribute]
  public int Height { get;set; }
}

无效的 XML 文件将正确反序列化,因此单元测试将通过,除非您对 MyObject 的值运行断言。

【问题讨论】:

    标签: c# unit-testing serialization xml-serialization


    【解决方案1】:

    您需要能够向后兼容吗?如果是这样,可能值得为旧版本生成的文件构建单元测试,这些文件应该仍然能够被新版本反序列化。

    除此之外,如果您引入任何“有趣”的东西可能值得进行单元测试,以检查您是否可以序列化和反序列化,以确保您没有用只读属性等。

    【讨论】:

    • 我们将来可能会添加和删除(叶)属性,但不会有太大变化
    • 我想说这仍然足以让它值得测试。
    【解决方案2】:

    我认为单元测试序列化是必不可少的如果您可以在版本之间读取数据至关重要。而且您必须使用“已知良好”的数据进行测试(即,仅在当前版本中写入数据然后再次读取它是不够的)。

    您提到您没有架构...为什么不生成一个?手动(不是很难)或xsd.exe。然后你有一些东西可以用作模板,你可以使用XmlReader 来验证这一点。目前我正在大量进行 xml 序列化工作,更新架构比担心数据是否正确要容易得多。

    即使XmlSerializer 也会变得复杂;特别是如果您涉及子类 ([XmlInclude])、自定义序列化 (IXmlSerializable) 或非默认 XmlSerializer 构造(在运行时将其他元数据传递给 ctor)。另一种可能性是创造性地使用[XmlIngore][XmlAnyAttribute][XmlAnyElement];例如,您可能在版本 X 中支持(仅)用于往返的意外数据,但将其存储在版本 Y 的已知属性中。


    一般带有序列化:

    原因很简单:你可以破坏数据!你这样做有多糟糕取决于序列化程序;例如,使用BinaryFormatter(我知道问题是XmlSerializer),只需更改为:

    public string Name {get;set;}
    

    private string name;
    public string Name {
        get {return name;}
        set {name = value; OnPropertyChanged("Name"); }
    }
    

    可能是enough to break serialization,因为字段名称已更改(并且BinaryFormatter 喜欢字段)。

    在其他情况下,您可能会意外重命名数据(即使在基于合约的序列化程序中,例如 XmlSerializer / DataContractSerializer)。在这种情况下,您通常可以覆盖线路标识符(例如 [XmlAttribute("name")] 等),但检查这一点很重要!

    归根结底,它归结为:读取旧数据重要吗?通常是;所以不要只是运送它...证明你可以。

    【讨论】:

    • 它最初确实有一个模式,并且读+写是由我自己的写出对象图的类完成的。然后(1 年后!)我发现所有工作都可以由 XmlSerializer 完成。就向后兼容性而言,我会说 XML 将绑定到编写它的程序集版本。因此,如果您为最新版本的程序集提供了一些由先前版本生成的 XML,则对象模型可能已更改,因此不再匹配。我不确定这是否会发生,但我不知道如何(续)
    • 您会反序列化旧格式(正如 Jon 所提到的),而不会总是中断并且必须使用 XmlReader 手动执行它?
    • XmlSerializer 丢弃意外数据而不会出错,或者您可以使用[XmlAny*] - 所以有办法部分反序列化一个对象(并从“任何” 道具)。
    【解决方案3】:

    对我来说,这绝对属于“勿扰”类别。我不对我的工具进行单元测试。但是,如果您编写了 自己的 序列化类,那么一定要对其进行单元测试。

    【讨论】:

      【解决方案4】:

      如果您想确保对象的序列化不会中断,那么一定要进行单元测试。如果您阅读了 XMLSerializer 类的 MSDN 文档:

      XmlSerializer 无法序列化或反序列化以下内容:

      ArrayList 数组
      List 数组

      声明为无符号长整数的枚举还有一个特殊问题。此外,任何标记为 [Obsolete] 的对象都不会从 .Net 3.5 开始序列化。

      如果您有一组正在序列化的对象,那么测试序列化可能看起来很奇怪,但只需要有人编辑正在序列化的对象以包含序列化中断的不支持条件之一。

      实际上,您不是在对 XML 序列化进行单元测试,而是在测试您的对象是否可以被序列化。这同样适用于反序列化。

      【讨论】:

        【解决方案5】:

        是的,只要需要测试的东西经过适当的测试,通过一些干预。

        您首先进行序列化和反序列化的事实意味着您可能正在与“外部世界”交换数据——.NET 序列化域之外的世界。因此,您的测试应该具有此域之外的方面。不能用 Reader 测试 Writer,反之亦然。

        这不仅仅是关于您是否最终会测试 .NET 序列化/反序列化;你必须测试你与外界的接口——你可以以预期的格式输出 XML,并且你可以以预期的格式正确使用 XML。

        您应该拥有静态 XML 数据,可用于与序列化输出进行比较并用作反序列化的输入数据。

        假设您将记笔记和阅读笔记的工作交给同一个人:

        你——鲍勃,我要你记下以下内容:“小黄鸭”。 鲍勃——好的,明白了。 你——现在,把它读给我听。 Bob - “小黄鸭”

        现在,我们在这里测试了什么?鲍勃真的会写作吗? Bob 有没有写过什么或者他记住了这些单词?鲍勃真的会读书吗? ——他自己的笔迹?别人的笔迹呢?我们没有这些问题的答案。

        现在让我们向爱丽丝介绍图片:

        你——鲍勃,我要你记下以下内容:“小黄鸭”。 鲍勃——好的,明白了。 你——爱丽丝,你能检查一下鲍勃写的什么吗? 爱丽丝——好吧,他明白了。 你——爱丽丝,你能记下几句话吗? 爱丽丝 - 完成。 你——鲍勃,你能读一下吗? 鲍勃 - “红狐” 爱丽丝——是的,听起来不错。

        我们现在可以肯定地知道,只要我们完全信任 Alice,Bob 就可以正确地读写。静态 XML 数据(最好针对模式进行测试)应该足够值得信赖。

        【讨论】:

          【解决方案6】:

          根据我的经验,这绝对值得做,尤其是当 XML 将被消费者用作 XML 文档时。例如,消费者可能需要让每个元素都存在于文档中,以避免在遍历时对节点进行空检查或通过模式验证。

          默认情况下,除非您添加 [XmlElement(IsNullable = true)] 属性,否则 XML 序列化程序将忽略具有空值的属性。同样,您可能必须将通用列表属性重定向到具有 XMLArray 属性的标准数组。

          正如另一位贡献者所说,如果对象随时间变化,则需要不断检查输出是否一致。它还将保护您免受序列化程序本身的更改并且不向后兼容,尽管您希望这不会发生。

          因此,除了琐碎的用途之外,或者上述考虑无关紧要时,值得对其进行单元测试。

          【讨论】:

            【解决方案7】:

            有很多类型是序列化无法处理的等等。另外,如果你的属性有误,在尝试读回 xml 时通常会出现异常。

            我倾向于创建一个对象示例树,可以使用每个类(和子类)的至少一个示例进行序列化。然后至少将对象树序列化为字符串流,然后从字符串流中读取。

            您会惊讶于它发现问题的次数,让我不必等待应用程序启动才能找到问题。这种级别的单元测试更多是为了加快开发速度而不是提高质量,所以我不会为了工作序列化而这样做。

            正如其他人所说,如果您需要能够回读旧版本软件保存的数据,您最好为每个发布的版本保留一组示例数据文件,并进行测试以确认您仍然可以读取他们。这比一开始看起来更难,因为对象上字段的含义可能会在版本之间发生变化,因此仅能够从旧的序列化文件创建当前对象是不够的,您必须检查含义是否相同因为它是保存文件的软件版本。 (现在在你的根对象中添加一个版本属性!)

            【讨论】:

              【解决方案8】:

              我同意您的观点,即您将更多地测试 .NET 实现,而不是测试您自己的代码。但是,如果这就是您想要做的(也许您不信任 .NET 实现 :)),我可能会按如下方式处理您的三个问题。

              1. 是的,当然可以在没有阅读器的情况下测试编写器。使用 writer 序列化您提供给 MemoryStream 的示例(20 岁的 Bob)。使用 XmlDocument 打开 MemoryStream。断言根节点名为“MyObject”。断言它有一个名为“Height”的属性,其值为“300”。断言存在一个“Name”元素,其中包含一个值为“Bob”的文本节点。断言存在一个包含值为“20”的文本节点的“Age”元素。

              2. 只需执行 #1 的相反过程。从 20 年前的 Bob XML 字符串创建一个 XmlDocument。使用阅读器反序列化流。断言 Name 属性等于“Bob”。断言 Age 属性等于 20。您可以执行一些操作,例如添加带有无关紧要的空格或单引号而不是双引号的测试用例以更彻底。

              3. 参见 #1。您可以通过添加您认为可能会破坏它的棘手“边缘”案例来扩展它。具有各种 Unicode 字符的名称。超长的名字。空名。负年龄。等等。

              【讨论】:

                【解决方案9】:

                在某些情况下我已经这样做了...不是这样测试序列化,而是使用一些“已知良好”的 XML 序列化,然后将它们加载到我的类中,并检查所有属性(如适用)是否符合预期价值观。

                这不会对 first 版本进行任何测试...但如果类不断发展,我知道我会发现格式中的任何重大变化。

                【讨论】:

                  【解决方案10】:

                  我们对序列化进行验收测试,而不是单元测试。

                  这意味着我们的验收测试人员采用 XML 模式,或者在您的情况下使用一些示例 XML,并重新创建他们自己的可序列化数据传输类。

                  然后我们使用 NUnit 来测试我们的 WCF 服务和这个无尘室 XML。

                  通过这种技术,我们发现了很多很多错误。例如,我们更改了 .NET 成员的名称并忘记添加带有 Name = 属性的 [XmlElement] 标记。

                  【讨论】:

                    【解决方案11】:

                    如果您无法更改类的序列化方式,那么您正在测试 .NET 的 XML 序列化实现;-)

                    【讨论】:

                      【解决方案12】:

                      如果序列化 XML 的格式很重要,那么您需要测试序列化。如果您可以反序列化它很重要,那么您需要测试反序列化。

                      【讨论】:

                        【解决方案13】:

                        看到你无法真正修复序列化,你不应该测试它 - 相反,你应该测试你自己的代码以及它与交互的方式序列化机制。例如,您可能需要对正在序列化的数据的结构进行单元测试,以确保没有人意外更改字段或其他内容。

                        说到这一点,我最近采用了一种做法,即在编译时而不是在执行单元测试期间检查这些内容。有点繁琐,但是我有一个组件可以遍历 AST,然后我可以在 T4 模板中读取它,如果遇到不应该存在的东西,我可以写很多 #error 消息。

                        【讨论】:

                        • 抱歉,这完全是错误的。通过向被序列化的类添加属性,可以极大地影响 XML 序列化的许多细节。
                        • 可能是一个解释问题——但对我来说,测试“它与序列化机制交互的方式”包括测试你设置的属性是否以这种方式工作您认为。您不是在测试属性是否可以序列化,而是在测试您的 此属性 的配置是否已正确配置。
                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 2018-07-20
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        相关资源
                        最近更新 更多