| 网站首页 | 测试咨讯 | 新手入门 | 专注性能 | 测试技术 | 测试管理 | 测试工具 | 开发社区 | 工具下载 | 资料下载 | 测试论坛 | 

[CNTester联盟群]交流群:34446273/21968356/64461572 白盒群:18400216 自动化群:2706508 性能群:4498858 外包群:59649884 管理群(需有管理经验):64442523

新手入门
 入门指南
 经验之谈
测试咨讯
 行业新闻
 网站动态
 人才就业
 CNTester基金
 标准规范
专注性能
 性能测试知识
 性能测试工具
 程序设计性能
 数据库性能
 Web应用服务器性能
 操作系统性能
 服务器与网络性能
测试技术
 用例设计
 性能测试
 自动化测试
 Web测试
 面向对象测试
 综合技术
 阶段性测试
 行业类测试
测试管理
 项目管理
 项目案例
 质量管理
 软件过程
测试工具
 Mercury系列
 Rational系列
 测试管理工具
 性能测试工具
 功能测试工具
 单元测试工具
 其它测试工具
开发社区
 Java技术
 DotNet技术
 C技术
 数据库技术
 操作系统
 服务器与中间件
工具下载
 测试工具
 开发工具
 企业工程
 网络通信
 媒体桌面
 游戏娱乐
 其他工具
资料下载
 测试资料
 程序设计
 数据库
 操作系统
 应用服务器
 电子期刊
 其他资料
 
 
您现在的位置: 中国测试员网站 >> 开发社区 >> Java技术 >> 文章正文
  [推荐]追求代码质量: 探究 XMLUnit           ★★★ 【字体:
追求代码质量: 探究 XMLUnit
作者:Andrew G…    文章来源:IBM    点击数:    更新时间:2007-5-21    
Java™ 开发人员一般都很善于解决问题,所以由 Java 开发人员提出更容易的方法用以验证 XML 文档是很自然的事。本月,Andrew 将向您介绍 XMLUnit,一个能满足您所有的 XML 验证需求的 JUnit 扩展框架。

在软件开发周期中,需要不时地验证 XML 文档的结构或内容。不管构建的是何种应用程序,测试 XML 文档都具有一定的挑战性,尤其是在没有相关工具的情况下就更是如此。

本月,我将首先向您说明为何不能 使用 String 比较来验证 XML 文档的结构和内容。之后,我会介绍 XMLUnit,一个由 Java 开发人员创建并可服务于 Java 开发人员的 XML 验证工具,向您展示如何使用它来验证 XML 文档。

古典的 String 比较

首先,假设您已经构建了一个应用程序,该应用程序可以输出代表对象依赖性报告的 XML 文档。对于给定的类和对应的过滤器的集合,会生成一个报告来输出类和类的依赖项(想象一下导入)。

清单 1 显示了用于给定类列表(com.acme.web.Widgetcom.acme.web.Account)的报告,过滤器被设为忽略外部类,比如 java.lang.String



清单 1. 一个示例依赖性 XML 报告
				
<DependencyReport date="Sun Dec 03 22:30:21 EST 2006">
  <FiltersApplied>
    <Filter pattern="java|org"/>
    <Filter pattern="net."/>
  </FiltersApplied>
  <Class name="com.acme.web.Widget">
    <Dependency name="com.acme.resource.Configuration"/>
    <Dependency name="com.acme.xml.Document"/>
  </Class>
  <Class name="com.acme.web.Account">
    <Dependency name="com.acme.resource.Configuration"/>
    <Dependency name="com.acme.xml.Document"/>
  </Class>
</DependencyReport>

清单 1 很明显是由应用程序生成的;因而,第一层测试就是验证应用程序是否真能生成一个文档。一旦验证了这一点,就可以继续测试指定文档的其他三个方面:

  • 结构
  • 内容
  • 指定内容

可以通过单独使用 JUnit 利用 String 比较处理上述前两个方面,如清单 2 所示:



清单2. 硬性验证 XML
				
public class XMLReportTest extends TestCase {

 private Filter[] getFilters(){
  Filter[] fltrs = new Filter[2];
  fltrs[0] = new RegexPackageFilter("java|org");
  fltrs[1] = new SimplePackageFilter("net.");
  return fltrs;
 }

 private Dependency[] getDependencies(){
  Dependency[] deps = new Dependency[2];
  deps[0] = new Dependency("com.acme.resource.Configuration");
  deps[1] = new Dependency("com.acme.xml.Document");
  return deps;
 }

