Adaptable Code Reuse in XSpec, Part 1

Value Templates

Amanda Galtman
6 min readAug 1, 2023

In the Code Reuse in XSpec: How to Use x:like topic, we saw how to use <x:like> to bring an exact copy of XSpec code from a scenario into multiple other XSpec scenarios. But sometimes, what you need are slight variants of code instead of exact copies. In other words, you copy, paste, and modify code instead of merely copying and pasting it.

In this two-part series, we’ll see how to avoid almost-repeating yourself in XSpec, by making code reusable in an adaptable way. The shared XSpec scenario is like a “Hello, my name is” tag that everyone at a party can pass around because the name section uses a variable.

Hello, my name is…{$string}

Here in Part 1, the adaptability lets you vary text content and attribute values. Part 2 shows how to vary nodes and other data types. In both cases, key pieces are <x:like> and <x:variable>.

Example Showing Near-Repetition

Using the same XSLT code as in the earlier topic (code repeated here for convenience), we’ll focus now on the $head-prefix parameter.

Figure 1. XSLT Code to Test

<xsl:template match="topic" as="element(h:html)">
<xsl:param name="head-prefix" select="'Topic: '" as="xs:string"/>
<xsl:variable name="title">
<xsl:apply-templates select="(title | info/title)[1]"/>
</xsl:variable>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>
<xsl:sequence select="concat($head-prefix, $title)"/>
</title>
</head>
<body>
<h1>
<xsl:sequence select="$title"/>
</h1>
<xsl:apply-templates select="*[not(self::title or self::info)]"/>
</body>
</html>
</xsl:template>

Here is a test scenario that checks that $head-prefix influences the HTML head title as expected.

Figure 2. Scenario to Check Head Title Prefix

<x:scenario label="Nondefault prefix for head title">
<x:context>
<x:param name="head-prefix" select="'Custom Prefix: '"/>
<topic xmlns="http://docbook.org/ns/docbook">
<title>title content</title>
</topic>
</x:context>
<x:expect label="head title contains custom-prefixed title text"
test="$x:result/h:head/h:title">
<title>Custom Prefix: title content</title>
</x:expect>
</x:scenario>

The <x:expect> element looks awfully similar to the following reusable one from Code Reuse in XSpec: How to Use x:like, which has a hard-coded default prefix:

<x:expect label="head title contains prefixed title text"
test="$x:result/h:head/h:title">
<title>Topic: title content</title>
</x:expect>

Can we still use <x:like> to avoid repetition? Yes, but first we need to tweak the reusable code to make it adaptable for different prefixes.

Using x:variable to Provide Adaptability

The <x:variable> element lets us define XSpec variables. They're not related to variables in the underlying XSLT code that we're testing. Here's our plan for making reusable code more adaptable:

  1. Set up a namespace for XSpec variables
  2. Create variables to store the default prefix and custom prefix
  3. Make the reusable code respond to whichever variable we specify per scenario

Step 1: Declare Namespace for XSpec Variables

Step 1 is namespace bookkeeping. A good practice with XSpec variables is to put them in a distinct namespace, so they won’t conflict with global variables or global parameters in the code you’re testing. To that end, we add a namespace declaration, xmlns:xv="urn:x-xspectacles:xspec:variables", to the <x:description> element in the XSpec file. We'll use xv as the prefix for all XSpec variables in the file.

Figure 3. Declaring xv Prefix and Namespace for Our XSpec Variables

<x:description
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://www.w3.org/1999/xhtml"
xmlns:x="http://www.jenitennison.com/xslt/xspec"
xmlns:xv="urn:x-xspectacles:xspec:variables"
stylesheet="code-reuse-adaptable-part1.xsl">
...
</x:description>

Step 2: Declare Variables

Next, we define a variable named xv:test-head-prefix in two spots:

  • In the outer scenario, the variable’s value is the default prefix.
<x:variable name="xv:test-head-prefix" select="'Topic: '"/>
  • In the inner scenario that uses a custom prefix, the variable’s value is the custom prefix. This value overrides the definition from the outer scenario.
<x:variable name="xv:test-head-prefix" select="'Custom Prefix: '"/>

The outer scenario with the variable definitions looks like the following. Notice that the last sub-scenario uses the variable in the attribute select="$xv:test-head-prefix" when passing a parameter to the template we're testing.

Figure 4. Scenario Defining Default Prefix and Custom Override

<x:scenario label="Tests for topic template (with reuse)">
<x:variable name="xv:test-head-prefix" select="'Topic: '"/>
<x:scenario label="Title outside info">
<x:context>
<topic xmlns="http://docbook.org/ns/docbook">
<title>title content</title>
</topic>
</x:context>
<x:like label="Check structure, title, and h1"/>
</x:scenario>
<x:scenario label="Title in info">
<x:context>
<topic xmlns="http://docbook.org/ns/docbook">
<info>
<title>title content</title>
</info>
</topic>
</x:context>
<x:like label="Check structure, title, and h1"/>
</x:scenario>
<x:scenario label="Nondefault prefix for head title">
<x:variable name="xv:test-head-prefix" select="'Custom Prefix: '"/>
<x:context>
<x:param name="head-prefix" select="$xv:test-head-prefix"/>
<topic xmlns="http://docbook.org/ns/docbook">
<title>title content</title>
</topic>
</x:context>
<x:like label="Check structure, title, and h1"/>
</x:scenario>
</x:scenario>

