Recipe 15.4. Including Embedded Unit Test Data in Utility Stylesheets
Problem
You want to package tests
with your utility
stylesheets so they can be verified at any time.
Solution
The following stylesheet is meant to be included as a utility.
However, this example provides the capability of testing the
stylesheet by executing it as its own input document:
<!-- math.max.xslt -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:math="http://www.exslt.org/math" exclude-result-prefixes="math"
xmlns:test="http://www.ora.com/XSLTCookbook/test" id="math:math.max">
<xsl:template name="math:max">
<xsl:param name="nodes" select="/.."/>
<xsl:param name="max"/>
<xsl:variable name="count" select="count($nodes)"/>
<xsl:variable name="aNode" select="$nodes[ceiling($count div 2)]"/>
<xsl:choose>
<xsl:when test="not($count)">
<xsl:value-of select="number($max)"/>
</xsl:when>
<xsl:when test="number($aNode) != number($aNode)">
<xsl:value-of select="number($aNode)"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="math:max">
<xsl:with-param name="nodes" select="$nodes[not(. <= number($aNode))]"/>
<xsl:with-param name="max">
<xsl:choose>
<xsl:when test="not($max) or $aNode > $max">
<xsl:value-of select="$aNode"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$max"/>
</xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- TEST CODE: DO NOT REMOVE! -->
<xsl:template match="/xsl:stylesheet[@id='math:math.max'] | xsl:include[@href='math.
max.xslt'] " priority="-1000">
<xsl:message>
TESTING math.max
</xsl:message>
<xsl:for-each select="document('')/*/test:test">
<xsl:variable name="ans">
<xsl:call-template name="math:max">
<xsl:with-param name="nodes" select="test:data"/>
</xsl:call-template>
</xsl:variable>
<xsl:if test="$ans != @ans">
<xsl:message>
math:max TEST <xsl:value-of select="@num"/> FAILED [<xsl:value-of
select="$ans"/>]
</xsl:message>
</xsl:if>
</xsl:for-each>
<!-- Test with Infinity -->
<xsl:variable name="ans1">
<xsl:call-template name="math:max">
<xsl:with-param name="nodes" select="document('')/*/test:test[@num=1]/test:
data"/>
<xsl:with-param name="max" select="1 div 0"/>
</xsl:call-template>
</xsl:variable>
<xsl:if test="$ans1 != Infinity">
<xsl:message>
math:max Infinity Test FAILED [<xsl:value-of select="$ans1"/>]
</xsl:message>
</xsl:if>
<!-- Test with -Infinity -->
<xsl:variable name="ans2">
<xsl:call-template name="math:max">
<xsl:with-param name="nodes" select="document('')/*/test:test[@num=1]/test:
data"/>
<xsl:with-param name="max" select="-1 div 0"/>
</xsl:call-template>
</xsl:variable>
<xsl:if test="$ans2 != document('')/*/test:test[@num=1]/@ans">
<xsl:message>
math:max -Infinity Test FAILED [<xsl:value-of select="$ans2"/>]
</xsl:message>
</xsl:if>
</xsl:template>
<test:test num="1" ans="9" xmlns="http://www.ora.com/XSLTCookbook/test">
<data>9</data>
<data>8</data>
<data>7</data>
<data>6</data>
<data>5</data>
<data>4</data>
<data>3</data>
<data>2</data>
<data>1</data>
</test:test>
<test:test num="2" ans="1" xmlns="http://www.ora.com/XSLTCookbook/test">
<data>1</data>
</test:test>
<test:test num="3" ans="1" xmlns="http://www.ora.com/XSLTCookbook/test">
<data>-1</data>
<data>1</data>
</test:test>
<test:test num="4" ans="0" xmlns="http://www.ora.com/XSLTCookbook/test">
<data>0</data>
<data>0</data>
</test:test>
<test:test num="5" ans="NaN" xmlns="http://www.ora.com/XSLTCookbook/test">
<data>foo</data>
<data>1</data>
</test:test>
<test:test num="6" ans="NaN" xmlns="http://www.ora.com/XSLTCookbook/test">
<data>1</data>
<data>foo</data>
</test:test>
<test:test num="7" ans="NaN" xmlns="http://www.ora.com/XSLTCookbook/test">
</test:test>
</xsl:stylesheet>
Discussion
The xsl:stylesheet element has an optional
attribute called id. This attribute idenfities
stylesheets that are embedded in larger documents. However, here the
ID is used for testing purposes. You want to package test code with
the stylesheet but make reasonably certain that this test code does
not interfere with the normal usage of the stylesheet. Do this by
creating a template that will match only when the stylesheet
processes itself:
<xsl:template match="/xsl:stylesheet[@id='math:math.max'] |
xsl:include[@href='math.max.xslt']">
This explains the
/xsl:stylesheet[@id='math:math.max'], but what
about the
xsl:include[@href='math.max.xslt']
part? To see the value of this, here is a stylesheet that packages
all your math utilities into a single file for easy inclusion. You
would like an easy way to test the entire package too:
<!-- math.xslt -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:math="http://exslt.org/math"
extension-element-prefixes="math" id="math:math">
<xsl:include href="math.abs.xslt"/>
<xsl:include href="math.constant.xslt"/>
<xsl:include href="math.exp.xslt"/>
<xsl:include href="math.highest.xslt"/>
<xsl:include href="math.log.xslt"/>
<xsl:include href="math.lowest.xslt"/>
<xsl:include href="math.max.xslt"/>
<xsl:include href="math.min.xslt"/>
<xsl:include href="math.power.xslt"/>
<xsl:include href="math.sqrt.xslt"/>
<!--TEST CODE -->
<xsl:template match="xsl:stylesheet[@id='math:math'] | xsl:include[@href='math.
xslt']">
<xsl:message>
TESTING math
</xsl:message>
<xsl:for-each select="document('')/*/xsl:include">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="xsl:include" priority="-10">
<xsl:message>
WARNING: <xsl:value-of select="@href"/> has no test code.
</xsl:message>
</xsl:template>
</xsl:stylesheet>
Here you see that the test code for a package simply loops over all
its xsl:include
elements and applies templates to them. This step causes each
included stylesheet tests to be exercised due to the aforementioned
xsl:include[@href='filename']
part of the match.
Notice the template <xsl:template match="xsl:include"
priority="-10">. This template causes emission of a
warning if an included file does not contain test code. This concept
is important for quality control, since forgetting to create tests is
easy.
If you object to packaging the tests with
the
actual code, you can achieve the same effect by creating separate
test files for each utility. In this case, there is no need to use
the id attribute of the stylesheet; simply match
against the root:
<!-- math.max.test.xslt-->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:math="http://www.exslt.org/math" exclude-result-prefixes="math"
xmlns:test="http://www.ora.com/XSLTCookbook/test">
<xsl:include href="../math/math.max.xslt"/>
<!-- TEST CODE: DO NOT REMOVE! -->
<xsl:template match="/ | xsl:include[@href='math.max.test.xslt']">
<xsl:message>
TESTING math.max
</xsl:message>
<xsl:for-each select="document('')/*/test:test">
<xsl:variable name="ans">
<xsl:call-template name="math:max">
<xsl:with-param name="nodes" select="test:data"/>
</xsl:call-template>
</xsl:variable>
<xsl:if test="$ans != @ans">
<xsl:message>
math:max TEST <xsl:value-of select="@num"/> FAILED [<xsl:value-of
select="$ans"/>]
</xsl:message>
</xsl:if>
</xsl:for-each>
<!-- ... Same as math.max.xslt above ... -->
</xsl:stylesheet>
You would then create separate test packages:
<!-- math.test.xslt -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:math="http://exslt.org/math"
extension-element-prefixes="math">
<xsl:include href="math.max.test.xslt"/>
<xsl:include href="math.min.test.xslt"/>
<!-- ... Same as math.xslt, above ... -->
</xsl:stylesheet>
If you separate your tests in this way, be sure to ship the test code
with the actual implementations. Doing so allows your clients to
verify tests for themselves. The test code also doubles as an example
of
how to
use the templates.
|