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.
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).
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>
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>Š</th> </test:expect> </test:test> </test:tests> <xsl:template match="thead/row/entry"> ... <xsl:when test="not(*) and not(normalize-space(.))"> <th>Š</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.
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.
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...
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.
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>
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;
The stylesheet to test is imported.
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.