Code Reuse in XSpec

How to Use x:like

Amanda Galtman
6 min readJul 27, 2023

Do you repeat yourself a lot, in an effort to make your XSpec tests thorough? Maybe you have several similar code situations that produce the same results, and you paste the same <x:expect> elements in multiple scenarios. Then, when maintaining the tests, you scrutinize (or diff) the assertions, to confirm that they’re identical. Alternatively, maybe the similar contexts produce nearly the same results, so you paste and then modify content across scenarios.

The repetition and near-repetition can make your XSpec tests longer and harder to maintain. In this topic, we’ll see how to avoid repeating yourself in XSpec. Related topics will show how to avoid almost-repeating yourself.

Example Showing Repetition

To start, let’s look at a simple XSLT template that creates some HTML markup, using an XML <title> element that can come from one of two places in the source XML. The <title> element can reside either directly inside <topic> or one level deeper, inside <info>.

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>

Tip: To download code appearing on this post, see xspectacles on GitHub.

Let’s write an XSpec test that checks the overall HTML structure and then focuses on the two HTML elements that use the source title. (To reduce distraction, these tests don’t bother to exercise the last <xsl:apply-templates> in the XSLT template.)

Figure 2. Scenario for One Title Position

<x:scenario label="Title outside info">
<x:context>
<topic xmlns="http://docbook.org/ns/docbook">
<title>title content</title>
</topic>
</x:context>
<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>Topic: 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>

Let’s do the same checks for the other place where the source title can occur. To do that, we can copy the scenario above, change the scenario label, and add <info> tags around the context’s title.

Figure 3. Scenario for the Other Title Position

<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: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>Topic: 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>

Do You Like Code Reuse?

Copying the scenario and modifying the context slightly was easy and effective, but can we do better?

Yes, we can! Using an XSpec element named <x:like>, we can reuse code and reduce repetition.

Key Fact: Wherever your test uses the <x:like> element and specifies the label of some <x:scenario> element, the XSpec compiler replaces the <x:like> element with that scenario’s content.

Here’s a before/after schematic that shows where we’re headed.

Left shows code repeated in two scenarios; right shows code in shared scenario with two references

So, if you’d like to reuse more code, keep reading.

How to Implement Reuse

In this example, the three <x:expect> elements are identical between the two test scenarios. Let’s cut those <x:expect> elements and substitute <x:like>:

Figure 4. Scenario Covering Both Title Positions

<x:scenario label="Tests for topic template (with reuse)">
<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>

The <x:like label="Check structure, title, and h1"/> elements that appear in both sub-scenarios are placeholders for the three reusable <x:expect> elements. Where did the <x:expect> elements go? We cut them; now we need to paste them into a scenario whose label equals the <x:like> element’s label, Check structure, title, and h1.

Figure 5. Scenario with Reusable Expectations

<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>Topic: 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>

You might notice two things about the scenario above:

  • It looks incomplete. It doesn’t call any templates or functions, and it doesn’t provide any context to trigger a template rule. All the child elements in this scenario are <x:expect>.
  • The scenario has an attribute, shared="yes".

These observations are related to each other. When XSpec sees shared="yes" on a scenario, it doesn’t attempt to run the scenario. Use this attribute when you want to reuse fragments of XSpec code that don’t form a self-contained test. Without that attribute, XSpec would notice that the scenario is incomplete and issue an error:

Figure 6. Error from Omitting shared="yes" Attribute on Incomplete Scenario

ERROR in x:scenario ('Check structure, title, and h1'):
There are x:expect but no x:call or x:context has been given

Variations and Coding Style Choices

Depending on your coding style, you can vary the implementation of your XSpec code reuse.

Locating the Source

The shared scenario is the single source (“one truth”) for the code you are reusing. You can place the shared scenario anywhere the XSpec compiler can find it, including:

  • As a top-level scenario right above, or right below, a scenario that uses <x:like> to refer to the shared scenario.
  • At the beginning or end of the XSpec file. You can group multiple reusable scenarios as sibling top-level scenarios.
  • In a separate file that you reference using <x:import href="...path to separate file...">. This approach is useful if you want to reuse XSpec code among multiple importing XSpec files. If the separate file has only shared scenarios, running the file doesn’t cause an error, and the resulting test report has no substantive content. The substantive content appears in the test report for the XSpec file that imports the shared scenarios.

Providing the Label

In the <x:like> element, you can express the label using the label attribute (as shown earlier) or <x:label> child element. The latter would look like this:

<x:like>
<x:label>Check structure, title, and h1</x:label>
</x:like>

It’s OK to use the attribute in one place and the element in another place, as long as the text content matches between <x:like> and <x:scenario>.

Indicating Reusability in the Label

You can make the label indicate more visibly or more emphatically that it is a reusable fragment of XSpec code rather than a self-contained test. You might find this tip useful if the shared="yes" attribute seems easy to overlook when scanning the test code. The label of a shared scenario does not appear in the XSpec report, if that affects your decision about verbiage. For instance, you can change the labels in the shared scenario and all <x:like> references to it, like this:

<x:scenario shared="yes" label="REUSABLE: Check structure, title, and h1">
<x:like label="REUSABLE: Check structure, title, and h1"/>

Using the shared Attribute

In the shared attribute, you can choose among the following synonyms: shared="yes", shared="true", shared="1".

Non-shared scenarios typically omit the shared attribute. If you want to emphasize that a scenario is not shared, you can add that attribute, choosing among the following synonyms: shared="no", shared="false", shared="0".

Caveat for Maven Users

The Maven plug-in from https://github.com/xspec/xspec-maven-plugin-1 has an issue where Maven reports success even when one of the tests failed. A workaround is to send the Maven output to a log file and search the log file for FAIL in all-caps.

The Maven plug-in from https://github.com/nkutsche/xspec-maven-plugin seems not to have this issue in the first place.

Key Takeaways

  • When you find yourself pasting identical XSpec code, such as <x:expect> or <x:variable> elements in multiple scenarios or sub-scenarios, think about reusing that code instead.
  • Place the XSpec elements you want to reuse in their own scenario, typically having attribute shared="yes". Then, wherever you want to use that code, specify that scenario’s label in an <x:like> element.

Code is downloadable from xspectacles on GitHub, in the src/code-reuse 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.