Test XProc steps

Publishing workflows often connect several tasks: read files, transform XML, add metadata and create a package. XProc can connect those steps. Since the 4.0 series, XSpec can also test custom XProc 3 steps.

Here we test an XProc step itself. That is different from using XProc merely to run tests for another language. Our example stays small: give an XML document an additional wrapper element.

Prepare a step with an option

The step receives a document through its input port and returns the wrapped document through its output port. By default, the new element is named package. An option allows a different name.

examples/07-xproc/wrap.xpl

<?xml version="1.0" encoding="UTF-8"?> 
<p:declare-step xmlns:p="http://www.w3.org/ns/xproc"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:ex="urn:example:pipeline" version="3.0" type="ex:wrap" name="wrap">
  <p:input port="source" primary="true" content-types="application/xml"/>
  <p:output port="result" primary="true"/>
  <p:option name="wrapper" as="xs:QName" select="QName('', 'package')"/>
  <p:wrap-sequence wrapper="{$wrapper}"/>
</p:declare-step>

type="ex:wrap" gives the step its callable type name. The XSpec file points to the definition with xproc. In x:call, step names the step, x:input provides the input and x:option sets the option. The expectation uses port to select the output to compare.

examples/07-xproc/wrap.xspec

<?xml version="1.0" encoding="UTF-8"?>
<x:description xmlns:x="http://www.jenitennison.com/xslt/xspec"
  xmlns:ex="urn:example:pipeline" xproc="wrap.xpl">
  <x:scenario label="Use the default wrapper">
    <x:call step="ex:wrap">
      <x:input port="source"><article><title>XML Testing</title></article></x:input>
    </x:call>
    <x:expect label="Return a package" port="result">
      <package><article><title>XML Testing</title></article></package>
    </x:expect>
  </x:scenario>
  <x:scenario label="Use a custom wrapper">
    <x:call step="ex:wrap">
      <x:input port="source"><article><title>XML Testing</title></article></x:input>
      <x:option name="wrapper" select="QName('', 'publication')"/>
    </x:call>
    <x:expect label="Return a publication" port="result">
      <publication><article><title>XML Testing</title></article></publication>
    </x:expect>
  </x:scenario>
</x:description>

The first test uses the default package wrapper. The second selects publication. The option is passed as a QName. Both tests matter: a default-only test would miss an ignored option, while a custom-only test would miss a broken default.

Set up XML Calabash

Saxon alone is not enough for this example. A compatible XML Calabash 3 distribution is also needed. The XSpec 4.0 release documentation reports tests with XML Calabash 3.0.47. The local setup still needs its own run, with the installed version recorded.

Install the complete distribution, including its libraries, rather than copying just one JAR. XSpec needs the Calabash classpath for this run.

The package runner reads it from CALABASH_CP. Adjust these example paths to the actual installation:

export CALABASH_CP="/path/to/xmlcalabash/xmlcalabash-app.jar:/path/to/xmlcalabash/lib/*"
python scripts/run-tests.py --only xproc

On Windows, use a semicolon as the classpath separator:

$env:CALABASH_CP = 'C:\tools\xmlcalabash\xmlcalabash-app.jar;C:\tools\xmlcalabash\lib\*'
python scripts/run-tests.py --only xproc

The application JAR's exact name depends on the installed distribution. For the XProc run, the runner passes this classpath as SAXON_CP. A direct XSpec invocation uses -p. If the runtime is missing, the suite is blocked rather than marked as a successful skip. Features shown as upcoming in version 4.1 are not required by this 4.0.3 example.

From one step to a complete workflow

The wrapper checks input, output and an option. It deliberately avoids network access and file writes, keeping the purpose of the test easy to see.

A production pipeline brings more questions: can it reach every input file? Can it write to the output directory? What happens when an external service fails? Those need additional tests with suitable files and test services. A passing wrapper test does not answer them.

For a larger workflow, test individual conversion steps with small documents, then test their combination with representative packages. That keeps a local transformation bug separate from a deployment or network problem.

<< back                                                                                                             next >>