跳转到内容

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 脚本用来创建一个实际测试文件的数据库,这些文件将被运行。这将测试直接放置在每个 XQuery 函数的上下文中。您可以使用 util:inspect-function() 函数来获取函数中所有注解的列表。

XUnit 测试结果格式

[编辑 | 编辑源代码]

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

  1. XPath 的结果为布尔值 true,或者
  2. 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

参考资料

[编辑 | 编辑源代码]
华夏公益教科书