Difference between revisions of "Unit Module"
(33 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
This [[Module Library|XQuery Module]] contains annotations and functions for performing XQUnit tests. | This [[Module Library|XQuery Module]] contains annotations and functions for performing XQUnit tests. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
=Introduction= | =Introduction= | ||
− | The more complex a software application grows, the more error-prone it gets. This is why testing frameworks have been developed, which provide a standardized, | + | The more complex a software application grows, the more error-prone it gets. This is why testing frameworks have been developed, which provide a standardized, automated way of testing software. The [https://en.wikipedia.org/wiki/XUnit XUnit] frameworks (such as SUnit or JUnit) allow testing of atomic units of a program, such as single functions and algorithms. |
This module borrows heavily from the existing frameworks: it provides various annotations for testing XQuery functions. Unit functions are provided to assert the validity of arbitrary conditions expressed in XQuery and to raise errors whenever a condition is not satisfied. Some additional functions exist to run all unit tests of the current module or a set of specified library modules. | This module borrows heavily from the existing frameworks: it provides various annotations for testing XQuery functions. Unit functions are provided to assert the validity of arbitrary conditions expressed in XQuery and to raise errors whenever a condition is not satisfied. Some additional functions exist to run all unit tests of the current module or a set of specified library modules. | ||
Line 15: | Line 9: | ||
=Usage= | =Usage= | ||
− | Tests are | + | Tests are started via the {{Command|TEST}} command. It compiles all XQuery modules in a given file |
− | + | or directory and runs all functions that are annotated with {{Code|%unit:test}}. A test report is | |
− | functions that are annotated with {{Code|%unit:test}}. A test report is generated and returned, | + | generated and returned, which resembles the format returned by other xUnit testing frameworks, |
− | which resembles the format returned by other xUnit testing frameworks, such as the Maven | + | such as the Maven Surefire Plugin ([[#Result|see below]]). |
− | Surefire Plugin ([[#Result|see below]]). | ||
=Conventions= | =Conventions= | ||
− | All annotations, functions and errors in this module are assigned to the | + | All annotations, functions and errors in this module are assigned to the <code><nowiki>http://basex.org/modules/unit</nowiki></code> namespace, which is statically bound to the {{Code|unit}} prefix.<br/> |
=Annotations= | =Annotations= | ||
− | == | + | ==unit:test== |
{| width='100%' | {| width='100%' | ||
− | |- | + | |- valign="top" |
| width='120' | '''Syntax''' | | width='120' | '''Syntax''' | ||
− | |{{Code|%unit:test}}<br/>{{Code|%unit:test("expected", | + | |{{Code|%unit:test}}<br/>{{Code|%unit:test("expected", CODE)}} |
− | |- | + | |- valign="top" |
| '''Summary''' | | '''Summary''' | ||
− | |With this annotation, a function can be marked as unit test. It will be evaluated | + | |With this annotation, a function can be marked as unit test. It will be evaluated if a test report is created for the module in which this function is located.<br/><code>error</code> can be supplied as additional string argument. It is followed by <code>CODE</code>, which must be a valid [[XQuery 3.0#Expanded QNames|EQName]] string. If the function expression does not raise that error, the test will fail. |
+ | |- valign="top" | ||
+ | | '''Examples''' | ||
+ | | | ||
+ | * The following test will be successful, as it does nothing (and, hence, nothing wrong): | ||
+ | <pre lang='xquery'> | ||
+ | declare %unit:test function local:void() { () }; | ||
+ | </pre> | ||
+ | * The following test will be successful, as the function body will raise <code>err:XPTY0004</code>: | ||
+ | <pre lang='xquery'> | ||
+ | declare %unit:test('expected', "err:XPTY0004") function local:add() { | ||
+ | 123 + 'strings and integers cannot be added' | ||
+ | }; | ||
+ | </pre> | ||
|} | |} | ||
− | == | + | ==unit:before== |
+ | |||
{| width='100%' | {| width='100%' | ||
− | |- | + | |- valign="top" |
| width='120' | '''Syntax''' | | width='120' | '''Syntax''' | ||
− | |{{Code|%unit:before}} | + | |{{Code|%unit:before}}<br/>{{Code|%unit:before(FUNCTION)}} |
− | |- | + | |- valign="top" |
| '''Summary''' | | '''Summary''' | ||
− | |A function decorated with this annotation will be evaluated '''before each''' unit test. | + | |A function decorated with this annotation will be evaluated '''before each''' unit test as a separate transaction.<br/><code>FUNCTION</code> can be supplied as additional argument. It must be a valid [[XQuery 3.0#Expanded QNames|EQName]] string. If specified, the function will only be evaluated before a function with the given name is tested. This extension is e. g. helpful if the results of updates need to be tested. |
+ | |- valign="top" | ||
+ | | '''Examples''' | ||
+ | | | ||
+ | * The first function will be evaluated before the actual test: | ||
+ | <pre lang='xquery'> | ||
+ | declare %updating %unit:before("local:check") function local:before-check() { | ||
+ | db:create('test-db') | ||
+ | }; | ||
+ | declare %updating %unit:test function local:check() { | ||
+ | unit:assert(db:exists('test-db')) | ||
+ | }; | ||
+ | </pre> | ||
|} | |} | ||
− | == | + | ==unit:after== |
+ | |||
{| width='100%' | {| width='100%' | ||
− | |- | + | |- valign="top" |
| width='120' | '''Syntax''' | | width='120' | '''Syntax''' | ||
− | |{{Code|%unit:after}} | + | |{{Code|%unit:after}}<br/>{{Code|%unit:after(FUNCTION)}} |
− | |- | + | |- valign="top" |
| '''Summary''' | | '''Summary''' | ||
− | |A function decorated with this annotation will be evaluated '''after each''' unit test. | + | |A function decorated with this annotation will be evaluated '''after each''' unit test as a separate transaction.<br/><code>FUNCTION</code> can be supplied as additional argument. It must be a valid [[XQuery 3.0#Expanded QNames|EQName]] string. If specified, the function will only be evaluated after a function with the given name is tested. |
|} | |} | ||
− | == | + | ==unit:before-module== |
+ | |||
{| width='100%' | {| width='100%' | ||
− | |- | + | |- valign="top" |
| width='120' | '''Syntax''' | | width='120' | '''Syntax''' | ||
|{{Code|%unit:before-module}} | |{{Code|%unit:before-module}} | ||
− | |- | + | |- valign="top" |
| '''Summary''' | | '''Summary''' | ||
− | |If a function is decorated with this annotation, it will be evaluated '''before all''' unit tests in the current module. | + | |If a function is decorated with this annotation, it will be evaluated '''before all''' unit tests in the current module as a separate transaction. |
|} | |} | ||
− | == | + | ==unit:after-module== |
+ | |||
{| width='100%' | {| width='100%' | ||
− | |- | + | |- valign="top" |
| width='120' | '''Syntax''' | | width='120' | '''Syntax''' | ||
|{{Code|%unit:after-module}} | |{{Code|%unit:after-module}} | ||
− | |- | + | |- valign="top" |
| '''Summary''' | | '''Summary''' | ||
− | |If a function is decorated with this annotation, it will be evaluated '''after all''' unit tests in the current module. | + | |If a function is decorated with this annotation, it will be evaluated '''after all''' unit tests in the current module as a separate transaction. |
|} | |} | ||
− | == | + | ==unit:ignore== |
+ | |||
{| width='100%' | {| width='100%' | ||
− | |- | + | |- valign="top" |
| width='120' | '''Syntax''' | | width='120' | '''Syntax''' | ||
− | |{{Code|%unit:ignore}}<br/>{{Code|%unit:ignore( | + | |{{Code|%unit:ignore}}<br/>{{Code|%unit:ignore(MESSAGE)}} |
− | |- | + | |- valign="top" |
| '''Summary''' | | '''Summary''' | ||
|If a function is decorated with this annotation, it will temporarily be ignored by the test suite runner. | |If a function is decorated with this annotation, it will temporarily be ignored by the test suite runner. | ||
Line 89: | Line 112: | ||
=Functions= | =Functions= | ||
− | + | ==unit:assert== | |
− | |||
{| width='100%' | {| width='100%' | ||
− | |- | + | |- valign="top" |
− | | width='120' | ''' | + | | width='120' | '''Signature''' |
− | | | + | |<pre>unit:assert( |
− | |- | + | $test as item()*, |
+ | $info as item() := () | ||
+ | ) as empty-sequence()</pre> | ||
+ | |- valign="top" | ||
| '''Summary''' | | '''Summary''' | ||
|Asserts that the effective boolean value of the specified {{Code|$test}} is true and returns an empty sequence. Otherwise, raises an error. The ''effective boolean value'' of an expression can be explicitly computed by using the {{Code|fn:boolean}} function.<br/>The default failure message can be overridden with the {{Code|$info}} argument. | |Asserts that the effective boolean value of the specified {{Code|$test}} is true and returns an empty sequence. Otherwise, raises an error. The ''effective boolean value'' of an expression can be explicitly computed by using the {{Code|fn:boolean}} function.<br/>The default failure message can be overridden with the {{Code|$info}} argument. | ||
− | |- | + | |- valign="top" |
| '''Errors''' | | '''Errors''' | ||
− | |{{Error| | + | |{{Error|fail|#Errors}} the assertion failed, or an error was raised. |
|} | |} | ||
Line 107: | Line 132: | ||
{| width='100%' | {| width='100%' | ||
− | |- | + | |- valign="top" |
− | | width='120' | ''' | + | | width='120' | '''Signature''' |
− | | | + | |<pre>unit:assert-equals( |
− | |- | + | $returned as item()*, |
+ | $expected as item()*, | ||
+ | $info as item() := () | ||
+ | ) as empty-sequence()</pre> | ||
+ | |- valign="top" | ||
| '''Summary''' | | '''Summary''' | ||
− | |Asserts that the specified arguments are equal according to the rules of the {{Code|fn:deep- | + | |Asserts that the specified arguments are equal according to the rules of the [https://www.w3.org/TR/xpath-functions-31/#func-deep-equal {{Code|fn:deep-equal}} function]. Otherwise, raises an error.<br/>The default failure message can be overridden with the {{Code|$info}} argument. |
− | |- | + | |- valign="top" |
| '''Errors''' | | '''Errors''' | ||
− | |{{Error| | + | |{{Error|fail|#Errors}} the assertion failed, or an error was raised. |
|} | |} | ||
==unit:fail== | ==unit:fail== | ||
− | |||
− | |||
{| width='100%' | {| width='100%' | ||
− | |- | + | |- valign="top" |
− | | width='120' | ''' | + | | width='120' | '''Signature''' |
− | | | + | |<pre>unit:fail( |
− | |- | + | $info as item() := () |
+ | ) as empty-sequence()</pre> | ||
+ | |- valign="top" | ||
| '''Summary''' | | '''Summary''' | ||
|Raises a unit error. The default failure message can be overridden with the {{Code|$info}} argument. | |Raises a unit error. The default failure message can be overridden with the {{Code|$info}} argument. | ||
− | |- | + | |- valign="top" |
| '''Errors''' | | '''Errors''' | ||
− | |{{Error| | + | |{{Error|fail|#Errors}} default error raised by this function. |
|} | |} | ||
Line 140: | Line 169: | ||
==Query== | ==Query== | ||
− | <pre | + | <pre lang='xquery'> |
module namespace test = 'http://basex.org/modules/xqunit-tests'; | module namespace test = 'http://basex.org/modules/xqunit-tests'; | ||
Line 179: | Line 208: | ||
(:~ Function demonstrating an unexpected success. :) | (:~ Function demonstrating an unexpected success. :) | ||
− | declare %unit:test("expected", "FORG0001") function test:unexpected-success() { | + | declare %unit:test("expected", "err:FORG0001") function test:unexpected-success() { |
() | () | ||
}; | }; | ||
(:~ Function demonstrating an expected failure. :) | (:~ Function demonstrating an expected failure. :) | ||
− | declare %unit:test("expected", "FORG0001") function test:expected-failure() { | + | declare %unit:test("expected", "err:FORG0001") function test:expected-failure() { |
1 + <a/> | 1 + <a/> | ||
}; | }; | ||
Line 208: | Line 237: | ||
==Result== | ==Result== | ||
− | <pre | + | <pre lang="xml"> |
<testsuites time="PT0.256S"> | <testsuites time="PT0.256S"> | ||
<testsuite name="file:///C:/Users/user/Desktop/test.xqm" time="PT0.212S" tests="8" failures="4" errors="1" skipped="1"> | <testsuite name="file:///C:/Users/user/Desktop/test.xqm" time="PT0.212S" tests="8" failures="4" errors="1" skipped="1"> | ||
Line 250: | Line 279: | ||
! width="110"|Code | ! width="110"|Code | ||
|Description | |Description | ||
− | |- | + | |- valign="top" |
− | |{{Code| | + | |{{Code|fail}} |
|An assertion failed, or an error was raised. | |An assertion failed, or an error was raised. | ||
− | |- | + | |- valign="top" |
− | |{{Code| | + | |{{Code|no-args}} |
|A test function must have no arguments. | |A test function must have no arguments. | ||
− | |- | + | |- valign="top" |
− | |{{Code| | + | |{{Code|private}} |
− | |A test function | + | |A test function must not be private. |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
|} | |} | ||
=Changelog= | =Changelog= | ||
+ | |||
+ | ;Version 9.0 | ||
+ | |||
+ | * Updated: error codes updated; errors now use the module namespace | ||
+ | |||
+ | ;Version 8.0.2 | ||
+ | |||
+ | * Updated: (expected) errors are compared by QNames instead of local names (including namespaces). | ||
;Version 8.0 | ;Version 8.0 | ||
− | * Added: | + | |
+ | * Deleted: {{Code|UNIT0006}} (ignore results returned by functions). | ||
+ | * Added: {{Function||unit:fail}}, 0-argument signature. | ||
* Updated: the info argument of functions can now be an arbitrary item. | * Updated: the info argument of functions can now be an arbitrary item. | ||
* Updated: infos are now represented in an <code>info</code> child element. | * Updated: infos are now represented in an <code>info</code> child element. | ||
+ | * Updated: {{Function||unit:before}} and {{Function||unit:after}} can be extended by a filter argument. | ||
;Version 7.9 | ;Version 7.9 | ||
* Added: TEST command | * Added: TEST command | ||
− | * Removed: | + | * Removed: {{Function||unit:test}}, {{Function||unit:test-uris}} |
;Version 7.8 | ;Version 7.8 | ||
− | * Added: | + | * Added: {{Function||unit:assert-equals}} |
* Updated: enhanced test report output | * Updated: enhanced test report output | ||
This module was introduced with Version 7.7. | This module was introduced with Version 7.7. | ||
− | |||
− |
Latest revision as of 16:19, 7 December 2023
This XQuery Module contains annotations and functions for performing XQUnit tests.
Contents
Introduction[edit]
The more complex a software application grows, the more error-prone it gets. This is why testing frameworks have been developed, which provide a standardized, automated way of testing software. The XUnit frameworks (such as SUnit or JUnit) allow testing of atomic units of a program, such as single functions and algorithms.
This module borrows heavily from the existing frameworks: it provides various annotations for testing XQuery functions. Unit functions are provided to assert the validity of arbitrary conditions expressed in XQuery and to raise errors whenever a condition is not satisfied. Some additional functions exist to run all unit tests of the current module or a set of specified library modules.
Usage[edit]
Tests are started via the TEST
command. It compiles all XQuery modules in a given file
or directory and runs all functions that are annotated with %unit:test
. A test report is
generated and returned, which resembles the format returned by other xUnit testing frameworks,
such as the Maven Surefire Plugin (see below).
Conventions[edit]
All annotations, functions and errors in this module are assigned to the http://basex.org/modules/unit
namespace, which is statically bound to the unit
prefix.
Annotations[edit]
unit:test[edit]
Syntax | %unit:test %unit:test("expected", CODE)
|
Summary | With this annotation, a function can be marked as unit test. It will be evaluated if a test report is created for the module in which this function is located.error can be supplied as additional string argument. It is followed by CODE , which must be a valid EQName string. If the function expression does not raise that error, the test will fail.
|
Examples |
declare %unit:test function local:void() { () };
declare %unit:test('expected', "err:XPTY0004") function local:add() {
123 + 'strings and integers cannot be added'
};
|
unit:before[edit]
Syntax | %unit:before %unit:before(FUNCTION)
|
Summary | A function decorated with this annotation will be evaluated before each unit test as a separate transaction.FUNCTION can be supplied as additional argument. It must be a valid EQName string. If specified, the function will only be evaluated before a function with the given name is tested. This extension is e. g. helpful if the results of updates need to be tested.
|
Examples |
declare %updating %unit:before("local:check") function local:before-check() {
db:create('test-db')
};
declare %updating %unit:test function local:check() {
unit:assert(db:exists('test-db'))
};
|
unit:after[edit]
Syntax | %unit:after %unit:after(FUNCTION)
|
Summary | A function decorated with this annotation will be evaluated after each unit test as a separate transaction.FUNCTION can be supplied as additional argument. It must be a valid EQName string. If specified, the function will only be evaluated after a function with the given name is tested.
|
unit:before-module[edit]
Syntax | %unit:before-module
|
Summary | If a function is decorated with this annotation, it will be evaluated before all unit tests in the current module as a separate transaction. |
unit:after-module[edit]
Syntax | %unit:after-module
|
Summary | If a function is decorated with this annotation, it will be evaluated after all unit tests in the current module as a separate transaction. |
unit:ignore[edit]
Syntax | %unit:ignore %unit:ignore(MESSAGE)
|
Summary | If a function is decorated with this annotation, it will temporarily be ignored by the test suite runner. |
Functions[edit]
unit:assert[edit]
Signature | unit:assert( $test as item()*, $info as item() := () ) as empty-sequence() |
Summary | Asserts that the effective boolean value of the specified $test is true and returns an empty sequence. Otherwise, raises an error. The effective boolean value of an expression can be explicitly computed by using the fn:boolean function.The default failure message can be overridden with the $info argument.
|
Errors | fail : the assertion failed, or an error was raised.
|
unit:assert-equals[edit]
Signature | unit:assert-equals( $returned as item()*, $expected as item()*, $info as item() := () ) as empty-sequence() |
Summary | Asserts that the specified arguments are equal according to the rules of the fn:deep-equal function. Otherwise, raises an error.The default failure message can be overridden with the $info argument.
|
Errors | fail : the assertion failed, or an error was raised.
|
unit:fail[edit]
Signature | unit:fail( $info as item() := () ) as empty-sequence() |
Summary | Raises a unit error. The default failure message can be overridden with the $info argument.
|
Errors | fail : default error raised by this function.
|
Example[edit]
The following XQUnit module tests.xqm
contains all available unit annotations:
Query[edit]
module namespace test = 'http://basex.org/modules/xqunit-tests';
(:~ Initializing function, which is called once before all tests. :)
declare %unit:before-module function test:before-all-tests() {
()
};
(:~ Initializing function, which is called once after all tests. :)
declare %unit:after-module function test:after-all-tests() {
()
};
(:~ Initializing function, which is called before each test. :)
declare %unit:before function test:before() {
()
};
(:~ Initializing function, which is called after each test. :)
declare %unit:after function test:after() {
()
};
(:~ Function demonstrating a successful test. :)
declare %unit:test function test:assert-success() {
unit:assert(<a/>)
};
(:~ Function demonstrating a failure using unit:assert. :)
declare %unit:test function test:assert-failure() {
unit:assert((), 'Empty sequence.')
};
(:~ Function demonstrating a failure using unit:assert-equals. :)
declare %unit:test function test:assert-equals-failure() {
unit:assert-equals(4 + 5, 6)
};
(:~ Function demonstrating an unexpected success. :)
declare %unit:test("expected", "err:FORG0001") function test:unexpected-success() {
()
};
(:~ Function demonstrating an expected failure. :)
declare %unit:test("expected", "err:FORG0001") function test:expected-failure() {
1 + <a/>
};
(:~ Function demonstrating the creation of a failure. :)
declare %unit:test function test:failure() {
unit:fail("Failure!")
};
(:~ Function demonstrating an error. :)
declare %unit:test function test:error() {
1 + <a/>
};
(:~ Skipping a test. :)
declare %unit:test %unit:ignore("Skipped!") function test:skipped() {
()
};
By running TEST tests.xqm
, the following report will be generated (timings may differ):
Result[edit]
<testsuites time="PT0.256S">
<testsuite name="file:///C:/Users/user/Desktop/test.xqm" time="PT0.212S" tests="8" failures="4" errors="1" skipped="1">
<testcase name="assert-success" time="PT0.016S"/>
<testcase name="assert-failure" time="PT0.005S">
<failure line="30" column="15">
<info>Empty sequence.</info>
</failure>
</testcase>
<testcase name="assert-equals-failure" time="PT0.006S">
<failure line="35" column="22">
<returned item="1" type="xs:integer">9</returned>
<expected item="1" type="xs:integer">6</expected>
<info>Item 1: 6 expected, 9 returned.</info>
</failure>
</testcase>
<testcase name="unexpected-success" time="PT0.006S">
<failure>
<expected>FORG0001</expected>
</failure>
</testcase>
<testcase name="expected-failure" time="PT0.004S"/>
<testcase name="failure" time="PT0.004S">
<failure line="50" column="13">
<info>Failure!</info>
</failure>
</testcase>
<testcase name="error" time="PT0.004S">
<error line="55" column="6" type="FORG0001">
<info>Cannot cast to xs:double: "".</info>
</error>
</testcase>
<testcase name="skipped" skipped="Skipped!" time="PT0S"/>
</testsuite>
</testsuites>
Errors[edit]
Code | Description |
---|---|
fail
|
An assertion failed, or an error was raised. |
no-args
|
A test function must have no arguments. |
private
|
A test function must not be private. |
Changelog[edit]
- Version 9.0
- Updated: error codes updated; errors now use the module namespace
- Version 8.0.2
- Updated: (expected) errors are compared by QNames instead of local names (including namespaces).
- Version 8.0
- Deleted:
UNIT0006
(ignore results returned by functions). - Added:
unit:fail
, 0-argument signature. - Updated: the info argument of functions can now be an arbitrary item.
- Updated: infos are now represented in an
info
child element. - Updated:
unit:before
andunit:after
can be extended by a filter argument.
- Version 7.9
- Added: TEST command
- Removed:
unit:test
,unit:test-uris
- Version 7.8
- Added:
unit:assert-equals
- Updated: enhanced test report output
This module was introduced with Version 7.7.