与Robot Framework通讯

当关键字方法被调用后, 它可以使用任何机制去和被测系统通讯. 同时, 它还可以发送消息给 Robot Framework的日志文件, 返回结果以保存到变量中, 最重要的, 报告该关键字是否通过了(passed).

报告关键字状态

使用异常(exceptions)即可报告关键字状态. 如果一个方法的执行抛出了一个异常, 这个关键字的状态就是 FAIL, 如果正常返回, 则状态是 PASS.

错误消息会写入日志和报告文件. 控制台也会显示异常类型和异常消息. 一般的异常(如 AssertionError, Exception, 和 RuntimeError), 只显示异常消息; 其它的异常, 消息的格式是 异常类型: 异常消息.

从 Robot Framework 2.8.2 版本开始, 也可以让自己的异常类型和一般异常一样, 失败消息中没有异常类型作为前缀. 要实现这个效果, 为自定义异常类添加一个特殊属性 ROBOT_SUPPRESS_NAME, 并将值置为 True.

Python:

  1. class MyError(RuntimeError):
  2. ROBOT_SUPPRESS_NAME = True

Java:

  1. public class MyError extends RuntimeException {
  2. public static final boolean ROBOT_SUPPRESS_NAME = true;
  3. }

无论什么情况下, 异常消息的内容都应该尽量明确, 提供足够的信息给用户.

错误消息中使用HTML

从 Robot Framework 2.8 版本开始, 在错误消息中以 HTML 开头, 就可以直接使用HTML格式的消息内容. 例如:

  1. raise AssertionError("*HTML* <a href='robotframework.org'>Robot Framework</a> rulez!!")

不但可以像上面例子一样, 在测试库中抛出一个异常, 还可以 在测试数据中提供错误信息.

自动截断长消息

如果一个错误消息超过了40行, 就会被自动截断以防止报告变得太长而难以阅读. 完整的错误信息总会在失败关键字的相关日志中显示.

错误回溯(Tracebacks)

异常的回溯(traceback)信息在 日志级别DEBUG 时也会被写入日志. 这些信息默认在日志文件中不可见, 普通用户对这些消息一般也不感兴趣. 在开发测试库时, 则一般会使用 —loglevel DEBUG 选项来运行测试以方便定位问题.

停止测试执行

有时候出现异常意味着要 结束整个测试. 要实现这种效果, 为抛出的异常类设置一个特殊的 ROBOT_EXIT_ON_FAILURE 属性 , 并将其值设为 True. 例如:

Python:

  1. class MyFatalError(RuntimeError):
  2. ROBOT_EXIT_ON_FAILURE = True

Java:

  1. public class MyFatalError extends RuntimeException {
  2. public static final boolean ROBOT_EXIT_ON_FAILURE = true;
  3. }

失败后继续测试执行

有时候, 即使出现了错误仍然希望测试 继续执行. 这时要为异常类设置特殊属性 ROBOT_CONTINUE_ON_FAILURE, 并将值设为 True. 例如:

Python:

  1. class MyContinuableError(RuntimeError):
  2. ROBOT_CONTINUE_ON_FAILURE = True

Java:

  1. public class MyContinuableError extends RuntimeException {
  2. public static final boolean ROBOT_CONTINUE_ON_FAILURE = true;
  3. }

日志信息

异常消息不是为用户提供信息的唯一途径. 可以通过向标准输出流(stdout)或者标准错误流(stderr)写入的方式来写 log files, 同时这种写入还可以使用不同的 日志级别. 另一种通常更好的写日志方式是使用 编程式日志API.

默认情况下, 向标准输出中写入的所有内容都会以一条INFO 级别的日志被写入到日志文件. 向标准错误流中写入的消息处理也类似, 不过它们会在关键字结束时, 在初始的stderr中回显. 因此, 如果你需要在测试执行的时候在控制台显示消息, 可以使用stderr.

使用日志级别

要使用其它的日志级别, 可以在日志消息中指明日志级别, 格式是 LEVEL 日志消息. 其中 LEVEL 必须在行首, 而且必须是下列日志级别的其中之一: TRACE, DEBUG, INFO, WARN, ERRORHTML.

错误与警告

ERRORWARN 级别的消息会自动写入控制台, 并在日志文件中写入单独的 测试执行错误章节. 这都是为了让错误消息提示更加显著, 以便向用户报告那些重要的问题.

注解

在 Robot Framework 2.9 版本中, ERROR 日志自动写入测试执行错误章节作为新功能被加入.

HTML日志

