Testing and CI for XML

Testing and CI for XML Pipelines

Stylesheets and queries are code and deserve unit tests. XSpec 4.1.2 (github.com/xspec/xspec (https://github.com/xspec/xspec 125 ), MIT; 22 September 2026) tests XSLT, XQuery and Schematron with scenarios of input and expected output. These tests pin down the repricing stylesheet of Schema-Aware XSLT, including the rounding rule finance asked for:

reprice.xspec: three scenarios for reprice.xslXML
<x:description xmlns:x="http://www.jenitennison.com/xslt/xspec" stylesheet="reprice.xsl">
  <x:scenario label="A 10% rise">
    <x:context><price currency="USD">10.00</price></x:context>
    <x:expect label="keeps the currency and rounds to cents">
      <price currency="USD">11.00</price></x:expect>
  </x:scenario>
  <x:scenario label="Half-even rounding of 18.75 plus 10%">
    <x:context><price currency="USD">18.75</price></x:context>
    <x:expect label="is 20.63, as finance expects">
      <price currency="USD">20.63</price></x:expect>
  </x:scenario>
  <x:scenario label="Other elements">
    <x:context><title>Salt and Saffron</title></x:context>
    <x:expect label="are copied unchanged"><title>Salt and Saffron</title></x:expect>
  </x:scenario>
</x:description>
Running XSpec with Saxon-HE; -e makes a failed test fail the buildShell
export SAXON_CP=~/xml-tools/saxon/saxon-he-12.10.jar
~/xml-tools/xspec-4.1.2/bin/xspec.sh -e reprice.xspec > xspec.log 2>&1; echo "exit code $?"
sed -n '/^A 10%/,/^passed/p' xspec.log
Output
exit code 1
A 10% rise
keeps the currency and rounds to cents
Half-even rounding of 18.75 plus 10%
is 20.63, as finance expects
      FAILED
Other elements
are copied unchanged
Formatting Report...
passed: 2 / pending: 0 / failed: 1 / total: 3

The failing test is the useful one: format-number() rounds half to even (20.62), so the stylesheet needs round(..., 2) before formatting if finance wants 20.63. In CI, run XSpec, the validators of Schema Validators against sample feeds (including broken ones that must fail) and well-formedness checks on generated files. A GitHub Actions 29 workflow along these lines (not run here; run-xspec.sh would set SAXON_CP as above):

xml-ci.yml: validation and XSpec on every pushYAML
# .github/workflows/xml-ci.yml: validate feeds and run XSpec on every push
name: xml-ci
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-java@v6
        with: { distribution: temurin, java-version: "21" }
      - run: sudo apt-get install -y libxml2-utils jing
      - run: xmllint --noout --schema catalog.xsd feeds/*.xml
      - run: jing booknest.rng feeds/*.xml
      - run: "! xmllint --noout --schema catalog.xsd tests/bad/*.xml"   # must fail
      - run: ./ci/run-xspec.sh -e tests/*.xspec

Keep golden output files under version control as well, so a diff shows every change in behaviour.