Saturday, January 14, 2006

Test::Unit::XML Quick Start Tutorial

If you are working with XML and Ruby, you might want to have a look at Test::Unit::XML, a unit test framework for XML documents and document fragments. The following is a short tutorial to help you get started.

This version of the tutorial is for Test::Unit::XML version 0.1.4.

Downloading and Installing

The easiest way to download and install Test::Unit::XML is to use gem, the Ruby package manager. All you need to do is this:

gem install testunitxml

You can also download Test::Unit::XML from the download page, and install manually.

If you install from the tarball or Zip archives, you need to:

  1. Unpack the archive file
  2. Run the script setup.rb from a console window.

Testing XML Documents

Let's try something easy, just to see how Test::Unit::XML works. Assume that you have two XML files that you wish to compare. Lets call them expected.xml and actual.xml. If you wish, you can pretend that actual.xml has been created by some software that you want to test.

Fire up a text editor and create a test case like this:

#! /usr/bin/ruby

@@lib_path = File.join(File.dirname(__FILE__), "..", "lib")
$:.unshift @@lib_path

require 'test/unit/xml'

class TestTestUnitXml < Test::Unit::TestCase
def setup
@expected ="test/data/expected.xml")
@actual ="test/data/actual.xml")

def test_io_objects
assert_xml_equal(@expected, @actual)

Note that when assert_xml_equal is given IO objects (File is a subclass of IO) they are converted to XML documents before the comparison is made.

In the current version of Test::Unit::XML, assert_xml_equal is the only available assertion. Luckily, assert_xml_equal is also the XML assertion you are likely to need the most. Also, assert_xml_equal can do a lot more than testing io objects. For example, you can test strings:

class TestTestUnitXml < Test::Unit::TestCase
def setup
@expected = %Q{<t:root xmlns:t="urn:x-hm:test" xmlns:x="urn:x-hm:test2" id="a" t:type="test1"/>}
@actual = %Q{<s:root xmlns:s="urn:x-hm:test" xmlns:x="urn:x-hm:test" id="a" s:type="test1"/>}

def test_strings
assert_xml_equal(@expected, @actual)

Note that the assertion succeds, even though the t and s namespace prefixes used by the root element are different. This is because the prefixes don't matter. Both root elements are bound to the same namespace, urn:x-hm:test, and that is what counts.

Also note that the xmlns:x declarations are different, yet the test succeeds. Why is that? It is because the x prefix is never actually used. Test::Unit::XML does not compare namespace declarations, only how the namespaces are used. There are reasons for this:

An XML processor may move declarations, and change prefixes, outside of programmer control. For example, XSLT processors may do this. Also, namespace declarations that are never actually used may suddenly appear as the result of a transformation. This does not affect how a document is processed in any way. Because of this, Test::Unit::XML completely ignores the namespace declarations. (If you need to be specific about which prefixes are used, on which element a namespace declaration ends up on, and whether there are any extraneous namespaces, you need to do DTD validation. Test::Unit::XML has no support for this, but you could try out the XML::Tools module at RubyForge.)

Testing REXML Nodes

The most flexible way to use the assert_xml_equal assertion is to feed it REXML nodes. You can compare any kind of REXML node, for example element nodes, CDATA nodes, comment nodes, etc. This is interesting, because it means you can compare pieces of documents. For example, given a document that contains a table with records, you could compare two records like this:

assert_xml_equal(table.elements["table/record[5]"], table.elements["table/record[7]"])

What About Doctype Declarations?

The current version of Test::Unit::XML does not validate documents against DTDs. However, the assert_xml_equal assertion does compare Doctype declarations, if they are present. (This is new from version 0.1.4.)

The online documentation describes precisely how doctype declarations are compared.

1 comment:

Anonymous said...

I want to extract element information and element data from an XML file. Is it possible for me to use Ruby? If possible could you please suggest me some tutorials which can help me with this.