 public void testToXML() {
  Date now = new Date();
  BatchDependencyXMLReport report = 
   new BatchDependencyXMLReport(now, this.getFilters());

  report.addTargetAndDependencies(
    "com.acme.web.Widget", this.getDependencies());
  report.addTargetAndDependencies(
    "com.acme.web.Account", this.getDependencies());

  String valid = "<DependencyReport date=\"" + now.toString() + "\">"+
    "<FiltersApplied><Filter pattern=\"java|org\" /><Filter pattern=\"net.\" />"+
    "</FiltersApplied><Class name=\"com.acme.web.Widget\">" +
    " <Dependency name=\"com.acme.resource.Configuration\" />"+
    "<Dependency name=\"com.acme.xml.Document\" /></Class>"+
    "<Class name=\"com.acme.web.Account\">"+
    "<Dependency name=\"com.acme.resource.Configuration\" />"+
    "<Dependency name=\"com.acme.xml.Document\" />"+
    "</Class></DependencyReport>";

   assertEquals("report didn't match xml", valid, report.toXML());
 }
}

清单 2 中的测试有其他一些重大的缺陷 —— 而不仅仅是硬编码 String 比较那么简单。首先,测试并不真正可读。第二,它惊人的脆弱;一旦 XML 文档的格式改变(包括添加空格),与其尝试修复 String 本身,还不如粘贴进一个新的文档副本。最后,测试的本性会迫使您必须应付 Date 方面,虽然您并不想如此。

若想确保文档中第二个 Class 元素的 name 值是 com.acme.web.Account 又该如何呢?当然,您可以使用常规表达式或 String 搜索,但所需的工作量太大。这样看来,通过一个解析框架来操纵此 DOM 不是更有意义么?

XMLUnit 能否用于 TestNG?

XMLUnit 是一个 JUnit 扩展,但这并不意味着不能在 TestNG 使用它。只要它具有 API 而且此 API 支持委托同时不基于修饰器,那么您可以将几乎任何框架整合进 TestNG。

用 XMLUnit 进行测试

当您感觉自己为完成一项任务而努力过了头,您就可以想想解决此问题是否还有更容易的捷径可寻。如果所要解决的问题涉及的是编程式地验证 XML 文档,那么所应想到的解决方案就是 XMLUnit。

XMLUnit 是一种 JUnit 扩展框架,有助于开发人员测试 XML 文档。实际上,XMLUnit 是一种真正的 XML 测试的“多面手”:可以使用它来验证 XML 文档的结构、内容甚至该文档的指定部分。

最简单的做法是使用 XMLUnit 在逻辑上对比运行时 XML 文档和预定义的有效控制文件。本质上讲,这就是一种差异测试:假定一个 XML 文档是正确的,那么此应用程序在运行时是否会生成同样的东西?它是相对简单的一种测试,但也可以使用它来验证 XML 文档的结构和内容。也可以通过 XPath 的一点帮助来验证特定内容。

委托而非继承

首要原则是尽量避免测试用例继承。许多 JUnit 扩展框架,包括 XMLUnit,都提供可以通过继承得到的专门的测试用例来协助测试某一特定的架构。从框架继承来的测试用例都缺乏灵活性,这是 Java 平台的单一继承的范型所致。更多的时候,这些相同的 JUnit 扩展框架提供一个委托 API,此 API 可以更易于组合不同的框架,而无需采用严格的继承结构。

验证内容

可以通过委托或继承的方式使用 XMLUnit。作为最佳策略,我建议避免测试用例继承。另一方面,从 XMLUnit 的 XMLTestCase 继承确实可以提供一些方便的声明方法(这些方法不是静态 的,因而也就不能像 JUnit的 TestCase 声明一样被静态引用)。

不管您如何选择使用 XMLUnit,都必须实例化 XMLUnit 的解析器。您可以通过 System.setProperty 调用实例化它们,也可以通过 XMLUnit 核心类上的一些方便的 static 方法对它们进行实例化。

一旦用所需要的不同的解析器实例化 XMLUnit 之后,就可以使用 Diff 类,这是从逻辑上对比两个 XML 文档所需的中心机制。在清单 3 中,我利用 XMLUnit 对 >testToXML test 做了一些改进:



清单 3. 改进后的 testToXML 测试
				
public class XMLReportTest extends TestCase {