Step 3: Making the Reusable Code Adaptable

The next step is to make the reusable code adapt to different values of the XSpec variable. We want to express that the HTML title element we’re expecting to see starts with the XSpec variable value, whatever that value happens to be. Because the variable’s value is a string, we can take advantage of text value templates to express the varying expected value.

Figure 5. An Adaptable Expectation

<title x:expand-text="1">{$xv:test-head-prefix}title content</title>

Aside: XSpec mimics XSLT’s support for text value templates, using an attribute whose local name is expand-text. When the attribute's parent element is not an XSpec element, the attribute must be in the XSpec namespace. That's why the line above says x:expand-text="1" instead of expand-text="1".

Here’s the whole shared scenario, including that adaptable <title> element plus a comment that says the calling scenario (or an ancestor) is responsible for defining the xv:test-head-prefix variable.

Figure 6. Scenario with Reusable, Adaptable Expectations

<!-- Scenario containing <x:like> must have $xv:test-head-prefix --> 
<x:scenario shared="yes" label="Check structure, title, and h1">
<x:expect label="Check overall structure">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>...</title>
</head>
<body>
<h1>...</h1>
</body>
</html>
</x:expect>
<x:expect label="head title contains prefixed title text"
test="$x:result/h:head/h:title">
<title x:expand-text="1">{$xv:test-head-prefix}title content</title>
</x:expect>
<x:expect label="h1 contains unprefixed title text"
test="$x:result/h:body/h:h1">
<h1>title content</h1>
</x:expect>
</x:scenario>

Why is the shared scenario allowed to refer to $xv:test-head-prefix without defining it? It's because all the content of the shared scenario will substitute for some <x:like> element located in a spot where $xv:test-head-prefix is defined. In the XSpec system, the substitution happens before the variable's value is needed.

The result of these three steps is a test that, for each situation in the three sub-scenarios, checks all three expectations with the appropriate prefix value. Here’s how that looks in the main section of the HTML test report.

XSpec report showing three assertions for each of three scenarios

Variation Using Attribute Value Templates

As a minor variation on the text value template idea above, you can use XSpec variables in attribute value templates. For instance, suppose the XSLT template used some logic to compute a class attribute for the HTML <h1> element. To test that logic in different XSpec scenarios, we could make each scenario define a string variable, xv:h1-class, with the correct class value for that scenario, and then express the expected <h1> element using an attribute value template like class="{$xv:h1-class}".

Figure 7. An Adaptable Expectation Using an Attribute Value Template

<!-- Scenario containing <x:like> must have $xv:h1-class --> 
<x:expect label="h1 has correct class and unprefixed title text"
test="$x:result/h:body/h:h1">
<h1 class="{$xv:h1-class}">title content</title>
</x:expect>

Variation Foreshadowing an XPath Approach

As an alternative to an attribute value template in the <h1> class situation, you can use an approach that relies on XPath expressions. In the following code, $xv:h1-class is the value of the first <x:expect> element's select attribute.

Figure 8. An Adaptable Expectation Using x:expect/@select

<!-- Scenario containing <x:like> must have $xv:h1-class -->
<x:expect label="h1 has correct class"
test="$x:result/h:body/h:h1/@class/string()"
select="$xv:h1-class"/>
<x:expect label="h1 contains unprefixed title text"
test="$x:result/h:body/h:h1">
<h1 class="...">title content</h1>
</x:expect>

In effect, we’re saying that in the XSLT result’s h:body/h:h1/@class attribute, the string value must equal the value of the XSpec variable, $xv:h1-class. The first <x:expect> element checks the class attribute but not the text content, while the second <x:expect> element does the opposite. Part 2 of this series will explain this alternative technique and give further examples of combining <x:like>, <x:variable>, and XPath expressions.

Key Takeaways

  • You can adaptably reuse some XSpec code that you would otherwise paste and then modify. We looked at cases where the modifications occur in an expected element’s text content and attribute value.
  • Place the XSpec elements you want to reuse in their own shared scenario. Wherever you want to use that code, specify that scenario's label in <x:like>. Finally, introduce variables that capture your desired modifications: define the variables in the scenarios that use <x:like> and refer to the variables in text value templates or attribute value templates in the shared scenario.

Code is downloadable from xspectacles on GitHub, in the src/code-reuse-adaptable-part1 folder.

--

--

Amanda Galtman

I'm an XML software developer, a maintainer of the XSpec infrastructure, and a contributor to a couple of other open source projects.