Skip to content
Sandro Cirulli edited this page Aug 7, 2016 · 8 revisions

Introduction

This page demonstrates how to get started with XSpec. It runs through a tutorial that tests an XSLT stylesheet with XSpec. The examples are in the tutorial directory.

Requirements

Make sure that XSpec is installed correctly by running the help message at the end of the installation process:

Your First Test Suite

The tutorial directory contains an sample of XSLT stylesheet escape-for-regex.xslt to test and its sample XSpec test suite escape-for-regex.xspec.

Running a Test Suite

Navigate to your XSpec directory (e.g. ~/xspec/for Mac/Linux or C:\xspec\ for Windows) and run the XSpec test suite with this command:

For Mac/Linux:

bin/xspec.sh tutorial/escape-for-regex.xspec

For Windows:

bin\xspec.bat tutorial\escape-for-regex.xspec

The output in the shell should be similar to this:

Creating XSpec Directory at tutorial/xspec...

Creating Test Stylesheet...

Running Tests...
Testing with SAXON HE 9.7.0.1
No escaping
Must not be escaped at all
Test simple patterns
..When encountering parentheses
escape them.
..When encountering a whitespace character class
escape the backslash
result should have one more character than source
When processing a list of phrases
All phrase elements should remain
Strings should be escaped and status attributes should be added
      FAILED

Formatting Report...
passed: 5 / pending: 0 / failed: 1 / total: 6
Report available at tutorial/xspec/escape-for-regex-result.html
Done.

Open the HTML report at tutorial/xspec/escape-for-regex-result.html with your favourite web browser to see the test results. The HTML report should look like this:

XSpec HTML Report

As you can see, one of the tests has failed. Note that you invoke the XSpec script against the test, not the stylesheet. The XSLT stylesheet filename to test is specified at the beginning of the XSpec test on the stylesheet attribute.

<x:description xmlns:x="http://www.jenitennison.com/xslt/xspec"
               xmlns:functx="http://www.functx.com"
               stylesheet="escape-for-regex.xslt">

If everything worked, there should be a new xspec subdirectory in ~/xspec/tutorial (or C:\xspec\tutorial). It contains three files generated by XSpec:

  • escape-for-regex.xsl: the XSpec test in XSLT form.
  • escape-for-regex-result.xml: the test results in XML format.
  • escape-for-regex.html: the human-readable HTML test report. Most of the time, this is the only file out of these three that you'll care about.

Dive into the XSpec tests

Open the fully commented escape-for-regex.xslt and escape-for-regex.xspec in your favourite XML editor to follow the rest of the tutorial.

The XSLT stylesheet contains a single function and two templates:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:functx="http://www.functx.com" xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="xs" version="2.0">

    <xsl:function name="functx:escape-for-regex" as="xs:string">
        <xsl:param name="arg" as="xs:string?"/>

        <xsl:sequence
            select=" 
            replace($arg,
            '(\.|\[|\]|\\|\||\-|\^|\$|\?|\*|\+|\{|\}|\(|\))','\\$1')
            "
        />
    </xsl:function>

    <xsl:template match="phrases">
        <phrases>
            <xsl:apply-templates select="phrase"/>
        </phrases>
    </xsl:template>

    <xsl:template match="phrase">
        <xsl:variable name="escaped-text" select="functx:escape-for-regex(.)"/>
        <phrase status="{if (. = $escaped-text) then 'changed' else 'same'}">
            <xsl:value-of select="functx:escape-for-regex(.)"/>
        </phrase>
    </xsl:template>

</xsl:stylesheet>

The XSpec file has three top-level scenarios, which are contained in x:scenario elements. The first two test the function, and the third tests the matching templates.

The first scenario is simple, it just calls the function functx:escape-for-regex() with the given parameter value and directly compares its result to our expected result. A function is tested by using an x:call element, passing it a parameter using an x:param element. Our expectations about how the functions should behave are captured by x:expect elements.

