Writing the Tests

You can either write tests inside the stylesheet (see the simple example below) or store them externally (see the Standalone Tests section below). You can also see the Pre-defined Tests section to write tests that skip the generation step.

Simple Example

The following simple example shows a single test that checks the result of calling the eg:square() function with the number parameter set to 2. The expected result is the number 4.

<test:tests>
  <test:test>
    <test:param name="number" select="2" />
    <test:expect select="4" />
  </test:test>
</test:tests>

<xsl:function name="eg:square" as="xs:double">
  <xsl:param name="number" as="xs:double" />
  <xsl:sequence select="$number * $number" />
</xsl:function>

All the testing elements are in the namespace http://www.jenitennison.com/xslt/unit-test, so that needs to be declared in your stylesheet. If you want to prevent namespace declarations for that namespace littering your code, you can use the exclude-result-prefixes attribute on <xsl:stylesheet> as follows:

<xsl:stylesheet version="..."
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                ...
                xmlns:test="http://www.jenitennison.com/xslt/unit-test"
                exclude-result-prefixes="... test">
...
</xsl:stylesheet>

The tests that are applicable to a particular template or function are all wrapped in a <test:tests> element. This is primarily to make it easy to collapse them all out of the way (in editors that can do such a thing) so they don't become too distracting when writing the code. Individual tests are given in <test:test> elements.

Both <test:tests> and <test:test> elements can have an id attribute to specify a unique identifier and/or a <test:title> child that gives a human-readable title. Both are optional, however if you use descriptive verb phrases, you could use these to uniquely identify and describe your tests. What's more is you can then report on this and present a meaningful report of what's being tested.

Within the <test:test> element, you need to specify the input to the template or function and the expected output. The input can consist of a context node (for templates only) specified with an optional <test:context> element and any number of parameters specified with <test:param> elements. The expected output must be specified with a <test:expect> element.

The values for the context, parameters and expected result are all defined in the same ways. For atomic values (strings, numbers and the like), use the select attribute, as in the example previously. If you want to specify nodes, you need to supply a document from which the nodes can be selected. You can do this either with the href attribute, which gives a URI for an external document, or by embedding the document (or document fragment) within the relevant element. The select attribute is then used to select nodes within the document; the path it holds is interpreted from the root/document node of the document. The default value for the select attribute is "/*", which selects (all) the document element(s).

Using External Documents as Input and Output

If you want to test the stylesheet as a whole, the easiest thing to do is to create external input/output files and reference them. Note that the result must be an XML document, so you can't use this to test HTML output.

        
<test:tests>
  <test:test>
    <test:context href="input.xml" />

    <test:expect href="output.xml" />
  </test:test>
</test:tests>
<xsl:template match="/">
  ...
</xsl:template>

Supplying nodes as context and expected value

You can test with simplified documents by simply embedding the important part within the test itself. The embedded document should include any important ancestors of the relevant element, but doesn't need to include any of the irrelevant parts of the document.

Use the select attribute to pick the nodes you want from the document. Remember that if your function or template returns something other than elements, you will need a select attribute on the <test:expect> element too.

<test:tests>
  <test:test>
    <test:title>Empty header cells</test:title>
    <test:context select="/table/tgroup/thead/row/entry">
      <table>
        <tgroup>
          <thead>
            <row>
              <entry />
            </row>
          </thead>
        </tgroup>
      </table>
    </test:context>
    <test:expect>
      <th>&#x160;</th>
    </test:expect>
  </test:test>
</test:tests>    
<xsl:template match="thead/row/entry">
  ...
  <xsl:when test="not(*) and not(normalize-space(.))">
    <th>&#x160;</th>
  </xsl:when>
  ...
</xsl:template>

NB. Differences in whitespace-only text nodes between the expected and actual result are ignored in this version. If you want to generate tests that take whitespace-only text nodes into account, you'll need to configure the testing; see below.

Standalone Tests

It's possible to write tests externally to the template they test. This is useful for example, when you don't want to ship tests along with your templates.

