XQuery/XUnit Annotations

= Motivation = You want an easy way to add unit tests to each function of a module without having to create separate test files for each function.

= Method = We will use XQuery 3.0 annotations to add assertions to each of our functions. Note that the following examples are based on the eXist 2.0 release and are dependent on the XQuery 3.0 functions.

How it Works
Lets say you have a simple XQuery function that returns the string "Hello World!" like this:

You want to add a single assertion to the function using an XQuery 3.0 annotation. The XQuery 3.0 Working Draft indicates that annotations always start with the "%" symbol and occur after the keyword "declare" and before the keyword "function" like this:

Note that the %test:assertEquals('Hello World!') has been added to the original function. The "assertion" is just a function that must return true for the test to pass. The output of the function is automatically compared with the input string and in this case the assertEquals function returns true if the strings match exactly.

Note that this syntax is different from other languages like Java that put assertions in comments before each method.

You now have enough information it the function to run a simple unit test.

How to Invoke the Tests
To invoke the tests you use the following test suite function: This function will return an XUnit test results file.

If you want to test all the functions in a module you can use the util:list-functions($module-uri) function which returns a sequence of function objects for the module.

Sample Test Driver

XQuery Annotations
Annotations are structures that are added to each function. They are not compiled by the XQuery compiler but can be used by other XQuery scripts to create a database of actual test files to be run. This places the tests directly in the context of each XQuery function. You can use the util:inspect-function function to get a list of all the annotations in a function.

XUnit Test Result Format
XUnit is the family name given to a general class of testing frameworks that have become widely known amongst software developers. The name is a derivation of JUnit, the first of these to be widely known.

XUnit also includes a is an standardized XML test results output format that is used by many continious integration tools such as Jenkins, Hudson, CruiseControl, Team City and other tools.

The following is a sample XUnit result showing a passing test: The test name is any string that describes the test. You can add the name of the function to help you know what function is being tested. You can also add a classname attribute to group the test results together in test results reports. The time element is the execution time of the unit test.

A failing test is indicated as follows:

See https://gist.github.com/959290 for a sample XML Schema of these files.

Example Annotation
The following is a module with a single function. This function takes no input parameters and always returns the string 'a'.

We can then write a test driver that will test this function using the assertion:

The following annotation example shows how you would add an assertion to test a simple function that adds two decimal numbers. Note that you must place the percent sign between the declare and function.

This example would return a successful test.

This example would return a test failure record.

Assertions
An assertion is any statement that returns true if a test has passed and false if a test fails. Assertions form the basis of XQuery annotations.

Simple Assertions
Many XQuery tests can be run based on a single line of code without the need for pre-conditions or post-conditions to the test.

For example

Checks the result returned from the function for equality with the provided data. If the function returned a string sequence with more than one item, a space is added between the items (as above).

An assertion can take any sequence of literals, including strings and numbers, but not XML fragments. However, assertEquals does inspect the return type of the function. If it returned an XML fragment, it will be normalized (ignorable whitespace stripped). The annotation string is parsed into XML as well and the two values are compared using deep-equals. Thus, if your function returns XML, you provide a string to %test:assertEquals and it is parsed into XML as well.

- returns true if the result is an empty string.

- returns true if the result exists.

- returns true if the result is true

- returns true if the result is false

- Excepts the function to fail with an error. If an error code is given (optional), it should be contained in the error message or the test will fail.

- This is the most powerful assertion. It checks the result against an arbitrary XPath expression. The assertion is true if
 * 1) the XPath results to a boolean true or
 * 2) the XPath returns a non-empty sequence

The output of the called function is always contained in variable $result.

Passing Parameters to Assertions
The following example uses the test:args annotation to pass to input parameters to the unit test. In this example the first parameter is the $queryStr="fenny snake" and the second parameter is the $mode="all". This example then has two separate assertions. The first must have at least two results with the class of scene and the section assertion show that the result must contain a table with a row and a table data element with a class of "hi".

In this way many assertions may be created with these two parameters.

Using Multiple Arguments
You can use the %test:args function as many times as you want to create multiple tests for a single function. You just repeat the %test:args function over and over with a separate set of assert statements after each %test:args. Here is the pattern:

Helper Annotations
In addition to the basic assertions there are a few additional annotations that can be used. You can describe a test using the following:

Can be used to supply an additional description for the test. This will be used for the @name attribute in the xUnit element.

There are also special functions which will be called before and after any other test in the module is evaluated. Use this to load documents, configure indexing and the like.