测试库写日志的所有内容, 默认情况下都会被转换为 可被安全表示为HTML 的格式. 例如, <b>foo</b> 在日志中会完全按原样展示, 而不是粗体的 foo.如果测试库希望显示格式化的内容, 或者链接, 图片等等, 就可以使用一种特殊的伪测试级别 HTML. Robot Framework 仍将这些消息按 INFO 级别写入日志, 但是可以使用任意的 HTML 语法.

注意, 这个特性功能需要小心使用, 因为一个错误的 </table> 标签就有可能使整个日志文件变得非常糟糕.

当使用 日志API 时, 不同日志级别的方法都提供了一个可选选项 html, 如果想使用HTML格式的内容, 可以将其设置为 True

时间戳

默认情况下, 通过stdout或stderr记录的日志消息的时间戳是在关键字结束后获取到的. 这就意味着这个时间戳是不准确的, 特别是在一个长时间执行的关键字中, 想借此定位问题是有问题的.

如果有需要的话, 关键字可以为日志消息添加精确的时间戳. 这个时间戳必须以 Unix时间戳 的格式提供, 紧跟 日志级别 后面, 两者以冒号(:)隔开, 例如:

  1. *INFO:1308435758660* Message with timestamp
  2. *HTML:1308435758661* <b>HTML</b> message with timestamp

如下例所示, 添加这种时间戳对于Python和Java来说都是很容易的事情. 如果使用的是Python, 通过使用 编程式日志API 会格外简单. 添加明确的时间戳的一个好处是其在 远程库接口 中仍然有效.

Python:

  1. import time
  2.  
  3. def example_keyword():
  4. print '*INFO:%d* Message with timestamp' % (time.time()*1000)

Java:

  1. public void exampleKeyword() {
  2. System.out.println("*INFO:" + System.currentTimeMillis() + "* Message with timestamp");
  3. }

控制台日志

测试库如果想向控制台写入一些内容, 可以有好几种选择. 前面已经讨论过, 警告消息, 以及所有写入到stderr中内容会同时写入日志文件和控制台.

这两种方式都有一个限制, 那就是消息只有等当前的关键字执行完毕后才会打印出来. 而好处是, 这两种方法在Python和Java中都可用.

另一个方式只有Python支持, 那就是把消息写入 sys.stdoutsys.stderr. 这种方式, 消息会立即在控制台显示, 并且不会写入到日志文件. 例如:

  1. import sys
  2.  
  3. def my_keyword(arg):
  4. sys.__stdout__.write('Got arg %s\n' % arg)

最后一个选择就是使用 日志API:

  1. from robot.api import logger
  2.  
  3. def log_to_console(arg):
  4. logger.console('Got arg %s' % arg)
  5.  
  6. def log_to_console_and_log_file(arg)
  7. logger.info('Got arg %s' % arg, also_console=True)

日志示例

INFO 级别的日志可以满足大多数情况. 比它更低的级别, DEBUGTRACE, 用来打印调试信息. 这两种消息平常不怎么展示, 但在debugging测试库自身的问题时很有用. WARNERROR 级别可以使得消息提示更显著. 而 HTML 在需要多种格式的时候很有用.

下面的示例阐明了不同的日志级别是如何工作的. 对于Java程序员来说, 下面代码中的 print 'message' 可以认为是 System.out.println("message");.

  1. print 'Hello from a library.'
  2. print '*WARN* Warning from a library.'
  3. print '*ERROR* Something unexpected happen that may indicate a problem in the test.'
  4. print '*INFO* Hello again!'
  5. print 'This will be part of the previous message.'
  6. print '*INFO* This is a new message.'
  7. print '*INFO* This is <b>normal text</b>.'
  8. print '*HTML* This is <b>bold</b>.'
  9. print '*HTML* <a href="http://robotframework.org">Robot Framework</a>'
16:18:42.123INFOHello from a library.
16:18:42.123WARNWarning from a library.
16:18:42.123ERRORSomething unexpected happen that may indicate a problem in the test.
16:18:42.123INFOHello again!This will be part of the previous message.
16:18:42.123INFOThis is a new message.
16:18:42.123INFOThis is <b>normal text</b>.
16:18:42.123INFOThis is bold.
16:18:42.123INFORobot 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文档 的一部分, 详见 这里. 下面是一个简单的示例:

  1. from robot.api import logger
  2.  
  3. def my_keyword(arg):
  4. logger.debug('Got argument %s' % arg)
  5. do_something()
  6. logger.info('<i>This</i> is a boring example', html=True)
  7. 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 产生依赖.

  1. import logging
  2.  
  3. def my_keyword(arg):
  4. logging.debug('Got argument %s' % arg)
  5. do_something()
  6. logging.info('This is a boring example')