<x:scenario label="No escaping">
      <!-- call the function with the string 'Hello' -->
      <x:call function="functx:escape-for-regex">
         <x:param select="'Hello'"/>
      </x:call>
      <!-- check the result -->
      <x:expect label="Must not be escaped at all" select="'Hello'"/>
   </x:scenario>

Scenarios can be nested and the second top-level scenario is an example as it contains two scenarios that test two aspects of the function functx:escape-for-regex(). The first nested scenario is an equality test, just like the first-top level scenario. The second nested scenario is more interesting as it tests the output of a function against an XPath expression given in @test. Note that we can have as many expectations as we like within a scenario.

<x:scenario label="Test simple patterns">

      <!-- first sub-scenario -->
      <x:scenario label="When encountering parentheses">
         <!-- call the function -->
         <x:call function="functx:escape-for-regex">
            <x:param name="arg" select="'(Hello)'"/>
         </x:call>
         <!-- check the result -->
         <x:expect label="escape them." select="'\(Hello\)'"/>
      </x:scenario>

      <!-- second sub-scenario -->
      <x:scenario label="When encountering a whitespace character class">
         <!-- call the function with another parameter -->
         <x:call function="functx:escape-for-regex">
            <x:param name="arg" select="'\sHello'"/>
         </x:call>
         <!-- check the result -->
         <x:expect label="escape the backslash" select="'\\sHello'"/>
         <!-- we can have several checks on the same result -->
         <x:expect label="result should have one more character than source"
                   test="string-length(.) = 8"/>
      </x:scenario>

   </x:scenario>

The third top-level scenario tests the templates in the transform. An x:context element contains a nodeset we want to apply templates to. It has two expectations: the first a simple XPath test and the second demonstrates how we can directly compare a nodeset to the output by including the nodeset as descendants of an x:expect element.

<x:scenario label="When processing a list of phrases">
      <!-- apply template rules to this element -->
      <x:context>
         <phrases>
            <phrase>Hello!</phrase>
            <phrase>Goodbye!</phrase>
            <phrase>(So long!)</phrase>
         </phrases>
      </x:context>
      <!-- check the result -->
      <x:expect label="All phrase elements should remain"
                test="count(phrases/phrase) = 3"/>
      <x:expect label="Strings should be escaped and status attributes should be added">
         <phrases>
            <phrase status="same">Hello!</phrase>
            <phrase status="same">Goodbye!</phrase>
            <phrase status="changed">\(So long!\)</phrase>
            </phrases>
      </x:expect>
   </x:scenario>

Although it's not shown here, both x:context and x:call elements have optional @href and @select attributes, which can be a very powerful way to define tests. @href is used to point to an external document, and @select is used to select certain nodes to test against.

One of the tests failed, so let's fix it. When a test fails in XSpec, the report shows both the actual result and the expected result side-by-side, with differences highlighted in red. From this report, we can see that the failed scenario is labelled "When processing a list of phrases" and the specific expectation that failed is labelled "Strings should be escaped and status attributes should be added." The difference is that the @status attributes contain the wrong values. Change line 35 of the transform in the XSLT stylesheet to read:

<phrase status="{ if (. = $escaped-text) then 'same' else 'changed' }">

Save the XSLT stylesheet and re-run the XSpec test. All tests should be green:

XSpec HTML Report

Tips

There are many ways to integrate XSpec into your workflow. Here are a few tips:

  • Add the XSpec executable directory to your system path. For example, if XSpec is installed in ~/xspec, add this to your ~/.bashrc:

    PATH=~/xspec/bin:$PATH

    In Windows:

    SET PATH=%PATH%;C:\xspec\bin
    
  • By default, XSpec stores its result documents in the subdirectory xspec of the folder where the particular stylesheet that you are testing resides. You may want to change the variable TEST_DIR to the system temporary folder:

    export TEST_DIR=/tmp/xspec

    In Windows:

    SET TEST_DIR=%TEMP%\xspec
    

    This works well when you don't want to clutter up your project folders with XSpec results.

  • XSpec is integrated in the Oxygen XML editor.