与Robot Framework通讯
当关键字方法被调用后, 它可以使用任何机制去和被测系统通讯. 同时, 它还可以发送消息给 Robot Framework的日志文件, 返回结果以保存到变量中, 最重要的, 报告该关键字是否通过了(passed).
报告关键字状态
使用异常(exceptions)即可报告关键字状态. 如果一个方法的执行抛出了一个异常, 这个关键字的状态就是 FAIL
, 如果正常返回, 则状态是 PASS
.
错误消息会写入日志和报告文件. 控制台也会显示异常类型和异常消息. 一般的异常(如 AssertionError
, Exception
, 和 RuntimeError
), 只显示异常消息; 其它的异常, 消息的格式是 异常类型: 异常消息
.
从 Robot Framework 2.8.2 版本开始, 也可以让自己的异常类型和一般异常一样, 失败消息中没有异常类型作为前缀. 要实现这个效果, 为自定义异常类添加一个特殊属性 ROBOT_SUPPRESS_NAME
, 并将值置为 True
.
Python:
- class MyError(RuntimeError):
- ROBOT_SUPPRESS_NAME = True
Java:
- public class MyError extends RuntimeException {
- public static final boolean ROBOT_SUPPRESS_NAME = true;
- }
无论什么情况下, 异常消息的内容都应该尽量明确, 提供足够的信息给用户.
错误消息中使用HTML
从 Robot Framework 2.8 版本开始, 在错误消息中以 HTML
开头, 就可以直接使用HTML格式的消息内容. 例如:
- raise AssertionError("*HTML* <a href='robotframework.org'>Robot Framework</a> rulez!!")
不但可以像上面例子一样, 在测试库中抛出一个异常, 还可以 在测试数据中提供错误信息.
自动截断长消息
如果一个错误消息超过了40行, 就会被自动截断以防止报告变得太长而难以阅读. 完整的错误信息总会在失败关键字的相关日志中显示.
错误回溯(Tracebacks)
异常的回溯(traceback)信息在 日志级别 为 DEBUG
时也会被写入日志. 这些信息默认在日志文件中不可见, 普通用户对这些消息一般也不感兴趣. 在开发测试库时, 则一般会使用 —loglevel DEBUG
选项来运行测试以方便定位问题.
停止测试执行
有时候出现异常意味着要 结束整个测试. 要实现这种效果, 为抛出的异常类设置一个特殊的 ROBOT_EXIT_ON_FAILURE
属性 , 并将其值设为 True
. 例如:
Python:
- class MyFatalError(RuntimeError):
- ROBOT_EXIT_ON_FAILURE = True
Java:
- public class MyFatalError extends RuntimeException {
- public static final boolean ROBOT_EXIT_ON_FAILURE = true;
- }
失败后继续测试执行
有时候, 即使出现了错误仍然希望测试 继续执行. 这时要为异常类设置特殊属性 ROBOT_CONTINUE_ON_FAILURE
, 并将值设为 True
. 例如:
Python:
- class MyContinuableError(RuntimeError):
- ROBOT_CONTINUE_ON_FAILURE = True
Java:
- public class MyContinuableError extends RuntimeException {
- public static final boolean ROBOT_CONTINUE_ON_FAILURE = true;
- }
日志信息
异常消息不是为用户提供信息的唯一途径. 可以通过向标准输出流(stdout)或者标准错误流(stderr)写入的方式来写 log files, 同时这种写入还可以使用不同的 日志级别. 另一种通常更好的写日志方式是使用 编程式日志API.
默认情况下, 向标准输出中写入的所有内容都会以一条INFO
级别的日志被写入到日志文件. 向标准错误流中写入的消息处理也类似, 不过它们会在关键字结束时, 在初始的stderr中回显. 因此, 如果你需要在测试执行的时候在控制台显示消息, 可以使用stderr.
使用日志级别
要使用其它的日志级别, 可以在日志消息中指明日志级别, 格式是 LEVEL 日志消息
. 其中 LEVEL
必须在行首, 而且必须是下列日志级别的其中之一: TRACE
, DEBUG
, INFO
, WARN
, ERROR
和 HTML
.
错误与警告
ERROR
或 WARN
级别的消息会自动写入控制台, 并在日志文件中写入单独的 测试执行错误章节. 这都是为了让错误消息提示更加显著, 以便向用户报告那些重要的问题.
注解
在 Robot Framework 2.9 版本中, ERROR 日志自动写入测试执行错误章节作为新功能被加入.
HTML日志
测试库写日志的所有内容, 默认情况下都会被转换为 可被安全表示为HTML 的格式. 例如, <b>foo</b>
在日志中会完全按原样展示, 而不是粗体的 foo.如果测试库希望显示格式化的内容, 或者链接, 图片等等, 就可以使用一种特殊的伪测试级别 HTML
. Robot Framework 仍将这些消息按 INFO
级别写入日志, 但是可以使用任意的 HTML 语法.
注意, 这个特性功能需要小心使用, 因为一个错误的 </table>
标签就有可能使整个日志文件变得非常糟糕.
当使用 日志API 时, 不同日志级别的方法都提供了一个可选选项 html
, 如果想使用HTML格式的内容, 可以将其设置为 True
时间戳
默认情况下, 通过stdout或stderr记录的日志消息的时间戳是在关键字结束后获取到的. 这就意味着这个时间戳是不准确的, 特别是在一个长时间执行的关键字中, 想借此定位问题是有问题的.
如果有需要的话, 关键字可以为日志消息添加精确的时间戳. 这个时间戳必须以 Unix时间戳 的格式提供, 紧跟 日志级别 后面, 两者以冒号(:)隔开, 例如:
- *INFO:1308435758660* Message with timestamp
- *HTML:1308435758661* <b>HTML</b> message with timestamp
如下例所示, 添加这种时间戳对于Python和Java来说都是很容易的事情. 如果使用的是Python, 通过使用 编程式日志API 会格外简单. 添加明确的时间戳的一个好处是其在 远程库接口 中仍然有效.
Python:
- import time
- def example_keyword():
- print '*INFO:%d* Message with timestamp' % (time.time()*1000)
Java:
- public void exampleKeyword() {
- System.out.println("*INFO:" + System.currentTimeMillis() + "* Message with timestamp");
- }
控制台日志
测试库如果想向控制台写入一些内容, 可以有好几种选择. 前面已经讨论过, 警告消息, 以及所有写入到stderr中内容会同时写入日志文件和控制台.
这两种方式都有一个限制, 那就是消息只有等当前的关键字执行完毕后才会打印出来. 而好处是, 这两种方法在Python和Java中都可用.
另一个方式只有Python支持, 那就是把消息写入 sys.stdout
或 sys.stderr
. 这种方式, 消息会立即在控制台显示, 并且不会写入到日志文件. 例如:
- import sys
- def my_keyword(arg):
- sys.__stdout__.write('Got arg %s\n' % arg)
最后一个选择就是使用 日志API:
- from robot.api import logger
- def log_to_console(arg):
- logger.console('Got arg %s' % arg)
- def log_to_console_and_log_file(arg)
- logger.info('Got arg %s' % arg, also_console=True)
日志示例
INFO
级别的日志可以满足大多数情况. 比它更低的级别, DEBUG
和 TRACE
, 用来打印调试信息. 这两种消息平常不怎么展示, 但在debugging测试库自身的问题时很有用. WARN
或 ERROR
级别可以使得消息提示更显著. 而 HTML
在需要多种格式的时候很有用.
下面的示例阐明了不同的日志级别是如何工作的. 对于Java程序员来说, 下面代码中的 print 'message'
可以认为是 System.out.println("message");
.
- print 'Hello from a library.'
- print '*WARN* Warning from a library.'
- print '*ERROR* Something unexpected happen that may indicate a problem in the test.'
- print '*INFO* Hello again!'
- print 'This will be part of the previous message.'
- print '*INFO* This is a new message.'
- print '*INFO* This is <b>normal text</b>.'
- print '*HTML* This is <b>bold</b>.'
- print '*HTML* <a href="http://robotframework.org">Robot Framework</a>'
16:18:42.123 | INFO | Hello from a library. |
16:18:42.123 | WARN | Warning from a library. |
16:18:42.123 | ERROR | Something unexpected happen that may indicate a problem in the test. |
16:18:42.123 | INFO | Hello again!This will be part of the previous message. |
16:18:42.123 | INFO | This is a new message. |
16:18:42.123 | INFO | This is <b>normal text</b>. |
16:18:42.123 | INFO | This is bold. |
16:18:42.123 | INFO | Robot Framework |
编程式日志API
用于编程写日志的API, 相对于往stdout和stderr中写入内容, 提供了更清晰的写日志方式. 但是, 当前这些API只对基于Python的库可用.
日志API
Robot Framework 提供了基于Python的日志API, 可以用来写日志文件和控制台. 测试库可以按照类似 logger.info('My message')
的方式来调用API, 以替代直接写stdout的方式 print 'INFO My message'
.
使用API接口不但看上去更清楚, 还有个好处是可以提供精确的 时间戳.
日志API作为Robot Framework API文档 的一部分, 详见 这里. 下面是一个简单的示例:
- from robot.api import logger
- def my_keyword(arg):
- logger.debug('Got argument %s' % arg)
- do_something()
- logger.info('<i>This</i> is a boring example', html=True)
- logger.console('Hello, console!')
使用这个日志API的一个明显的限制是会使测试库依赖于 Robot Framework. 在 2.8.7 版本之前, Robot还必须是运行状态才可用. 从 2.8.7 版本开始, 如果Robot不在运行中, 消息会自动重定向到Python的标准 logging 模块.
使用Python标准 logging 模块
除了 日志API, Robot Framework 提供对Python标准日志模块 logging 的支持. 使用这个模块后, 所有root logger收到的消息都会自动传递给 Robot Framework的日志文件. 同样, 该API提供了精确 时间戳 的支持. 但是不支持HTML格式, 以及向控制台打印日志.最大的好处是, 使用这种日志API不会对 Robot Framework 产生依赖.
- import logging
- def my_keyword(arg):
- logging.debug('Got argument %s' % arg)
- do_something()
- logging.info('This is a boring example')
logging
模块的日志级别和Robot Framework的相比略有不同, 其中 DEBUG
, INFO
, WARNING
和 ERROR
直接对应Robot Framework相应的日志级别, CRITICAL
对应 ERROR
.
自定义的日志级别映射为 “和它最接近, 同时低于它” 的标准级别. 例如, 介于 INFO
和 WARNING
之间的级别最终映射为 INFO 级别.
库初始化时写日志
库在导入和初始化时也可以写日志. 这部分日志不会和普通日志消息一样写入 日志文件, 而是写入 系统日志. 这种日志可以将任何关于库的初始化的debug信息记录下来. 级别为 WARN
或者 ERROR
的日志同时也可在 test execution errors 章节中看到.
这种日志既可以使用 标准输出和错误流 的方式, 也可以使用 编程式日志API. 下面的例子都做了说明:
Java库在初始化时通过stdout写日志:
- public class LoggingDuringInitialization {
- public LoggingDuringInitialization() {
- System.out.println("*INFO* Initializing library");
- }
- public void keyword() {
- // ...
- }
- }
Python库在导入时通过logging API写日志:
- from robot.api import logger
- logger.debug("Importing library")
- def keyword():
- # ...
注解
如果你在初始化阶段写日志, 例如, 在Python的 init
方法中或者Java的构造函数中, 这些日志按 测试库的作用域 的不同, 可能会记录多次.
返回值
关键字与核心框架间交互的最后一步就是返回值, 该值可以是从被测系统获取的, 也可能是其它方式生成的.
返回值可以被 赋值给变量, 然后作为其它关键字的输入, 而这些关键字可以是属于不同的测试库的.
在Python和Java方法中, 都使用 return
语句来返回值. 一般情况下, 一个值会赋给一个 标量变量, 如下例所示. 该示例还展现了返回值可以是任意对象, 并且使用 扩展变量语法 来获取对象的属性.
- from mymodule import MyObject
- def return_string():
- return "Hello, world!"
- def return_object(name):
- return MyObject(name)
- *** Test Cases ***
- Returning one value
- ${string} = Return String
- Should Be Equal ${string} Hello, world!
- ${object} = Return Object Robot
- Should Be Equal ${object.name} Robot
关键字还可以一次返回多个值, 这些值可以一次性的赋值给多个 标量变量, 或者是 一个列表变量, 亦或者是若干标量变量加上一个列表变量. 所有这些用法要求返回的值是Python的列表(lists)或者元组(tuples), 或者是Java中的数组(arrays), 列表(Lists)或迭代器(Iterators).
- def return_two_values():
- return 'first value', 'second value'
- def return_multiple_values():
- return ['a', 'list', 'of', 'strings']
- *** Test Cases ***
- Returning multiple values
- ${var1} ${var2} = Return Two Values
- Should Be Equal ${var1} first value
- Should Be Equal ${var2} second value
- @{list} = Return Two Values
- Should Be Equal @{list}[0] first value
- Should Be Equal @{list}[1] second value
- ${s1} ${s2} @{li} = Return Multiple Values
- Should Be Equal ${s1} ${s2} a list
- Should Be Equal @{li}[0] @{li}[1] of strings
使用多线程
如果库使用了多线程, 通常应该只在主线程中与框架通讯. 如果一个工作线程需要发送错误报告或者其它日志, 它应该首先将信息传给主线程. 主线程使用异常或本章介绍的其它机制来与框架通讯.
当线程在后台运行, 同时其它关键字在运行时这点显得尤为重要. 这种情况下, (子线程)和框架间的通讯是未定义的(undefined), 在最坏的情况下甚至会导致程序崩溃, 或者输出文件损坏.
如果一个关键字启动了后台任务, 那么要想检查后台线程的状态, 或者搜集相应的信息上报, 需要使用另外的关键字来完成.
在非主线程中使用 编程式日志API 写日志会被默默忽略.
不过, 有个单独的 robot后台日志 项目, 提供了 BackgroundLogger
, 拥有和标准 robot.api.logger
类似的API. 使用 BackgroundLogger
, 非主线程的日志消息也会被保存下来.