单元测试序列化有什么意义吗?

时间:2009-07-09 08:46:42

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

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

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

我认为这些问题(有3个但它们都有关系)我最终都在寻找反馈意见:

  1. 是否可以在不使用Reader的情况下测试Writer?
  2. 测试阅读器的最佳策略是什么(XML文件?记录/回放模拟)?是否所有您真正要做的是测试已反序列化的对象的属性值?
  3. 测试作者的最佳策略是什么!
  4. 关于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的值运行断言,否则单元测试将通过。

13 个答案:

答案 0 :(得分:26)

您是否需要能够向后兼容?如果是这样,可能值得建立旧版本生成的文件的单元测试,这些文件仍然可以通过新版本进行反序列化。

除此之外,如果您介绍任何“有趣”的内容,可能值得进行单元测试,只需检查您是否可以序列化和反序列化,以确保您没有做任何有趣的事情。只读财产等。

答案 1 :(得分:19)

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

你提到你没有架构......为什么不生成架构?手工(不是很难)或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")]等),但重要的是要检查它!

归根结底,它归结为:您是否可以阅读旧数据?通常是;所以不要只发货...... 证明你可以。

答案 2 :(得分:7)

对我来说,这绝对是在Do not Bother类别中。我没有对我的工具进行单元测试。但是,如果您编写了自己的序列化类,那么请务必对其进行单元测试。

答案 3 :(得分:5)

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

  

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

数组列表数组列表&lt; T&gt;

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

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

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

答案 4 :(得分:3)

是的,只要通过一些干预对需要测试的内容进行适当的测试。

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

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

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

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

You - Bob, I want you to jot down the following: "small yellow duck."
Bob - OK, got it.
You - Now, read it back to me.
Bob - "small yellow duck"

现在,我们在这里测试了什么?鲍勃真的可以写吗? 是否鲍勃甚至写了什么,还是记住了这些词?鲍勃真的能读懂吗? - 他自己的笔迹?另一个人的笔迹怎么样?我们对这些问题都没有答案。

现在让我们向爱丽丝介绍一下:

You - Bob, I want you to jot down the following: "small yellow duck."
Bob - OK, got it.
You - Alice, can you please check what Bob wrote?
Alice - OK, he's got it.
You - Alice, can you please jot down a few words?
Alice - Done.
You - Bob, can you please read them?
Bob - "red fox"
Alice - Yup, that sounds right.

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

答案 5 :(得分:2)

根据我的经验,这绝对值得做,特别是如果XML将被消费者用作XML文档。例如,消费者可能需要在文档中包含每个元素,以避免在遍历时对节点进行空检查或传递模式验证。

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

正如另一位撰稿人所说,如果对象随时间而变化,则需要不断检查输出是否一致。它还可以保护您免受序列化程序本身的影响而不是向后兼容,尽管您希望这不会发生。

因此,对于除了微不足道的用途之外的任何事情,或者上述考虑因素无关紧要的事情,单位测试它的价值是值得的。

答案 6 :(得分:2)

序列化无法处理很多类型等。另外,如果你的属性有问题,那么在尝试读取xml时常常会出现异常。

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

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

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

答案 7 :(得分:2)

我同意你的看法,你将测试.NET实现,而不是测试你自己的代码。但如果你想做什么(也许你不相信.NET实现:)),我可能会按照以下三个问题处理。

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

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

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

答案 8 :(得分:1)

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

这不会测试第一个版本的任何内容......但是如果这些类不断发展,我知道我会抓住格式中的任何重大变化。

答案 9 :(得分:1)

我们接受测试我们的序列化而不是单元测试。

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

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

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

答案 10 :(得分:0)

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

答案 11 :(得分:0)

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

答案 12 :(得分:0)

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

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