 protected void setUp() throws Exception {		 
  XMLUnit.setControlParser(
    "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
  XMLUnit.setTestParser(
    "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
  XMLUnit.setSAXParserFactory(
    "org.apache.xerces.jaxp.SAXParserFactoryImpl");
  XMLUnit.setIgnoreWhitespace(true);   
 }

 private Filter[] getFilters(){
  Filter[] fltrs = new Filter[2];
  fltrs[0] = new RegexPackageFilter("java|org");
  fltrs[1] = new SimplePackageFilter("net.");
  return fltrs;
 }

 private Dependency[] getDependencies(){
  Dependency[] deps = new Dependency[2];
  deps[0] = new Dependency("com.acme.resource.Configuration");
  deps[1] = new Dependency("com.acme.xml.Document");
  return deps;
 }

 public void testToXML() {
  BatchDependencyXMLReport report = 
    new BatchDependencyXMLReport(new Date(1165203021718L), 
	  this.getFilters());

  report.addTargetAndDependencies(
    "com.acme.web.Widget", this.getDependencies());
  report.addTargetAndDependencies(
    "com.acme.web.Account", this.getDependencies());

  Diff diff = new Diff(new FileReader(
    new File("./test/conf/report-control.xml")),
    new StringReader(report.toXML()));

  assertTrue("XML was not identical", diff.identical());		
 }
}

注意一下我是如何实例化 XMLUnit 的 setControlParsersetTestParsersetSAXParserFactory 方法的。您可以为这些值使用任何兼容 JAXP 的解析器。还要注意我是用 true 调用 setIgnoreWhitespace 的 —— 这是一根救命稻草,相信我!否则,不一致的空白会导致很多故障。




用 Diff 比较

Diff 类支持两种比较:identicalsimilar。如果所比较的文档在结构和值(如果设置了标志就忽略空白)方面都完全相同,那么它们就被认为是 identical;如果两个文档是完全相同的,那么它们也就很自然的是 similar 的。反之,却不一定。

例如,清单 4 是与清单 5 相似的一个简单的 XML 代码片段,但二者并不相同:



清单 4. 一个帐号 XML 片段
				
<account>
 <id>3A-00</id>
 <name>acme</name>
</account>

清单 5 中的 XML 片段与清单 4 中所示的 XML 片段有相同的逻辑文档。但 XMLUnit 并不认为二者是相同的,原因是二者的 nameid 元素是颠倒的。



清单 5. 一个相似的 XML 片段
				
<account>
 <name>acme</name>
 <id>3A-00</id>
</account>

相应地,我可以编写测试用例来验证 XMLUnit 的行为,如清单 6 所示:



清单 6. 用来验证相同性和相似性的测试
				
public void testIdenticalAndSimilar() throws Exception {
 String controlXML = "<account><id>3A-00</id><name>acme</name></account>";
 String testXML = "<account><name>acme</name><id>3A-00</id></account>"; 
 Diff diff = new Diff(controlXML, testXML);
 assertTrue(diff.similar());
 assertFalse(diff.identical());
}

相似和相同的 XML 文档之间的差异是很微小的;但若能验证两者却非常有用,例如在需要测试由不同应用程序或客户程序生成的文档的情况下。




验证结构

除了验证内容之外,您还需要验证 XML 文档的结构。在这种情况下,元素和属性的值并不重要 —— 您所关心的是结构。

还好,我还可以再次使用清单 3 中定义的测试用例来验证文档的结构,并可以有效忽略元素文本值和属性值。为实现此目的,我调用 Diff 类上的 overrideDifferenceListener() 并为它添加由 XMLUnit 提供的 IgnoreTextAndAttributeValuesDifferenceListener。修改后的测试如清单 7 所示:



清单 7. 无需属性值验证 XML 结构
				
public void testToXMLFormatOnly() throws Exception{
 BatchDependencyXMLReport report = 
   new BatchDependencyXMLReport(new Date(), this.getFilters());

 report.addTargetAndDependencies(
   "com.acme.web.Widget", this.getDependencies());
 report.addTargetAndDependencies(
   "com.acme.web.Account", this.getDependencies());
 
 Diff diff = new Diff(new FileReader(
   new File("./test/conf/report-control.xml")),
   new StringReader(report.toXML()));

 diff.overrideDifferenceListener(
   new IgnoreTextAndAttributeValuesDifferenceListener());
 assertTrue("XML was not similar", diff.similar());		
}

相似但不相同!

当使用 IgnoreTextAndAttributeValuesDifferenceListener 类时,必须声明这两个文档是 similar identical。如果错误地调用了 identical,那么就需要处理属性值。

当然,DTD 的模式和 XML 模式都有助于 XML 结构验证,然而,有时文档并不需要引用它们 —— 在这些场景下,结构验证可能会很有用。同样,如果需要忽略特定的一些值(例如那些 Date 值),就可以实现 DifferenceListener 接口(正如 IgnoreTextAndAttributeValuesDifferenceListener 所做的一样)并提供一个定制实现。

XMLUnit 和 XPath

为实现 XML 测试的所有三个方面,XMLUnit 还可以借助 XPath 进行 XML 文档特定部分的验证。

例如,使用清单 1 所示相同的格式,我想验证由应用程序生成的第一个 Class 元素的 name 属性值是否是 com.acme.web.Widget。要实现此目的,我必须创建一个 XPath 表达式来导航到准确的位置;而且,XMLUnit 的 XMLTestCase 提供了一个方便的 assertXpathExists() 方法,这意味着我必须现在扩展 XMLTestCase



清单 8. 使用 XPath 来验证准确的 XML 值
				
public void testToXMLFormatOnly() throws Exception{
 BatchDependencyXMLReport report = 
   new BatchDependencyXMLReport(new Date(), this.getFilters());

 report.addTargetAndDependencies(
   "com.acme.web.Widget", this.getDependencies());
 report.addTargetAndDependencies(
   "com.acme.web.Account", this.getDependencies());
 
 assertXpathExists("//Class[1][@name='com.acme.web.Widget']", 
  report.toXML());	
}

如清单 8 所示,XMLUnit 和 XPath 一起协作提供了可以准确验证 XML 文档 的一种便捷机制,而不是进行大规模的差异测试。请记住要在 XMLUnit 内充分利用 XPath,您的测试用例必须要扩展 XMLTestCase。如果熟悉 XPath 也会大有帮助!

XPath 是什么?
XPath 或 XML Path Language 是一种表达式语言,用来基于树表示定位 XML 文档的各部分。XPath 允许您导航 XML 文档并可以帮您选择文档值。

为何要舍近求远呢?

XMLUnit 是一种基于 Java 的开放源码工具,它使测试 XML 文档更为简单和灵活,而这是使用 String 比较所达不到的。使用 XMLUnit 进行差异测试所存在的惟一缺点是测试会依赖于文件系统来加载控制文档。在编写测试时,请务必考虑这一附加的依赖性。

虽然 XMLUnit 已经有段时间没有发布任何更新了,但它当前的特性集已经足够健壮来应对各种测试冲击,并且它用在这种情况下基本上是免费的!

文章录入:root    责任编辑:root 
  • 上一个文章:

  • 下一个文章:
  • 发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
     
    最新热点 最新推荐 相关文章
    · 面向对象设计与编程核心技术
    · Tomcat系统安全管理与权限管理
    · 雅虎公司C#笔试题
    · 利用Java事件处理机制实现录制、回放功能
    · apache+tomcat+mysql负载均衡和集群
    · Linux 指令大全
    · 应用服务器内存泄露问题诊断一例
    · 追求代码质量: 软件架构的代码质量
    · 对话 UNIX
    · 关于一些算法问题与朋友的讨论
    · 对话 UNIX
    · nmon 性能:分析 AIX 和 Linux 性能的免费
    · 追求代码质量: 谨防紧密耦合!
    · 应用服务器内存泄露问题诊断一例
    · Linux 指令大全
    · C 编程最佳实践
    · LPI 证书 101 考试准备,第2部分 [正则表
    · LPI 证书 101 考试准备,第1 部分 [使用L
    · Linux 究竟是什么?
    · 利用Java事件处理机制实现录制、回放功能
    Visual C++程序设计中的API和
    追求代码质量: 通过测试分类
     
     
     
    ======> [CNTester联盟群]交流群:34446273/21968356/64461572 白盒群:18400216 自动化群:2706508 性能群:4498858 外包群:59649884 管理群(需有管理经验):64442523
    | 设为首页 | 加入收藏 | 联系站长 | 友情链接 | 版权申明 | 网站公告 | 管理登录 | 

    Copyright@2007 by CNTester.com 中国测试员网站 桂ICP备07005590

    本站为开源免费网站,非商业赢利性组织。本站文章部分从网络搜索获取,如果您认为某些侵犯了您的权益,麻烦您联系本站,我们会尽快删除相关内容,同时也希望您的谅解,我们的初衷是为了让更多人去学习这方面的知识,让行业有更好的发展。

    联系电话: 15021358905