logging 模块的日志级别和Robot Framework的相比略有不同, 其中 DEBUG, INFO, WARNINGERROR 直接对应Robot Framework相应的日志级别, CRITICAL 对应 ERROR.

自定义的日志级别映射为 “和它最接近, 同时低于它” 的标准级别. 例如, 介于 INFOWARNING 之间的级别最终映射为 INFO 级别.

库初始化时写日志

库在导入和初始化时也可以写日志. 这部分日志不会和普通日志消息一样写入 日志文件, 而是写入 系统日志. 这种日志可以将任何关于库的初始化的debug信息记录下来. 级别为 WARN 或者 ERROR 的日志同时也可在 test execution errors 章节中看到.

这种日志既可以使用 标准输出和错误流 的方式, 也可以使用 编程式日志API. 下面的例子都做了说明:

Java库在初始化时通过stdout写日志:

  1. public class LoggingDuringInitialization {
  2.  
  3. public LoggingDuringInitialization() {
  4. System.out.println("*INFO* Initializing library");
  5. }
  6.  
  7. public void keyword() {
  8. // ...
  9. }
  10. }

Python库在导入时通过logging API写日志:

  1. from robot.api import logger
  2.  
  3. logger.debug("Importing library")
  4.  
  5. def keyword():
  6. # ...

注解

如果你在初始化阶段写日志, 例如, 在Python的 init 方法中或者Java的构造函数中, 这些日志按 测试库的作用域 的不同, 可能会记录多次.

返回值

关键字与核心框架间交互的最后一步就是返回值, 该值可以是从被测系统获取的, 也可能是其它方式生成的.

返回值可以被 赋值给变量, 然后作为其它关键字的输入, 而这些关键字可以是属于不同的测试库的.

在Python和Java方法中, 都使用 return 语句来返回值. 一般情况下, 一个值会赋给一个 标量变量, 如下例所示. 该示例还展现了返回值可以是任意对象, 并且使用 扩展变量语法 来获取对象的属性.

  1. from mymodule import MyObject
  2.  
  3. def return_string():
  4. return "Hello, world!"
  5.  
  6. def return_object(name):
  7. return MyObject(name)
  1. *** Test Cases ***
  2. Returning one value
  3. ${string} = Return String
  4. Should Be Equal ${string} Hello, world!
  5. ${object} = Return Object Robot
  6. Should Be Equal ${object.name} Robot

关键字还可以一次返回多个值, 这些值可以一次性的赋值给多个 标量变量, 或者是 一个列表变量, 亦或者是若干标量变量加上一个列表变量. 所有这些用法要求返回的值是Python的列表(lists)或者元组(tuples), 或者是Java中的数组(arrays), 列表(Lists)或迭代器(Iterators).

  1. def return_two_values():
  2. return 'first value', 'second value'
  3.  
  4. def return_multiple_values():
  5. return ['a', 'list', 'of', 'strings']
  1. *** Test Cases ***
  2. Returning multiple values
  3. ${var1} ${var2} = Return Two Values
  4. Should Be Equal ${var1} first value
  5. Should Be Equal ${var2} second value
  6. @{list} = Return Two Values
  7. Should Be Equal @{list}[0] first value
  8. Should Be Equal @{list}[1] second value
  9. ${s1} ${s2} @{li} = Return Multiple Values
  10. Should Be Equal ${s1} ${s2} a list
  11. Should Be Equal @{li}[0] @{li}[1] of strings

使用多线程

如果库使用了多线程, 通常应该只在主线程中与框架通讯. 如果一个工作线程需要发送错误报告或者其它日志, 它应该首先将信息传给主线程. 主线程使用异常或本章介绍的其它机制来与框架通讯.

当线程在后台运行, 同时其它关键字在运行时这点显得尤为重要. 这种情况下, (子线程)和框架间的通讯是未定义的(undefined), 在最坏的情况下甚至会导致程序崩溃, 或者输出文件损坏.

如果一个关键字启动了后台任务, 那么要想检查后台线程的状态, 或者搜集相应的信息上报, 需要使用另外的关键字来完成.

在非主线程中使用 编程式日志API 写日志会被默默忽略.

不过, 有个单独的 robot后台日志 项目, 提供了 BackgroundLogger , 拥有和标准 robot.api.logger 类似的API. 使用 BackgroundLogger , 非主线程的日志消息也会被保存下来.