把自己日常用的比较多的设计方法分享出来,包括常用方法、开发系统关键字、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】如何运行

这节非常重要,学会后:

  1. 可以不依赖工具,直接命令行运行单个用例或套件
  2. 不直接支持robotframework框架的IDE,都可以自行配置RUN参数或Docker环境来满足运行条件
  3. 云效流水线部署、集成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进行开发

选择一同执行的测试用例

image (28).png

筛选特定标签执行

image (29).png

【1.2】输出测试报告(rebot)

【1.2.1】robot和rebot输出测试报告的区别

也许你留意到了当你使用robot运行一个测试套件时,已经默认输出三个测试报告文件,那为什么还需要rebot命令来输出测试报告呢?

试想一下,一个实际的项目里,有几十个到几百个套件运行,每个套件都输出一个测试报告,看完都2077年了。

因此我们需要使用rebot来聚合这些报告,使得一份报告包含所有测试套件测试结果报告

image (30).png

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和不加都能生成测试报告,那么对同个测试套件不同用例执行结果合并,这两个命令生成的报告有什么区别呢?

区别如下图,不加参数的情况,同个测试套件下的用例只是简单的聚合

加参数的情况,同个测试套件下的用例会聚合到一起,成为一个测试套件

image (31).png

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的用例会被排除外(排除的优先级大于包含的优先级)

image (21).png

实际执行命令:

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名称可以任意)

新建好的效果如下

image (22).png

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,直接引用,可以识别)

引入成功该库名称显示为黑色,引入不成功为红色字体

image (23).png

【4.3】注意事项

有些时候,无论是修改自定义库或者是往自定义库新增python方法,会在项目内无法成功使用,总是提示找不到该关键字

这是python的一个BUG,触发原因不明,总之就是.py文件无法成功编译为二进制文件.pyc

举个例子,公用库commonfunction库中之前成功使用过一次,那么该库会有一个__pycache__文件夹,包含了该项目所有python文件的编译文件.pyc,

而某次你往commonfunction添加了一个新的方法B(),robot中调用总是提示B这个关键字不存在。

请尝试删除__pycache__文件夹,后再使用robot调用该关键字运行一次,正常情况会重新编译该库,并运行成功

image (24).png

如果以上步骤还不能解决,则需要手动编译(太惨了,我确实遇到了这个问题)

在库目录下(commonfunction文件夹下)打开CMD或者是powershell

输入:python -m py_compile python文件

注意如果有多个python文件,则每个都要编译一次

image (25).png

编译完成后即可成功调用

【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库则可以根据项目情况修改