robotframework编码分享
把自己日常用的比较多的设计方法分享出来,包括常用方法、开发系统关键字、python库
***测试环境说明***
python版本:python 3.6.4
robot framework版本:3.2.2
RIDE版本:1.7.4.2
*** 文档指引 ***
robot framework官方网站:https://robotframework.org/
robot framework项目地址:https://github.com/robotframework/robotframework
robof framework用户手册:http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html
【1】如何运行
这节非常重要,学会后:
- 可以不依赖工具,直接命令行运行单个用例或套件
- 不直接支持robotframework框架的IDE,都可以自行配置RUN参数或Docker环境来满足运行条件
- 云效流水线部署、集成Jenkins等都需要使用Shell命名配置
【1.1】运行方式(robot)
robotframework支持robot、pybot、jybot和ipybot四种脚本运行形式,这四种脚本运行分别使用不同的语言:
robot:robotframework
pybot:Python
jybot:Java
ipybot:IrconPython(IrconPython是基于.net实现的python,可直接在.net程序里调用python脚本)
即是说.robot文件支持不同的语言下运行。这里只讲robot脚本运行方式,其他的自行举一反三
【1.1.1】运行命令
python -m robot testSuite01.robot
该命名是python2.6+才能使用(robotframework3.0+推荐用这个,我们目前使用的是3.2.2)
python -m robot.run testSuite01.robot
robot.run是python所有版本都能调用的(当然是要robotframework支持的python版本才行)
其他
windows下C:UsersAdministratorAppDataLocalprogramsPythonPython36Scripts有robot.exe
且该路径默认都会写入到系统变量中,可直接在CMD或者powershell中使用:
robot testSuite01.robot
或者
robot.exe testSuite01.robot
【1.1.2】带参数运行
筛选特定标签的用例运行
robot -i 标签 被测测试套件
robot -i normal_test D:codeuprrzuji_RF_APIInterface_CodeDemotesttestSuite01.robot
扩展
一个套件下多个标签,用多个-i参数
robot -i normal -i exception Interface_CodeDemotesttestSuite01.robot
=========================================================
-i参数支持正则匹配,支持*、?和[]
示例:匹配R开头标签的所有用例
robot -i R* Interface_CodeDemotesttestSuite01.robot
上面的例子都是某个套件下面,指定标签的测试用例去运行,那么我要运行一整个项目带该标签的用例,或指定目录
筛选某个套件下指定用例运行
使用多个-t来加入多个测试用例
robot -t 用例名称1 -t 用例名称2 被测测试套件
robot -t Class_01 -t Class_03 Interface_CodeDemotesttestSuite01.robot
指定目录或者是指定标签的全部套件全部用例中筛选执行
robot模块并没有提供此类参数,但是聪明的你想到了用Shell脚本或者Python文件去遍历所有测试套件,再每个测试套件调用一次robot命名执行,后面的Jenkins集成或者流水线部署也是同样的思路
关于产生的大量测试报告文件,合并方案同上
注意:为减少遍历时间,可以把Resources整个都排除在外
【1.1.3】搞不来?使用RIDE开发和测试
我个人认为,RIDE是robotframework最好上手的开发工具,对robotframework不熟悉的推荐使用RIDE进行开发
选择一同执行的测试用例
筛选特定标签执行
【1.2】输出测试报告(rebot)
【1.2.1】robot和rebot输出测试报告的区别
也许你留意到了当你使用robot运行一个测试套件时,已经默认输出三个测试报告文件,那为什么还需要rebot命令来输出测试报告呢?
试想一下,一个实际的项目里,有几十个到几百个套件运行,每个套件都输出一个测试报告,看完都2077年了。
因此我们需要使用rebot来聚合这些报告,使得一份报告包含所有测试套件测试结果报告
robot脚本默认输出三个测试报告文件
rebot 原始测试报告1 原始测试报告2 原始测试报告3
【1.2.2】rebot合并测试报告
rebot有很多参数,大家自行使用rebot -help进行查看和使用,这里只讲项目中用到的
1、我们最常用的就是合并测试报告log文件(html格式)
rebot -l 测试报告名称.html 原始测试报告1 原始测试报告2 原始测试报告3
示例:合并两份测试报告:testSuite01_res.xml、testSuite01_res.xml
rebot -l 20201123.html testSuite01_res.xml testSuite01_res.xml
2、如果多个测试报告,都是一个测试套件的,合并的时候使用-R参数(或--merge)
rebot -l repor-merge testCase01_res.xml testCase02_res.xml
既然是rebot加-R和不加都能生成测试报告,那么对同个测试套件不同用例执行结果合并,这两个命令生成的报告有什么区别呢?
区别如下图,不加参数的情况,同个测试套件下的用例只是简单的聚合
加参数的情况,同个测试套件下的用例会聚合到一起,成为一个测试套件
robot不加-R(左)加-R参数(右)运行结果
【2】RobotFrameWork一些常用用法
【2.1】关键字必选参数,定义和传参方法
*** Test Cases ***
Class_01_必选参数测试用例
${host} Set Variable https://xxx.rrzuji.xx
${path} Set Variable /individual/defaultV2/index
request_get ${host} ${path}
*** Keywords ***
request_get
[Arguments] ${host} ${path}
Log host->${host}
Log path->${path}
【2.2】可选参数,定义和传参方法
*** Test Cases ***
Class_01_可选参数测试用例
${host} Set Variable https://xxx.rrzuji.xx
${path} Set Variable /individual/defaultV2/index
${params} Set Varibale ?id=1
${datas} Create Dictionary city=全国 user_id=12345
${headers} Create Dictionary Authorization=fuck_auth
# 只传必要参数
request_get ${host} ${path}
# 传一个可选参数示例1
request_get ${host} ${path} ${params}
# 传一个可选参数示例2
request_get ${host} ${path} \ ${datas}
# 传一个可选参数示例3
request_get ${host} ${path} \ \ ${headers}
*** Keywords ***
request_get
[Arguments] ${host} ${path} {params}=${EMPTY} ${datas}=${EMPTY} ${headers}
[Documentation] params、datas和headers为可选参数
Log host->${host}
Log path->${path}
Log params->${params}
Log datas->${datas}
Log headers->${headers}
【2.3】善用Evaluate
Evaluate被调用python内置方法的关键字,这样可以用最少的代码解决更多问题,用途非常广
*** Test Cases ***
Class_01_Evaluate关键字测试用例
# 判定一个变量类型
${obj_type} Evaluate type(${obj})
# 把一个数组productIds转化为字符串["ph1324","cp1001",123]->"ph1323,cp1001,em224"
${productIds_str} Evaluate ",".join('%s' %id for id in ${productIds})
【2.4】数组字典互相转化
*** Test Cases ***
Class_01_字典转化为数组
${dict1} Create Dictionary a=1 b=2 c=3 d=4
${keys} Create List
${values} Create List
# 把字典dict1转为key数组和value数组
FOR ${key} ${value} IN ${dict1}
Append To ${keys} ${key}
Append To ${values} ${value}
END
Log keys->${keys}
Log values->${values}
Class_02_字典键和值完全转化为一个数组的元素
${dict1} Create Dictionary a=1 b=2 c=3 d=4
${list1_from_dict1} Get Dictionary Items ${dict1}
Log ${list1_from_dict1}
Class_01_数组转化为字典
${list1} Create List a b c
${dict2} Create Dictionary
# 方法一,使用enumerate函数
${enumerate_list1} Evaluate enumerate(${list1})
FOR ${value} IN @{enumerate_list1}
Log ${value}[0]->${value}[1]
END
#方法二,使用数组长度来作为循环条件
${list1_len} Get Lenght ${list1}
FOR ${index} IN RANGE ${list1_len}
Set To Dictionary ${dict2} ${list1}[${index}]
END
Log dict1_from_list1->${dict1_from_list1}
Log dict2->${dict2}
# 方法三,使用robot内置函数FOR IN ENUMERATE
FOR ${key} ${val} IN ENUMERATE @{list1}
Log ${key}->${val}
END
【2.5】对象、值判断
在很多情况我们不关系类型,只关心值的比较
可使用Should Be Equal As XXX,该关键字会根据指定的类型对需要比较的参数进行转化
官方原话:Fails if objects are unequal after converting them to xxxx
*** Test Cases ***
Class_01_只判断值
${scalar1} Set Variable 0.5
Should Be Equal As Numbers ${scalar1} ${0.5}
# 该用例执行通过
Class_01_对象类型和值都判断
${scalar1} Set Variable 0.5
Should Be Equal ${scalar1} ${0.5}
# 该用例执行不通过
# Argument types are:
# <type 'unicode'>
# <class 'float'>
# FAIL : 0.5 (string) != 0.5 (float)
【2.6】多重循环
robotframework不支持嵌套循环,因此有需求的话需要自行实现(其实这个设定也合理,robotframework的语法都应该简洁明了)
需要嵌套循环的,拆分为多个关键字,这里直接贴一个个人小程序首页底部推荐模块的商品有效性校验:从第一个接口拿出所有类目,放到第二个接口去请求拿到所有的商品
*** Test Cases ***
Class_01_推荐专区所有TAP页商品有效性校验
recommend_item
*** Keywords ***
recommend_item
[Documentation] 外层循环关键字:推荐类目
${res} request_get ${swoole_URL} /individual/defaultV2/recommendItem
${cate} getJsonValues scroll_cate ${res.json()}
Remove Values From List ${cate} ${EMPTY}
${cate_len} Get Length ${cate}
FOR ${index} IN RANGE ${cate_len}
product_dump_assertClass ${cate}[${index}] 1
END
product_dump_assertClass
[Arguments] ${cate} ${page}
[Documentation] 内循环关键字:推荐商品有效性校验
${resp} request_get ${swoole_URL} ${path}?page=${page}&${cate}
# 取出所有商品id(未去重)
${matches_product} getJsonValues id ${resp.json()}
Log ${matches_product}
# 取出所有商品图片cover(未去重)
${cover_list} getJsonValues cover ${resp.json()}
Log ${cover_list}
# 遍历所有商品有效性
${matches_product_len} Get Length ${matches_product}
${error_product_id} Create List
${error_product} Create Dictionary
FOR ${index} IN RANGE ${matches_product_len}
${product_id} Set Variable ${matches_product}[${index}]
${resp} request_get ${swoole_URL} /individual/item/view?id=${product_id}
Run Keyword And Continue On Failure Should Be True '${resp.json()["status"]}'=='0'
Run Keyword If '${resp.json()["status"]}'!='0' Run Keywords Append To List ${error_product_id} ${product_id}
... AND Set To Dictionary ${error_product} '${product_id}'=${cover_list}[${index}]
END
Run Keyword If ${error_product_id}==[] Log 推荐商品配置无发现错误商品~
Run Keyword Unless ${error_product_id}==[] Log 错误的商品id列表:${error_product_id}
# 方便测试人员或运营人员根据图片寻找错误商品
Run Keyword Unless ${error_product_id}==[] Log 错误的商品id和图片对应字典:${error_product}
【2.7】类型转化问题
直接说结论,定义变量时可以统一使用$符号,但是使用时,要明确告知robot该变量的类型,否则变量类型可能会被识别错误
定义一个数组可以是:${scalar1} Set Variable 1 2 3
或者是@{scalar1} Set Variable 1 2 3
或者是${scalar1} Create List 1 2 3
或者是@{scalar1} Create List 1 2 3
但是使用时一定是 @{scalar1},而不是 ${scalar1},使用${scalar1}时有可能被转化为Scalar类型
Set Variable定义出来的不一定是Scalar类型
*** Test Cases ***
Class_SetVariable测试
${scalar1} Set Variable 123
${scalar2} Set Variable 123 456
${scalar3} Set Variable abc=123 def=456
${scalar1_type} Evaluate type(${scalar1})
${scalar2_type} Evaluate type(${scalar2})
${scalar3_type} Evaluate type(${scalar3})
# output:
# ${scalar1_type} = <class 'int'>
# ${scalar2_type} = <class 'list'>
# ${scalar3_type} = <class 'list'>
Create List出来的不一定是可迭代的数组,在使用前变量符号要是@才能被认定是数组
*** Test Cases ***
Class_01_类型转化错误例子
# 定义一个数组,尽管定义的是一个Scalar类型,${}
${list1} Create List a b c d
# 判断${list1}类型,output:<class 'list'>,看起来没毛病
Log list1_type:${list1_type}
FOR ${val} IN ${list1}
Log ${val}
END
# output:['a', 'b', 'c', 'd']
# 整个list1被识别成一个值输出,说明在进入for循环时${list1}被当成一个变量处理
Class_02_类型转化正确例子
# 定义一个数组
${list1} Create List a b c
Log list1_type:${list1_type}
# 在使用变量时,明确类型,让robot关键字处理不出错,比如这里list1我们明确是一个list
# 那么就写成@{list1}
FOR ${val} IN @{list1}
Log ${val}
END
# output:
# a
# b
# c
【2.8】${0}和0的区别
稍不注意断言就会提示失败,失败的原因是0==0不成立
1、参数如果是${数字}那么关键字处理的时候会是数值类型,比如${0}为int,${0.5}为double
2、如果参数是初始化的变量,如${scalar2} Set Variable 0,则该变量传递给关键字处理,是以str的形式处理
3、如果传参参数直接写数值,比如TypeTest02 1,则关键字接收到的1是str的形式
测试过程如下
*** Test Cases ***
Class_01_TypeTest
# ${0}被关键字接收到为int,0被关键字接收到为str
TypeTest ${0} 0
# 关键字接收到的scalar1为str
${scalar1} Evaluate str(0)
TypeTest ${0} ${scalar1}
# 以变量形式传入的,都作为str,TypeTest接收到的为str
${scalar2} Set Variable 0
TypeTest ${0} ${scalar2}
*** Keywords ***
TypeTest
[Arguments] ${first} ${second}
${first_type} Evaluate type(${first})
# 这里调用了python的type方法判断,类型转化过了,出来的结果是不准的
${second_type} Evaluate type(${second})
Should Be Equal ${first} ${second}
# output
# ${first_type} = <class 'int'>
# ${second_type} = <class 'int'>
# Argument types are:
# <class 'int'>
# <type 'unicode'>
# FAIL : 0 (integer) != 0 (string)
# <type 'unicode'>和python报出来的一样,python测试结果是<class 'str'>,这里直接给出了该
# 字符串的编码类型为unicode(python3默认str编码类型为unicode)
【2.9】根据标签运行指定用例
例如想在测试服跑核心且正常流的测试用例
则选中Only run tests with these tags为:env_test,P0
同时选中Skip tests with these tags为:exption
这样一来,那些同时带有exption和P0或env_test的用例会被排除外(排除的优先级大于包含的优先级)
实际执行命令:
command: robot --argumentfile C:\Users\ADMINI~1\AppData\Local\Temp\RIDEs5txfbwf.d\argfile.txt --listener C:\Users\Administrator\AppData\Local\programs\Python\Python36\lib\site-packages\robotide\contrib\testrunner\TestRunnerAgent.py:58086:False D:\codeup\rrzuji_RF_API
TestRunnerAgent: Running under CPython 3.6.4
argfile.txt文件内容为:
--include=normal-test
--exclude=exception-test
--outputdir
C:\Users\ADMINI~1\AppData\Local\Temp\RIDEs5txfbwf.d
-C
off
-W
130
【3】自定义Python库
【3.1】开发步骤
很多时候robot和python内置函数都无法满足业务需求,这类实现可以封装到一个python类中,再用robot语法去调用python方法实现业务
自定义python库有两个注意的地方
1、pythonwe文件名必须和类名相同,否则robot调用不了
2、调用时可用类名.方法名或者是直接写方法名
Python示例文件
# File Name:comfunc.py
import re
class comfunc:
def getMatchesFromList(self,param :str,mlist:list):
'''匹配数组中符合特定正则的所有元素
:param pattern:正则表达式
:param mlist:目标数组
:return <class 'list'>:返回一个含所有匹配元素的数组
'''
match_list = []
print("正则:",pattern)
print("数组:",mlist)
for value in mlist:
if isinstance(value,str):
if re.match(pattern,value):
match_list.append(re.match(pattern,value).group())
else:
value_str = str(value)
if re.match(pattern,value_str):
match_list.append(re.match(pattern,value_str).group())
return match_list
【3.2】调用
Robot这边的调用
*** Settings ***
Library ../../../../Resources/Lib/comfunc.py
*** Test Cases ***
Class_01_引入Python方法测试用例
${link_list} Create List ph1542 em129 158 cov01
${matches_product} getMatchesFromList ^([a-z]{2}\\d{1,8}$ ${link_list}
Log matches_product->${matches_product}
【4】开发系统关键字
【4.1】开发步骤
1、在python环境的第三方库路径下新建一个文件夹作为库名称,比如commonfunction
路径下新建一个__init__.py和main.py(main名称可以任意)
新建好的效果如下
2、打开main.py写入
#-*- coding: utf-8 -*-
# File Name: main.py
class Main(object):
def __init__(self):
pass
# 定义一个测试方法
def output(self, msg):
return msg
# 在下面编写你的方法
3、打开__init__.py写入
#-*- coding: utf-8 -*-
# File Name: __init__.py
from commonfunction.main import Main
class commonfunction(Main):
ROBOT_LIBRARY_SCOPE = 'GLOBAL'
【4.2】调用
在RIDE中直接引用库名称(不需要重启RIDE,直接引用,可以识别)
引入成功该库名称显示为黑色,引入不成功为红色字体
【4.3】注意事项
有些时候,无论是修改自定义库或者是往自定义库新增python方法,会在项目内无法成功使用,总是提示找不到该关键字
这是python的一个BUG,触发原因不明,总之就是.py文件无法成功编译为二进制文件.pyc
举个例子,公用库commonfunction库中之前成功使用过一次,那么该库会有一个__pycache__文件夹,包含了该项目所有python文件的编译文件.pyc,
而某次你往commonfunction添加了一个新的方法B(),robot中调用总是提示B这个关键字不存在。
请尝试删除__pycache__文件夹,后再使用robot调用该关键字运行一次,正常情况会重新编译该库,并运行成功
如果以上步骤还不能解决,则需要手动编译(太惨了,我确实遇到了这个问题)
在库目录下(commonfunction文件夹下)打开CMD或者是powershell
输入:python -m py_compile python文件
注意如果有多个python文件,则每个都要编译一次
编译完成后即可成功调用
【5】自定义Python库和系统关键字的区别
【5.1】1、引用方式不一样
*** Settings ***
# Python库引用:测试套件中,需要引用Python库路径
Library ../../../Resources/Lib/comfunc.py
# 系统关键字引用:直接引用库名
Library commonfunction
【5.2】2、通常来说,作用域不同
定义为系统关键字的,往往对于内部项目、外部项目等都通用,比如json遍历方法、数组排序等
定义为python库,则更多是适用内部项目的特定关键字,不宜公开,比如商家app的认证字符串生成,应当定义为python库而不是系统关键字
【5.3】3、项目管理方式不一样
python库直接放在项目的资源文件下,包含在项目代码内
系统关键字无法被项目的版本管控跟踪到(因为系统关键字存放在pip的目录下,不属于项目文件夹)。现阶段不开发系统关键字,因为难以管理,此后等RIDE完整支持docker后,就可以把系统关键字开发后包含到镜像内供调用
【5.4】4、系统关键字不会被频繁修改,而Python库可能会
系统关键字因为具备通用性,编码后,长时间不需要修改都能适用项目(也不应该频繁修改,因为系统关键字通常会在项目内被大量引用,频繁修改可能引发问题)
Python库则可以根据项目情况修改
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
评论已关闭