动态库API
动态库API大部分情况和静态API类似. 例如, 报告关键字状态, 写日志, 以及返回值, 都是以完全相同的方式工作. 最重要的是, 和其它测试库相比, 导入动态库并使用其中的关键字,完全没有区别. 换句话说, 用户无需知道测试库是使用何种API实现的.
静态和动态库的唯一区别在于Robot Framework是如何发现库中实现了哪些关键字, 这些关键字的参数和文档信息, 以及这些关键字实际是怎样执行的.对于静态API, 这些都是通过反射机制(除了Java库的文档), 但是对于动态库, 需要通过几个特殊的方法来实现.
使用动态API的一个好处是可以更灵活地组织库. 使用静态API时, 所有的关键字必须在一个类或者模块中, 然而对动态API, 举例来说, 你可以将每个关键字都实现为一个单独的类. 这种场景对Python来说不那么重要, 因为Python本身的动态特性和多重继承机制已经有了足够的灵活性, 而且还可以使用 混合库API.
另一个使用动态API的主要用户场景是可以实现一个库, 这个库仅作为代理, 实际的库可能运行在其它进程, 甚至其它机器上. 这种代理库可以非常轻量, 因为关键字的名称和其它所有信息都是动态的, 所以每次当实际库中新增了关键字, 无需去更新代理即可使用.
本节介绍了动态API是如何在Robot Framework和动态库中工作的. 对Robot Framework来说, 它并不关心这些库实际是如何实现的(例如, run_keyword
方法是如何映射到相应的关键字). 实际上, 可能会有很多不同的方式.但是, 如果你是使用Java, 在实现自己的系统前不妨先参考下 JavalibCore. 这个可重用工具的集合支持多种关键字创建方式, 也许其中的某个机制正好符合你的需求.
获取关键字名称
动态库通过 get_keyword_names
方法来告知它实现了哪些关键字. 当使用Java时, 还可以使用这个方法的别名 getKeywordNames
, 这更符合Java的命名规范. 这个方法不能接受任何参数, 必须返回一个字符串的列表或数组, 这些字符串就是这个库实现的关键字的名称.
如果返回的关键字名称包含多个单词, 它们可以以空格或者下划线分隔, 或者使用驼峰法(camelCase)格式. 例如, ['first keyword', 'second keyword']
, ['firstkeyword', 'second_keyword']
, 和 ['firstKeyword', 'secondKeyword']
最后都会被映射为 _First Keyword and Second Keyword.
动态库必须总是包含这个方法, 如果没有, 或者调用该方法时发生了错误, 这个库将被视作静态库.
将方法标记为关键字
如果一个动态库中包含的方法既有那些最终作为关键字执行的, 也有那些私有的提供辅助功能的, 那么将这些关键字方法打上标记会使 get_keyword_names
的实现变得轻松.
装饰器 robot.api.deco.keyword
提供了简便的方式. 它为被装饰的方法创建了 robot_name
属性. 于是, 在 get_keyword_names
中, 可以通过检查每个方法的 robot_name
属性来创建关键字的列表. 关于该装饰器的更多内容请参考 使用自定义的关键字名称.
- from robot.api.deco import keyword
- class DynamicExample:
- def get_keyword_names(self):
- return [name for name in dir(self) if hasattr(getattr(self, name), 'robot_name')]
- def helper_method(self):
- # ...
- @keyword
- def keyword_method(self):
- # ...
运行关键字
动态库还要提供一个特殊的 run_keyword
(别名 runKeyword
) 方法用来执行关键字.当动态库中的关键字在测试用例中被调用时, Robot Framework 通过调用这个库的 run_keyword
方法使其运行. 这个方法接受2个或者3个参数, 第1个参数是一个字符串, 即要执行的关键字的名称, 这个名称的格式和 get_keyword_names
返回的一样. 第2个参数是一个参数的列表或者数组, 其中包含需要传递给该关键字的参数.
第3个可选参数是一个Python字典(dict)或者Java中的map, 其中是要传递给关键字的可能的 任意关键字参数 (即 kwargs
). 更多细节请参见 [动态库里的 kwargs](#free-keyword-arguments-with-dynamic-libraries) 章节.
当获取到关键字名称和参数后, 库可以按自己的方式自由地执行这个关键字, 但是它还是必须使用和静态库相同的机制来和框架通讯. 也就是说, 使用异常来报告状态, 通过写stdout或API来写日志, 在 run_keyword
方法中使用return语句来返回值.
每个动态库都必须包含 get_keyword_names
和 run_keyword
这两个方法, 其它的方法都是可选的.
下面的例子展示了一个用Python实现的动态库:
- class DynamicExample:
- def get_keyword_names(self):
- return ['first keyword', 'second keyword']
- def run_keyword(self, name, args):
- print "Running keyword '%s' with arguments %s." % (name, args)
获取关键字的参数
如果一个动态库仅仅实现了 getkeyword_names
和 run_keyword
这两个方法, Robot Framework无法获取任何关于关键字所需的参数信息. 例如, 上例中的 _First Keyword 和 Second Keyword 都可以接受任意数量的参数.现实中大部分关键字都预期接受一定个数的参数, 在这种情况下它们将不得不自己检查参数的个数, 所以, 这是个问题.
动态库通过 get_keyword_arguments
(别名 getKeywordArguments
) 方法来告知Robot Framework 关键字预期的参数. 这个方法接受关键字的名称作为参数, 返回一个字符串的列表或数组, 每个字符串表示该关键字可接受的参数.
和静态关键字类似, 动态关键字可以有任意数量的参数, 可以有缺省值, 还可以同时接受可变数量的参数以及任意关键字参数.下面的表格说明了使用怎样的语法来表示这些不同的参数类型. 注意, 示例中使用的是Python的列表, Java开发应该用Java的列表或字符串数组替代.
如果提供了 get_keyword_arguments
, Robot Framework自动计算出有多少位置参数, 以及是否支持自由命名参数. 如果传递了错误的参数给关键字, 会在 run_keyword
调用之前就提示错误.
通过该方法返回的实际的参数名称和缺省值也同样重要. 当使用 命名参数 语法调用时需要用到, Libdoc_ 在创建库文档是也需要用到它们.
如果没有 get_keyword_arguments
方法, 或者针对某个关键字调用该方法返回了 None
或 null
, 则该关键字的参数规范就是可以接受所有参数. 这个自动的参数规范是 [varargs, **kwargs]
或者 [
varargs]
, 取决于 run_keyword
是否包含第3个代表 支持kwargs 的参数.
获取关键字的文档
最后一个动态库可实现的特殊方法是 get_keyword_documentation
(别名 getKeywordDocumentation
). 顾名思义, 它接受一个关键字名称作为参数, 以字符串的形式返回该关键字的文档.
返回的文档用起来和Python静态库的文档字符串没什么差别. 主要的使用场景就是插入到 Libdoc_ 生成的文档中. 并且文档第一行(第一个 \n
之前的部分)会写入到日志中.
获取关键字的标签
动态库没有其它方法来定义 关键字标签, 除了在文档的最后一行, 以 Tags:
作为前缀指定.
今后有可能会添加单独的 get_keyword_tags
方法到动态库的API中.
获取库的综合文档
getkeyword_documentation
方法还可以被用来指定测试库的总文档. 这部分文档不在测试执行时使用, 但是它们可以让 [Libdoc](#id190) 生成的文档变得更好.
这种综合性的文档有两种, 一种是关于库的介绍, 另一个是关于库的使用指导. 前一种需要传递 intro
参数给 getkeyworddocumentation
, 而后一种传递 _init
. 想了解这两种文档的差别, 最好是通过 [Libdoc](#id192) 实践看看表现.
基于Python的动态库还可以通过代码的文档字符串(docstring)来指定综合文档. 其中类的docstring对应 intro
, init
方法的对应 init
. 如果通过代码和 get_keyword_documentation
方法都能获取到非空的文档, 则最终使用后者.
动态库中的命名参数语法
从Robot Framework 2.8版本开始, 动态库API开始支持 命名参数. 使用该语法需要基于使用 get_keyword_arguments
获取到的参数名称和缺省值.
大部分情况下, 动态关键字的命名参数语法和其它关键字的没什么区别. 唯一的例外是当关键字有多个参数有缺省值, 而只有后面的几个传了值时, 此时框架会将略过的可选参数按照 get_keyword_arguments
中返回的缺省值进行赋值.
动态库中使用命名参数语法的例子见下面. 所有的例子都使用了关键字 Dynamic, 该关键字的参数规范是 [arg1, arg2=xxx, arg3=yyy]
.注释部分显示的是该关键字实际收到的入参.
- *** Test Cases ***
- Only positional
- Dynamic a # [a]
- Dynamic a b # [a, b]
- Dynamic a b c # [a, b, c]
- Named
- Dynamic a arg2=b # [a, b]
- Dynamic a b arg3=c # [a, b, c]
- Dynamic a arg2=b arg3=c # [a, b, c]
- Dynamic arg1=a arg2=b arg3=c # [a, b, c]
- Fill skipped
- Dynamic a arg3=c # [a, xxx, c]
动态库里的 **kwargs
从Robot Framework 2.8.2版本开始, 动态库也可以支持 任意关键字参数 (**kwargs). 一个必须的前提条件是动态库的 run_keyword
方法必须 接受三个参数. 其中第3个参数被用来接受kwargs. kwargs在Python中作为字典, 在Java中使用Map传递给关键字.
一个关键字接受什么参数取决于 get_keyword_arguments
返回的结果. 如果最后返回的参数是以 **
开头, 则表示这个关键字可以接受kwargs.
下面的例子演示了动态库使用kwargs的情况. 所有的例子都使用了关键字 Dynamic, 该关键字被设定的参数规范为 [arg1=xxx, arg2=yyy, **kwargs]
.注释部分是实际调用该关键字的入参.
- *** Test Cases ***
- No arguments
- Dynamic # [], {}
- Only positional
- Dynamic a # [a], {}
- Dynamic a b # [a, b], {}
- Only kwargs
- Dynamic a=1 # [], {a: 1}
- Dynamic a=1 b=2 c=3 # [], {a: 1, b: 2, c: 3}
- Positional and kwargs
- Dynamic a b=2 # [a], {b: 2}
- Dynamic a b=2 c=3 # [a], {b: 2, c: 3}
- Named and kwargs
- Dynamic arg1=a b=2 # [a], {b: 2}
- Dynamic arg2=a b=2 c=3 # [xxx, a], {b: 2, c: 3}
总结
动态库API中的所有特殊方法都列在下表中. 方法名使用了下划线的格式, 但是驼峰命名法同样也可以.
Name | Arguments | Purpose |
---|---|---|
get_keyword_names | 返回关键字名字. | |
run_keyword | name, arguments, kwargs | 以给定参数, 执行指定关键字. kwargs 是可选的 |
get_keyword_arguments | name | 返回关键字的 参数定义. 该方法是可选的. |
get_keyword_documentation | name | 返回关键字的 文档. 该方法是可选的. |
如果使用Java, 可以像下面这样正式的声明接口. 不过请记住, 测试库 不需要 实现任何显式的接口, 因为 Robot Framework 是使用反射直接检测类是否实现了必需的 get_keyword_names
和 run_keyword
方法(或者以驼峰命名的别名). 另外, get_keyword_arguments
和 get_keyword_documentation
完全是可选的.
- public interface RobotFrameworkDynamicAPI {
- List<String> getKeywordNames();
- Object runKeyword(String name, List arguments);
- Object runKeyword(String name, List arguments, Map kwargs);
- List<String> getKeywordArguments(String name);
- String getKeywordDocumentation(String name);
- }
注解
除了使用 List
, 还可以使用数组, 如 Object[]
或 String[]
.
使用动态API的一个很好的例子是Robot Framework自带的 远程库.