目录
前言
创建测试库
一些重要的概念:
结语
前言
今天给大家讲一讲robotframework自动化测试框架创建及使用测试库的主要实践,通过这篇文章希望可以让大伙能完善和增强robotframework基本功能。
创建测试库
测试库的实现可以是Python模块, 也可以是Python类.
Python模块
Python类
测试库通常在 Setting 表格中设置 Library 来导入, 库名称跟在 Library
后面. 不同于大部分的其它数据, 库名称既是大小写敏感的, 也是空白敏感的. 如果一个测试库是在某个文件里的, 则必须指明路径.
测试结果
==============================================================================
Functest
==============================================================================
测试库用例
我的方法
我的类
| PASS |
------------------------------------------------------------------------------
Functest | PASS |
1 test, 1 passed, 0 failed
==============================================================================
一些重要的概念:
- 实现为类的测试库都可以接受参数. 这些参数在Setting表中指定, 跟在库名后面, 当Robot Framework创建测试库的实例时, 把这些参数传给构造函数.意思就是MyModule=MyModule(__init__方法的参数),之后使用MyModule.method。
开始连接
==============================================================================
Functest
==============================================================================
DBtest 开始连接
开始查询
查询完成,数据为{'id': 1, 'username': 'admin', 'password': 'pbkdf2:sha256:260000$l0K5upqR$87143093593e3622bc700f3c57e91180ad05ccb51169b36617cfe3ac6207c9a2', 'nickname': '管理员', 'email': 'admin@163.com', 'isadmin': 0, 'group_id': 1, 'create_time': datetime.datetime(2022, 10, 15, 20, 19, 4), 'update_time': datetime.datetime(2022, 10, 15, 20, 19, 4), 'delete_time': None, 'update_user_id': None, 'create_user_id': None, 'update_user_name': None, 'create_user_name': None}
| PASS |
------------------------------------------------------------------------------
Functest | PASS |
1 test, 1 passed, 0 failed
==============================================================================
- 变量可以包含任何类型的对象作为值,用作参数的变量按原类型传递给关键字。默认情况下,关键字参数作为Unicode字符串传递给关键字。
case1
<class 'str'>
<class 'int'>
<class 'str'>
<class 'bool'>
case1 | PASS |
指定类型的最自然方式是使用Python函数注释,如果没有使用注释Robot Framework会尝试根据可能的参数默认值获取类型信息。转换失败时,关键字会获得原始值(Unicode字符串)。
上面会转换为int和float类型,如果参数具有显式类型和默认值,转换失败会导致错误。
case1 | FAIL | ValueError: Argument 'f' got value 'abc' that cannot be converted to float.
指定显式参数类型的另一种方法是使用@keyword装饰器。它接受一个可选的types参数,该参数可以用于指定参数类型,既可以作为字典,也可以作为列表。
无论使用哪种方法,都不一定要为所有参数指定类型。当将类型指定为列表时,可以使用None来标记某个参数没有类型,并且末尾的参数可以完全省略。只为第二个参数指定类型案例
除了如前几节所述自动执行参数转换之外,Robot Framework还支持自定义参数转换。ROBOT_LIBRARY_CONVERTERS可以可以为测试库注册转换器,参数转换器是获取数据中使用的参数并在参数传递给关键字之前将其转换为所需格式的函数或其他可调用函数。
[info] year: 2022, month: 1, day: 25 case1 | PASS |
@keyword(types={'count': int, 'bool': bool}) @keyword(types=[int, bool])
@keyword(types={'bool': bool}) @keyword(types=[None, bool])
转换失败会抛出异常
[FAIL] ValueError: Argument 'arg' got value 'invalid' that cannot be converted to date: not enough values to unpack (expected 3, got 1)
case1 | FAIL |
转换器也可以接受多个类型,将类型指定为Union。
def parse_fi_date(value: Union[str, int]):
通过将测试库实现为类,还可以与@library装饰器一起使用。以下案例说明
case1
FI 25.1.2022
ISO 8601 2022-01-22
ALL 2022-01-22
ALL 25.1.2022
[info] year: 2022, month: 1, day: 25
[info] year: 2022, month: 1, day: 22
[info] year: 2022, month: 1, day: 22
[info] year: 2022, month: 1, day: 25
上面案例显示如果参数是指定的开始类型(date),则不使用转换器。
- 将库实现为模块时,模块命名空间中的所有函数都将成为关键字。对于导入的函数也是如此,这可能会引起令人讨厌的意外。例如,如果下面的模块用作库,它将包含关键字example keyword,但也包含关键字Current Thread。 from threading import current_thread def example_keyword(): print('Running in thread "%s".' % current_thread().name)
避免导入的函数成为关键字的一个简单方法是只导入模块(例如导入线程),并通过模块使用函数(例如线程.current_thread())。或者,可以在导入时为函数提供一个以下划线开头的别名(例如,从线程导入current_thread作为_current_thread)。
- 当库作为类实现时,可以通过将Robot_AUTO_keywords属性设置为false来告诉Robot Framework不要自动将方法公开为关键字
case1 .f561aaf6ef0bf14d4208bb46a4ccb3ad
case1 | PASS |
------------------------------------------------------------------------------
case2 .698d51a19d8a121ce581499d7b701668
case2 | PASS |
------------------------------------------------------------------------------
case3 | FAIL |
- 传递给库的参数, 包括库名本身, 都可以使用变量. 也就是说可以在某些时候, 例如命令行(--variable), 修改它们.
- Robot Framework 为了保证测试用例之间的独立性, 默认情况下, 它为每个测试用例 创建新的测试库实例. 然而, 这种方式不总是我们想要的, 比如有时测试用例需要共享 某个状态的时候. 此外, 那些无状态的库显然也不需要每次都创建新实例.
- TEST CASE 为每个测试用例创建新的实例. 如果有suite setup和suite teardown的话, 同样 也会新建. 这是默认行为.
-
TEST SUITE
为每个测试集创建新的实例. 最底层的测试集, 也就是由测试用例文件组成的测试集, 拥有属于自己的测试库实例, 高层的测试集, 都有属于自己的测试库实例. GLOBAL
整个测试执行过程中只有一个实例被创建. 所有的测试集和测试用例共享这个实例. 通过模块创建的测试库都是全局的.
from robot.api import logger
class MyModule:
ROBOT_LIBRARY_SCOPE = 'GLOBAL'
def __init__(self):
self._counter = 0
def count(self):
self._counter += 1
logger.console(self._counter)
def clear_counter(self):
self._counter = 0
可以通过@library设置
@library(scope=''GLOBAL'') class MyModule:
- 名称的比较是忽略大小写的, 并且其中的空格和下划线 也都忽略掉
- 异常的回溯(traceback)信息在 日志级别 为
DEBUG
时也会被写入日志 - 有时候, 出现了错误从而停止整个测试执行.最简单的方法是使用robot.api.FatalError
==============================================================================
case1 | FAIL |
FatalError: 系统终止运行
------------------------------------------------------------------------------
case2 | FAIL |
Test execution stopped due to a fatal error.
------------------------------------------------------------------------------
Functest | FAIL |
2 tests, 0 passed, 2 failed
==============================================================================
- 希望即使出现故障,也可以继续执行测试。最简单的方法是使用robot.api.ContinuableFailure
==============================================================================
case1 F此用例继续
case1 | FAIL |
系统继续运行
------------------------------------------------------------------------------
- 希望即使出现故障,也可以跳过继续执行测试。最简单的方法是使用robot.api.SkipExecution
- 其中一个最有用的方法是
replace_variables
, 它允许访问当前可用的变量.
import hashlib
from robot.libraries.BuiltIn import BuiltIn
class MyModule:
def getcode(self,password):
code = hashlib.md5(BuiltIn().replace_variables("${password}").encode())
print(code.hexdigest())
*** Test Cases ***
case1
Set Global Variable ${password} 111111
Getcode ${password}
==============================================================================
Functest
==============================================================================
case1 | PASS |
------------------------------------------------------------------------------
[info] ${password} = 111111
[info] 96e79218965eb72c92a549dd5a330112
Functest | PASS |
1 test, 1 passed, 0 failed
==============================================================================
- 一个直接的方式是使用继承来扩展已有库,一个大问题是, 这两个库将很难同时使用. 首先这两个库的同名关键字会产生 冲突, 另外, 这两个库没有共享状态.
==============================================================================
Functest
==============================================================================
[info] ${data} = {'username': 'admin', 'password': '111111'}
case1 b'{"username": "admin", "password": "111111"}'
{'name': 'admin', 'nickname': '管理员', 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTY2NjI4MzI3NCwianRpIjoiMTY0ZmEzYjAtNDI1ZC00YzE1LWEyM2ItOThiMGIzZWY4NDY0IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6MSwibmJmIjoxNjY2MjgzMjc0LCJleHAiOjE2NjYzMDEyNzR9.ndUAu0VmcBKLvS9AGmxb-nufVumA4hR-9qeegKxJ4GI', 'uuid': 1}
| PASS |
------------------------------------------------------------------------------
Functest | PASS |
1 test, 1 passed, 0 failed
==============================================================================
- 内置关键字 Get Library Instance 可以用来获取当前活动的测试库的实例. 该关键字返回的实例与框架自己使用的实例完全一样
*** Settings ***
Library BuiltIn
Library SeleniumLibrary
Library util/SeLibExtensions.py
Suite Teardown Close All Browsers
*** Variables ***
${host} http://www.baidu.com
*** Test Cases ***
case1
Open Browser ${host} chrome
SeLibExtensions.Title Should Be 谷歌
==============================================================================
Functest
==============================================================================
[info] Opening browser 'chrome' to base url 'http://www.baidu.com'.
[info (+2.43s)] 获取的抬头:百度一下,你就知道
[FAIL] Title '百度一下,你就知道' is not '谷歌'
case1 | FAIL |
Title '百度一下,你就知道' is not '谷歌'
------------------------------------------------------------------------------
Functest | FAIL |
1 test, 0 passed, 1 failed
==============================================================================
- 导入测试库的另一种方式是使用 BuiltIn 库提供的关键字 Import Library. 该关键字接受库名称以及可能的参数作为它的参数. 被导入的库中的关键字在调用 Import Library 关键字的测试套件中可用。
- 重写上面的功能,初始化就获取对象,复用对象方法,可以用Import Library在实例生成后导入多个实例,使用
WITH NAME
(此处区分大小写) 命名,调用特定实例的方法。
*** Settings ***
Library BuiltIn
Library SeleniumLibrary
Suite Teardown Close All Browsers
*** Variables ***
${host} http://www.baidu.com
*** Test Cases ***
case1
Open Browser ${host} chrome b1
${seleniumlib} Get Browser Aliases
Import Library util/SeLibExtensions.py SeleniumLibrary WITH NAME mydriver
mydriver.get_session_id
Open Browser ${host} chrome b2
Switch Browser b2
${seleniumlib} Get Browser Aliases
Import Library util/SeLibExtensions.py SeleniumLibrary WITH NAME mydriver2
mydriver2.get_session_id
==============================================================================
Functest
==============================================================================
[info] Opening browser 'chrome' to base url 'http://www.baidu.com'.
[info (+4.34s)] ${seleniumlib} = {'b1': 1}
case1 dd7cf760150bbb2a910e1a355414eda5
[info (+0.01s)] Opening browser 'chrome' to base url 'http://www.baidu.com'.
[info (+9.59s)] ${seleniumlib} = {'b1': 1, 'b2': 2}
ee414a1679bb6013acdcdbc6edefeb82
| PASS |
------------------------------------------------------------------------------
Functest | PASS |
1 test, 1 passed, 0 failed
==============================================================================