unit testing序列化有什么意义吗?

我有一个类,它序列化一组对象(使用XML序列化),我想要unit testing。

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

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

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

关于Xml序列化的背景信息

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

 Bob 20  

会映射到

 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的值运行断言,否则unit testing将通过。

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

你提到你没有架构……为什么不生成架构? 手动(它不是很难),或使用xsd.exe 。 然后你有一些东西可以用作模板,你可以使用XmlReadervalidation这一点。 我目前在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"); } } 

可能足以打破序列化 ,因为字段名称已更改(并且BinaryFormatter喜欢字段)。

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

最终,它归结为:您是否可以阅读旧数据? 它通常是; 所以不要只发货… certificate你可以。

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

除此之外,如果你介绍任何“有趣”的东西, 可能值得unit testing,只是检查你可以序列化和反序列化只是为了确保你没有做一些与readonly属性等时髦的东西。

对我来说,这绝对是在Do not Bother类别中。 我没有对我的工具进行unit testing。 但是,如果您编写了自己的序列化类,那么一定要对它进行unit testing。

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

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

ArrayList的数组
List 的数组

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

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

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

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

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

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

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

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

你 - 鲍勃,我想让你记下下面的内容:“小黄鸭。”
鲍勃 - 好的,明白了。
你 - 现在,把它读回给我。
鲍勃 - “小黄鸭”

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

现在让我们介绍一下Alice:

你 - 鲍勃,我想让你记下下面的内容:“小黄鸭。”
鲍勃 - 好的,明白了。
你 - 爱丽丝,请你看看鲍勃写的是什么?
爱丽丝 - 好的,他知道了。
你 - 爱丽丝,你能记下几句话吗?
爱丽丝 - 完成。
你 - 鲍勃,你能读一下吗?
鲍勃 - “红狐狸”
爱丽丝 - 是的,听起来不错。

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

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

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

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

因此,对于除了微不足道的用途之外的任何事情,或者上述考虑因素无关紧要的事情,值得unit testing的努力值得。

有很多类型,序列化无法应对等。另外,如果你的属性有问题,通常在尝试读取xml时会出现exception。

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

你会惊讶于这个问题的出现次数,并且我不得不等待应用程序启动才能找到问题。 这种级别的unit testing更多的是关于加速开发而不是提高质量,所以我不会为了工作序列化而这样做。

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

我同意你的看法,你将测试.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字符的名称。 超长的名字。 空名。 负年龄。 等等。

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

对于第一个版本,这不会测试任何东西……但如果这些类不断发展,我知道我会抓住格式中的任何重大变化。

我们对序列化进行验收测试而不是unit testing。

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

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

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

如果没有什么可以改变你的类序列化的方式,那么你正在测试.NET的XML序列化实现;-)

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

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

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