Do I need to change my coding practice?

39 views
Skip to first unread message

Patricia Piolon

unread,
Sep 9, 2020, 4:22:41 PM9/9/20
to XSpec
Hi,

I'm maintaining quite a big XSLT project and thought it time to start with unit testing my code, as I'm starting to lose sight of what some changes do to other parts of the project.

I'm running into major problems with XSpec though, and I wonder if it's to do with me and my coding 'style' (I'm self-taught) or with XSpec. I'm assuming it's me, by the way. It's very hard to find anything that resembles good practice guides for XSLT online so I've always felt like I'm on my own.

I'll describe my most pressing problems below:

1. Testing variables
So I have some global variables that construct nodesets, which are then used for various tasks throughout the transformation. AFAIK, XSpec provides no way to test these, so am I supposed to refactor so that the xsl:variable declaration calls a named template, which *is* testable? Is this good practice, or just a workaround to be able to successfully add XSpec to the mix?

2. Calling named templates from matched templates
As far as I understand there is no way in XSpec to provide a context for a named template, but in the transformation proper, the named template *does* of course have a context, passed to it from the matched template. Here I assume I need to pass the context to the named template with a parameter. I've never done that before. Again: is this just good practice in general, or is it a workaround? Does it impact execution times at all if you do this for hundreds of templates?

I realize that this isn't a 'pure' XSpec question, so please forgive me for that, but I've been frustrated to no end and this whole project has kind of made me doubt my xslt coding abilities.

Thanks for your help!
Patricia

AirQuick

unread,
Sep 10, 2020, 12:50:03 AM9/10/20
to xspec...@googlegroups.com
> 1. Testing variables
> So I have some global variables that construct nodesets, which are then
> used for various tasks throughout the transformation. AFAIK, XSpec
> provides no way to test these, so am I supposed to refactor so that the
> xsl:variable declaration calls a named template, which *is* testable?
> Is this good practice, or just a workaround to be able to successfully
> add XSpec to the mix?

If you mean to verify a global variable value, you can put it in @test. For example,

<x:scenario label="When applying templates to a dummy empty context">
<x:context/>

<x:expect label="$my-global-var should be a foo element containing a bar element"
test="$my-global-var">
<foo>
<bar/>
</foo>
</x:expect>

<x:expect label="$my-global-var should have one child node"
test="$my-global-var/child::node() => count()"
select="1" />
</x:scenario>

x:context in this example is just a dummy context only for making the scenario valid. Any other elements will do as long as it makes the scenario valid. For example,

<x:scenario label="When fn:false() or whatever function is called">
<x:call function="false" />

<x:expect label="$my-global-var should be a foo element containing a bar element"
test="$my-global-var">
<foo>
<bar/>
</foo>
</x:expect>
</x:scenario>

---

If you mean to test your transformation with various global variables, you can override a global variable with a global x:variable ( https://github.com/xspec/xspec/wiki/Writing-Scenarios#xspec-variables ). So if you have a stylesheet

<xsl:stylesheet ...>
<xsl:variable name="my-global-var" as="element(e)">
<e/>
</xsl:variable>

<xsl:template ...>
...
</xsl:stylesheet>

and an XSpec

<x:description ...>
<x:variable name="my-global-var" as="element(f)">
<f/>
</x:variable>

<x:scenario ...>
...
</x:description>

then $my-global-var throughout the test scenarios is not <e/> but <f/>.

---

If your global variable depends on the source document, you may have to refactor your stylesheet because XSpec does not set the source document at all. For example, with this stylesheet

<xsl:stylesheet ...>
<xsl:variable name="my-global-var" select="/foo/bar" />
...
</xsl:stylesheet>

this XSpec will crash

<x:description ...>
<x:scenario ...>
<x:expect label="$my-global-var should be a bar element"
test="$my-global-var">
<bar/>
</x:expect>
</x:scenario>
</x:description>

because select="/foo/bar" in the stylesheet is evaluated with no context (no source document) while running XSpec. In this case, you may want to refactor the stylesheet with a global variable (or a global parameter) which by default points to the source document

<xsl:stylesheet ...>
<xsl:variable name="source-document" as="document-node()" select="." />
<xsl:variable name="my-global-var" select="$source-document/foo/bar" />
...
</xsl:stylesheet>

and override it in the XSpec to provide the "source" document for testing purposes

<x:description ...>
<x:variable name="source-document" select="/">
<foo>
<bar/>
</foo>
</x:variable>
<x:scenario label="With a source document containing a foo element containing a bar element">
<x:expect label="$my-global-var should be a bar element"
test="$my-global-var">
<bar/>
</x:expect>
</x:scenario>
</x:description>


> 2. Calling named templates from matched templates
> As far as I understand there is no way in XSpec to provide a context for
> a named template, but in the transformation proper, the named template
> *does* of course have a context, passed to it from the matched template.
> Here I assume I need to pass the context to the named template with a
> parameter. I've never done that before. Again: is this just good
> practice in general, or is it a workaround? Does it impact execution
> times at all if you do this for hundreds of templates?

You can provide a context for a named template.
I updated Wiki "Named Template Scenarios" section ( https://github.com/xspec/xspec/wiki/Writing-Scenarios#named-template-scenarios ) to clarify that.

--
AirQuick

Patricia Piolon

unread,
Sep 10, 2020, 7:12:59 AM9/10/20
to XSpec
Thanks for the great answers, AirQuick, that's given me heart and I've progressed a lot.

Could you explain though why in the $source-document override (the very last snippet in your reply to q1) we need to provide an actual nodeset (<foo><bar/></foo>) in the x:variable instead of being able to use @href to point to the test document used in e.g. the context of other scenarios? (At least I did; maybe it's my bad and I'm doing it wrong: <x:variable href="path/to/testsource.xml" select="/"/>; it produces an empty result while the content of this doc is the same, i.e. <foo><bar/></foo> [path is correct since it works in other scenarios' contexts])

AirQuick

unread,
Sep 10, 2020, 12:45:05 PM9/10/20
to xspec...@googlegroups.com
Of course @href works too. So, with these files

stylesheet.xsl
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="source-document" as="document-node()" select="."
/>
<xsl:variable name="my-global-var" select="$source-document/foo/bar"
/>
</xsl:stylesheet>

source.xml
<foo>
<bar />
</foo>

this XSpec scenario returns Success:

<x:description stylesheet="stylesheet.xsl"
xmlns:x="http://www.jenitennison.com/xslt/xspec">
<x:variable name="source-document" href="source.xml" />
<x:scenario label="With a source document containing a foo element
containing a bar element">
<x:context />
<x:expect label="$my-global-var should be a bar element"
test="$my-global-var">
<bar />
</x:expect>
</x:scenario>
</x:description>


> --
> You received this message because you are subscribed to the Google
> Groups "XSpec" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to xspec-users...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/xspec-users/b4a3526f-3936-41da-b757-e695574a6cean%40googlegroups.com.

Patricia Piolon

unread,
Sep 21, 2020, 6:47:00 AM9/21/20
to xspec...@googlegroups.com
Thanks again AirQuick, and apologies for the late reply, I was on holiday for a week.

Now that I'm back (we should really intersperse our weeks of work with holiday weeks, a fresh brain is a great thing), I've discovered why this didn't work for me:

<x:variable name="source-document" href="../source/test.xml" select="/"/>

--> the XML of note for this test is actually included in test.xml through a series of xi:include elements, which seem to not be resolved when running the test with ANT in oXygen Developer 22.1. I tried running the XProc version and that did work. So I guess I'll be taking this to the oXygen forum.

Cheers,
-- Patricia


AirQuick

unread,
Sep 21, 2020, 8:55:13 AM9/21/20
to xspec...@googlegroups.com
XInclude is not enabled by default. To enable it in @href documents, you
need to set "-xi:on"
(https://www.saxonica.com/documentation/index.html#!using-xsl/commandline)
in Ant "saxon.custom.options" property
(https://github.com/xspec/xspec/wiki/Running-with-Ant#useful-properties):

1. Duplicate [Run XSpec Test] transformation scenario.
2. In [Parameters] tab of the duplicated scenario, set "-xi:on" in
[saxon.custom.options].

--
AirQuick
> https://groups.google.com/d/msgid/xspec-users/CAAPdqZ9zXpwsWMKJCFXSMu4WhLZR_zCcymWpcnLDnoo5rcrcPw%40mail.gmail.com.

Patricia Piolon

unread,
Sep 21, 2020, 9:08:16 AM9/21/20
to xspec...@googlegroups.com
Ah! I'm new to Ant, thanks for that :)
-- Patricia


Reply all
Reply to author
Forward
0 new messages