XQuery/XUnit 注解
您希望有一种简单的方法,可以为模块的每个函数添加单元测试,而无需为每个函数创建单独的测试文件。
我们将使用 XQuery 3.0 注解为我们的每个函数添加断言。请注意,以下示例基于 eXist 2.0 版本,并且依赖于 XQuery 3.0 函数。
假设您有一个简单的 XQuery 函数,它返回字符串“Hello World!”,如下所示
declare function myfunct:hello() as xs:string {
'Hello World!'
};
您希望使用 XQuery 3.0 注解向该函数添加一个断言。XQuery 3.0 工作草案指出,注解总是以“%”符号开头,并出现在关键字“declare”之后和关键字“function”之前,如下所示
declare %test:assertEquals('Hello World!') function myfunct:hello() as xs:string {
'Hello World!'
};
请注意,%test:assertEquals('Hello World!') 已添加到原始函数中。“断言”只是一个必须返回 true 才能通过测试的函数。该函数的输出将自动与输入字符串进行比较,在本例中,assertEquals 函数在字符串完全匹配时返回 true。
请注意,此语法与 Java 等其他语言不同,Java 将断言放在每个方法之前的注释中。
现在您已经拥有足够的函数信息来运行简单的单元测试。
要调用测试,请使用以下测试套件函数
test:suite($function* as function) as node()*
该函数将返回一个 XUnit 测试结果文件。
如果您想测试模块中的所有函数,可以使用 util:list-functions($module-uri) 函数,该函数返回模块的函数对象的序列。
示例测试驱动程序
xquery version "3.0";
(: the following line must be added to each of the modules that include unit tests :)
import module namespace test="http://exist-db.org/xquery/xqsuite" at "resource:org/exist/xquery/lib/xqsuite/xqsuite.xql";
(: import the module that contains your test-annotated functions :)
import module namespace mt="http://exist-db.org/xquery/test/my-module" at "mymodule.xqm";
(: the test:suite() function will run all the test-annotated functions in the module whose namespace URI you provide :)
test:suite(util:list-functions("http://exist-db.org/xquery/test/my-module"))
注解是添加到每个函数的结构。它们不会被 XQuery 编译器编译,但可以被其他 XQuery 脚本用来创建一个实际测试文件的数据库,这些文件将被运行。这将测试直接放置在每个 XQuery 函数的上下文中。您可以使用 util:inspect-function() 函数来获取函数中所有注解的列表。
XUnit 是赋予一类通用测试框架的家族名称,这些框架在软件开发人员中广为人知。该名称源于 JUnit,它是第一个广为人知的框架。
XUnit 还包含一个标准化的 XML 测试结果输出格式,该格式被许多持续集成工具(如 Jenkins、Hudson、CruiseControl、Team City 等)使用。
以下是一个显示通过测试的 XUnit 示例结果
<testcase name="prefix:function-name Test #1" classname="my-module" time=".01"/
测试名称是描述测试的任何字符串。您可以添加函数名称以帮助您了解正在测试的函数。您还可以添加 classname 属性将测试结果分组到测试结果报告中。time 元素是单元测试的执行时间。
失败测试指示如下
<testcase name="prefix:function-name Test #2" classname="my-module" time=".01">
<failure message="prefix:function-name Test #2 has failed" type="failure-code-type-1"/>
</testcase>
有关这些文件的示例 XML 架构,请参阅 https://gist.github.com/959290。
以下是包含单个函数的模块。该函数不接受输入参数,始终返回字符串“a”。
xquery version "3.0";
module namespace mymodule="http://exist-db.org/xquery/test/myfunctions";
import module namespace test="http://exist-db.org/xquery/xqsuite" at "resource:org/exist/xquery/lib/xqsuite/xqsuite.xql";
declare %test:assertEquals("123") function mymodule:a() as xs:string {
'123'
};
然后,我们可以编写一个测试驱动程序,使用断言测试此函数
xquery version "3.0";
(: the following line must be added to each of the modules that include unit tests :)
import module namespace test="http://exist-db.org/xquery/xqsuite" at "resource:org/exist/xquery/lib/xqsuite/xqsuite.xql";
(: import the module that contains your test-annotated functions :)
import module namespace myfunct="http://exist-db.org/xquery/test/myfunctions" at "mymodule.xqm";
(: the test:suite() function will run all the test-annotated functions in the module whose namespace URI you provide :)
test:suite(util:list-functions("http://exist-db.org/xquery/test/myfunctions"))
以下注解示例展示了如何向一个简单的函数添加断言,该函数将两个小数相加。请注意,您必须将百分号放置在 declare 和 function 之间。
xquery version "3.0";
import module namespace test="http://exist-db.org/xquery/xqsuite" at "resource:org/exist/xquery/lib/xqsuite/xqsuite.xql";
declare %test:assertEquals(70) function test:a1() {
20 + 50
};
此示例将返回成功的测试。
declare %test:assertEquals(70) function test:a2() {
21 + 50
};
此示例将返回测试失败记录。
断言是任何在测试通过时返回 true,在测试失败时返回 false 的语句。断言构成了 XQuery 注解的基础。
许多 XQuery 测试可以基于单行代码运行,而无需测试的先决条件或后置条件。
例如 %test:assertEquals("Heinz Roland Uschi Verona")
检查函数返回的结果是否与提供的数据相等。如果函数返回的字符串序列包含多个项目,则会在项目之间添加空格(如上所示)。
断言可以接受任何文字序列,包括字符串和数字,但不包括 XML 片段。但是,assertEquals 会检查函数的返回值类型。如果它返回了 XML 片段,它将被规范化(忽略的空格将被剥离)。注解字符串也将被解析为 XML,并将使用 deep-equals() 比较这两个值。因此,如果您的函数返回 XML,则向 %test:assertEquals 提供一个字符串,它也会被解析为 XML。
%test:assertEmpty
- 如果结果为空字符串,则返回 true。
%test:assertExists
- 如果结果存在,则返回 true。
%test:assertTrue
- 如果结果为 true,则返回 true
%test:assertFalse
- 如果结果为 false,则返回 true
%test:assertError("error code")
- 预计该函数会失败并出现错误。如果给出了错误代码(可选),则该错误代码应包含在错误消息中,否则测试将失败。
%test:assertXPath("count($result) = 8")
- 这是最强大的断言。它根据任意 XPath 表达式检查结果。如果断言为 true
- XPath 的结果为布尔值 true,或者
- XPath 返回非空序列
被调用函数的输出始终包含在变量 $result 中。
以下示例使用 test:args 注解将输入参数传递给单元测试。
declare
%test:args('"fenny snake"', "all")
%test:assertXPath("count($result[@class='scene']) = 2")
%test:assertXPath("$result/table/tr/td[@class='hi'] = 'fenny'")
function t:show-hits($queryStr as xs:string?, $mode as xs:string) {
let $result := shakes:do-query($queryStr, $mode)
return
shakes:show-hits((), (), $result)
};
在本例中,第一个参数是 $queryStr="fenny snake",第二个参数是 $mode="all"。然后,此示例有两个单独的断言。第一个断言必须至少具有两个类为 scene 的结果,而第二个断言则显示结果必须包含一个包含行的表,以及一个类为“hi”的表格数据元素。
通过这种方式,可以使用这两个参数创建许多断言。
您可以根据需要使用 %test:args() 函数多次,为单个函数创建多个测试。您只需重复使用 %test:args() 函数,并在每个 %test:args() 之后添加一组单独的断言语句。以下模式
declare
(: test 1 :)
%test:args('a')
%test:assert()
%test:assert()
(: test 2 :)
%test:args('b')
%test:assert()
%test:assert()
(: test 3 :)
%test:args('b')
%test:assert()
%test:assert()
function prefix:myfunction($in as xs:string) as xs:string {
...
};
除了基本的断言之外,还可以使用一些额外的注释。您可以使用以下内容来描述测试
%test:name("description")
可用于为测试提供额外的描述。这将用于 xUnit <test> 元素中的 @name 属性。
还有一些特殊函数,它们将在评估模块中的任何其他测试之前和之后被调用。使用它来加载文档、配置索引等。
%test:setUp
%test:tearDown