XForms forms can be incredibly complex and I have been thinking of an XForms unit test environment for a while. Before I start coding here are some of my thoughts on the topic…
XForms test cases should be native
A common way of testing XForms application is to use a generic purpose web testing tool such as Selenium.
The benefits of such tools is that you can test exactly what is displayed in the browser and simulate user actions. It’s downside is that the tests are expressed in browser related terms rather than using XForms concepts. To write these tests you need to know how XForms will be transformed into HTML and this transformation depends on the XForms implementation being used and may vary between versions.
By contrast a native XForms test environment would allow to express the tests using XForms concepts such as binds, controls and events.
Principles
The basic idea is to describe a test suite and to add actions to the form to test so that the tests are executed by the XForms engine.
Let’s say we have the following form which is a slightly adapted version of the traditional XForms « hello world »:
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms">
<head>
<title>Hello World in XForms</title>
<xf:model id="model">
<xf:instance id="instance" xmlns="">
<data>
<PersonGivenName></PersonGivenName>
<Greetings></Greetings>
</data>
</xf:instance>
<xf:bind id="greetings" nodeset="/data/Greetings"
calculate="concat('Hello ', ../PersonGivenName, '. We hope you like XForms!')"/>
</xf:model>
</head>
<body>
<p>Type your first name in the input box. <br /> If you are running XForms, the output should be displayed in
the output area.</p>
<xf:input ref="PersonGivenName" incremental="true">
<xf:label>Please enter your first name: </xf:label>
</xf:input>
<br />
<xf:output value="Greetings">
<xf:label>Output: </xf:label>
</xf:output>
</body>
</html>
To test that the greeting are what you’d be expecting you would enter a value in the name and check the greetings.
This test can be expressed as:
<?xml version="1.0" encoding="UTF-8"?>
<suite xmlns:xh="http://www.w3.org/1999/xhtml" xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<form src="hello-world.xhtml"/>
<!-- The test cases -->
<case id="test-greetings">
<action>
<setvalue ref="instance('instance')/PersonGivenName">Eric</setvalue>
</action>
<assertEqual>
<actual ref="instance('instance')/Greetings"/>
<expected>Hello Eric. We hope you like XForms!</expected>
<message>The greetings should be the concatenation of "Hello ", the given name and ". We hope you like
XForms!".</message>
</assertEqual>
</case>
</suite>
The generation of the augmented form which will perform the tests can be done in XSLT.
To implement these tests in XForms, we can add an instance initialized with the description of the test suite and updated by XForms actions to reflect the result of the tests:
<xf:instance id="xfu:instance">
<suite xmlns="">
<case id="test-greetings">
<action id="d8e9">
<setvalue ref="instance('instance')/PersonGivenName" id="d8e11">Eric</setvalue>
</action>
<assertEqual id="d8e15" passed="">
<actual ref="instance('instance')/Greetings" id="d8e17"></actual>
<expected id="d8e19">Hello Eric. We hope you like XForms!</expected>
<message id="d8e22">The greetings should be the concatenation of "Hello ", the given name
and ". We hope you like XForms!".</message>
</assertEqual>
</case>
</suite>
</xf:instance>
Note how the transformation has added id attributes to the elements of this instance which did not already carry one. These ids will be helpful while writing the actions.
In this version I have chosen to encapsulate each action and test in its own XForms action and use custom events to trigger these actions one after the other. Also note the xf:recalculate which is necessary to make sure that the greetings are recalculated after we’ve changed the name.
Now that xfu:instance contains the result of the tests, it could be saved on a server and can also be displayed using XForms controls such as:
Note that this delay causes additional client/server exchanges for server based implementations such as Orbeon Forms and the workaround should be used specifically with XSLTForms.
What you test and what you see
In our test we check that an instance value is what we expect, but can we be sure that this instance value is displayed? This is pretty obvious in our example, but what if there was a typo in the control:
More generally speaking there is a lack of standard introspection functions, not only for controls but also on instances and testing if an instance node is relevant, readonly, required or valid is not possible without using extension functions. Some (but not all) these needs are covered by EXForms functions but their support by actual implementations has to be checked.
Conclusion
Despite some limitations there seems to be a nice potential in a native XForms test framework and of course your feedback is most welcome.
Ever modified an XML schema? Ever broken something while fixing a bug or adding a new feature? As withany piece of engineering, the more complex a schema is, the harder it is to maintain. In other domains, unit tests dramatically reduce the number of regressions and thus provide a kind of safety net for maintainers. Can we learn from these techniques and adapt them to XML schema languages? In this workshop session, we develop a schema using unit test techniques, to illustrate their benefits in this domain.
The workshop is run as an exchange between a customer (played by Tommie Usdin) and a schema expert (played by Eric van der Vlist).
The customer, needs a schema for her to list XML application, is puzzled by the « test first programming » technique imposed by the schema expert.
At the end of the day (or workshop), will she be converted to this well known agile or extreme programming technique adapted to the development of XML schemas?
Step 1: Getting started
Hi Eric, can you help me to write a schema?
—Customer
Hi Tommie, yes, sure, what will the schema be about?
—Expert
I need a vocabulary for my todo lists, with todo ite…
—Customer
OK, you’ve told me enough, let’s get started
—Expert (interrupting his customer)
Get started? but I haven’t told you anything about it!
—Customer
Right, but it’s never too soon to write tests when you do test first programming!
—Expert
Note
Test first programming (also called test driven development) developers create test case (usually unit test cases) before implementing a function. The test suite is run, code is written based on the result of these tests and the test suite and code are updated untill all the tests pass.
The vocabulary used to define these test cases has been inspired by the SUT (XML Schema Unit Test) project. It’s a simple vocabulary (composed of only three different element) allowing to pack several XML instances together with the outcome validation result. It uses conventions that you’ll discover during the course of this workshop.
Figure 1. Test results
Test results
Note
The test suite is run using a simple Orbeon Forms application. The rendering relies on Orbeon Forms XForms’ implementation while the test suite is run using an Orbeon Forms’ XPLpipeline.
Step 2: Adding a schema
You see, you can already write todo lists!
—Expert
Hold on, we don’t have any schema!
—Customer
That’s true, but you don’t have to write a schema to write XML documents.
—Expert
I know, but you’re here to write a schema! Furthermore right now we accept anything. I don’t want
to have XML documents with anything as a root element!
—Customer
That’s a good reason to write a schema but before that we need to add a test in our suite
Now that we’ve updated the test suite, we run it again.
—Expert
Figure 4. Test results
Test results
You see? We do already support list title elements!
—Expert
Sure, but I don’t want to accept any content in my todo list. And the title element should be mandatory. And it should not be empty by have at least one character!
This is the first example with non top level tf:case elements. To understand how this works, we must look in more detail to the algorithm used by the framework to split a test suite into instances. The algorithm consists in two steps:
Loop over each tf:case element
Suppression of the tf:caseelements and of the top level elements which arenot ancestors of the current tf:case element.
This description may look complex, but the result is a rather intuitive way to define sub-trees that are common to several test cases.
Now that we’ve updated the test suite, we run it again.
split-tests.xslis the XSLT transformation that splits a test suite into top levelelement test cases. This transformation has no dependence on Orbeon Forms and can be manuallyrun against a test suite.
run-test.xpl is the XPL pipeline that runs a test case.
list-suites.xpl is the XPL pipeline that gives the list avaible test cases.
view.xhtml is the XForms application that displays the results.
copy the orbeon-resources/ directory under /WEB-INF/resources/apps/in yourorbeon webapp directory
or, alternatively, copy the tefisc/ directory wherever you want, edit web.xml.savto replace<param-value>/home/vdv/projects/tefisc/orbeon-resources</param-value>by the location of this directory on your filesystem, replace /WEB-INF/web.xml by this file and restart your application server.