A standalone test suite has a <test:suite> document element. The <test:suite> element has two attributes: stylesheet, which is a URL (relative to the test suite document) that points to the stylesheet tested by the suite; and date, which is a xs:dateTime that gives the date/time for the suite, useful for versioning.

The <test:suite> element contains one or more <test:tests> elements, which are the same as described above except that they also contain, immediately after the <test:title> element if there is one, a <test:xslt> element. The <test:xslt> element contains either an <xsl:template> or a <xsl:function> element, with name, match and/or mode attributes to identify the template/function being tested but no content.

Standalone Test Suite

Below is an example standalone test suite for a utils.xsl stylesheet, last modified at 12:44 on September 20th 2005. The only test shown is for the eg:square function.

<test:suite stylesheet="utils.xsl" date="2005-09-20T12:44:00">
        
<test:tests>
  <test:xslt>
    <xsl:function name="eg:square" />
  </test:xslt>
  <test:test>

    <test:param name="number" select="2" />
    <test:expect select="4" />
  </test:test>
  ...
</test:tests>
...
</test:suite>

The standalone test suite can be used as the input to generate-tests.xsl and the tests will be run on the referenced stylesheet.

The extract-tests.xsl stylesheet transforms a stylesheet that has tests embedded in it into a standalone test suite. The reverse has not yet been implemented...

Configuring the Testing

You can configure the details of how the testing is carried out, and in particular how sequences/items/nodes are compared, by creating your own implementations of the various functions in generate-tests-utils.xsl. A test:config attribute on the <xsl:stylesheet> element, or a config attribute on the <test:suite> element in a standalone test suite, can point to a stylesheet which contains these implementations in order to override the default behaviour.

Ignoring Insignificant Ordering Differences

By default, the framework compares the expected and actual results on an item-by-item basis. So if you expect

<xs:all>
  <xs:element ref="foo" />
  <xs:element ref="bar" />
</xs:all>

but you get

<xs:all>
  <xs:element ref="bar" />
  <xs:element ref="foo" />
</xs:all>

then the test fails.

In some cases, it might be that you really don't care what order particular elements appear in, just as long as they're all generated. In this example, the <xs:element> elements appearing in the content of the <xs:all> element can appear in any order with the same meaning.

To configure the testing package to ignore these ordering differences, you can create xsd-config.xsl, which overrides the test:sorted-children function from generate-test-utils.xsl. This ensures that the sequence of <xs:element> elements are sorted by name before being compared, effectively ignoring the original order in which they appeared.

To use xsd-config.xsl, include a reference to it from the test:config attribute on the <xsl:styelsheet> document element.

<xsl:stylesheet version="2.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:test="http://www.jenitennison.com/xslt/unit-test"
                extension-element-prefixes="test"
                test:config="xsd-config.xsl">
...  
</xsl:stylesheet>

Pre-defined Tests

By writing pre-defined tests, you can avoid the initial step of applying generate-test.xsl. Instead you write your tests directly and import the stylesheet under test along with a utility stylesheet.

You must ensure that;

  1. The stylesheet to test is imported.

  2. The tests-utils.xsl template is also imported.

An example is shown below

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:test="http://www.jenitennison.com/xslt/unit-test">
	<xsl:import test:stylesheet="" href="./my-stylesheet.xsl"/>
	<xsl:import href="../tennison-tests/main/src/xslt/tests-utils.xsl"/>
	<test:suite>
		<test:tests>
			<test:test>
				<test:title>test-initial-transform</test:title>
				<test:context href="../input1.xml"/>
				<test:expect href="../output1.xml"/>
			</test:test>
			<test:test>
				<test:title>test-secondary-transform</test:title>
				<test:context href="../input2.xml"/>
				<test:expect href="../output2.xml"/>
			</test:test>
		</test:tests>
	</test:suite>
</xsl:stylesheet>

This exampe, uses the inputn.xml as input and compares the resulting transformation to the output file outputn.xml.

Notice that the stylesheet under test is imported (my0stylesheet.xsl in this example) and that test-utils.xsl is imported. This is a helper that is used to by-pass the generation step, it allows you to apply-templates to the root node.