首页 > 技术文章 > 【Robot Framework 】项目实战汇总

Detector 2019-03-07 19:04 原文

写在前面

RF自动化的文章记录基本完成,建一个汇总目录,方便查看。

【Robot Framework 项目实战】汇总

∮【RF 项目实战 00】环境搭建

∮【RF 项目实战 01】使用 RequestsLibrary 进行接口测试

∮【RF 项目实战 02】使用脚本生成统一格式的RF关键字

∮【RF 项目实战 03】使用脚本自动生成统一格式的RF自动化用例

∮【RF 项目实战 04】基于录制生成RF关键字及基础自动化用例

脚本优化

上一篇博客我们编写了基于录制生成的RF自动化Demo用例,但是只是完全根据抓包数据构造了一条正常的测试用例,其实针对接口的参数校验这样的重复性比较强的测试,我们也可以把它们脚本化,参数化到RF文件中。

接口测试,基本参数校验范围

基本参数校验,是每次接口测试首要考虑的内容,这部分应当是测试工程师的基本能力。

  1. 可选与必选
    • 字段可选,传与不传时的区别
    • 字段必选,必选参数缺失时返回什么错误,返回格式是否标准
  • 空值
    • 空值时,返回什么结果。(如网络超时未返回,当成空值在处理还是默认值处理)
  • 默认值
    • 是否有默认值,可选参数和必选参数是否有默认值
  • 类型
    • int,区分32和64位,int类型时,传str时的错误展示
    • string类型,
    • float类型,往往在计算金额时,会用到浮点数
  • 类型长度
    • 超过长度,接口请求是否出错,如果不出错,展示是否被截断
    • 中文跨越边界时被截断的问题,如nickname是32字节,共16个中文,前15是中文,第16是英文,第17是中文,则多出一个字节,最后一个中文是否被截断显示为乱码
  • 大小写
    • 是否大小写敏感,signkey,等内容,一般大小写敏感
  • 安全过滤
    • <>是否被过滤掉,是否有直接存写到DB
    • ' " 是否被转译或者使用防SQL注入的ORM组件
  • 编码类型
    • utf-8,GBK,unicode
    • 特殊字符(空格,换行等),emoji(4个字节),中文,拉丁文,日文等

具体实现

首先我们定义一批可能需要传递的参数,使用常量的方式存储。

PARAM_ERR = 10000
OK = 0
BOOL = True  # False
INT = 10
NEG_INT = -1
ZERO = 0
BIG_INT = 99**5
NEG_FLOAT = -1.1
FLOAT = 0.99
EMPTY_STR = ""
INT_STR = "1111"
LETTERS_STR = "ABCabc"
SPACE_STR = " "
EMPTY_LIST = []
STR_LIST = ["a", "b"]
INT_LIST = [10, 20]
BOOL_LIST = [True, False]
MIX_LIST = ["a", True, 10, ["aa"], {"a": "aa"}]
EMPTY_DICT = {}
STR_DICT = {"a": "b"}
INT_DICT = {"d": 11}
BOOL_DICT = {"c": True}
MIX_DICT = {"b": "", "c": True, "d": 11, "e": {"f": [1, 2]}}
...

然后我们通过原始的请求参数,自己组装不同的请求参数:

def gen_req_data(sheet_obj):
    str_params = sheet_obj.cell_value(1, 1)
    str_method = sheet_obj.cell_value(1, 6)
    temp_list = []
    # print(f"str_params:{str_params}")
    if not len(str_params):
        return ""
    try:
        # 转化 json类型为Python标准类型
        params = eval(str_params.replace("false", "False").replace("true", "True").replace("null", "None"))
        if not len(params):
            return ""
    except Exception as f:
        logger.warning("====================================================")
        logger.error("=" + str(f))
        logger.warning("====================================================")
        params = ""

    # 正常参数
    temp_list.append(params)

    # 添加异常参数
    for k, v in params.items():
        if isinstance(v, dict):
            for i in [
                BOOL,
                NEG_FLOAT,
                LETTERS_STR,
                MIX_LIST,
                ZERO,
                MIX_DICT
            ]:
                params = copy.deepcopy(params)  # 深拷贝,字典存储的是内存地址
                params[k] = i  # doc
                temp_list.append(params)
        elif isinstance(v, list):
            for i in [BOOL, NEG_FLOAT, LETTERS_STR, MIX_LIST, ZERO, MIX_DICT]:
                params = copy.deepcopy(params)
                params[k] = i
                temp_list.append(params)
        elif isinstance(v, str):
            for i in [BOOL, NEG_FLOAT, EMPTY_STR, INT_STR, SPACE_STR, MIX_DICT, STR_LIST, EMPTY_DICT]:
                params = copy.deepcopy(params)
                params[k] = i
                temp_list.append(params)
        elif isinstance(v, int):
            for i in [BOOL, NEG_FLOAT, INT_STR, INT_LIST, NEG_INT, ZERO, BIG_INT, NEG_FLOAT, FLOAT, EMPTY_DICT]:
                params = copy.deepcopy(params)
                params[k] = i
                temp_list.append(params)
        elif isinstance(v, bool):
            for i in [True, False, ZERO, FLOAT, NEG_INT, INT_STR, INT_LIST, STR_DICT]:
                params = copy.deepcopy(params)
                params[k] = i
                temp_list.append(params)
    return str_method, temp_list

最后,我们把不同的请求参数转化为RF测试用例需要的数据格式,最终生成对应的测试用例:

def gen_testcase(self, sheet_obj, target_robot_name, interface_name):
    """
    :param sheet_obj:
    :param target_robot_name:
    :param interface_name:
    :return:
    """
    method, params_list = self.gen_req_data(sheet_obj)
    with open(target_robot_name, 'a') as f:
        for num, param in enumerate(params_list):
            """
            待办:动态修改Documentation的信息,异常参数以tuple的形式存储Documentation,然后解析的时候参数化到文档中
            """
            f.write(interface_name + f'.Demo_case{num}' + '\n')
            f.write('    [Documentation]    demo' + '\n')
            f.write('    [Tags]    ' + self.tag + '\n')
            params = self._gen_param_data(method, param)
            f.write(params)
            if params:
                f.write('    ${{Resp_data}}    {}'.format(self.project_abbr) + interface_name + '    ${HOST}    ' + "${param}")
            else:
                f.write('    # 未获取到请求参数数据 \n')
                f.write('    ${{Resp_data}}    {}'.format(self.project_abbr) + interface_name + '    ${HOST}    ' + "${EMPTY}")
            f.write('\n')
            f.write('    Log    ${Resp_data.text}' + '\n')

            # 第一条用例正常断言,其他用例标记为失败用例
            if num == 0:
                f.write('    Should Be Equal As Strings    ${Resp_data.status_code}    200' + '\n')
                f.write('    ${resp_json}    to json    ${Resp_data.text}' + '\n')
                f.write(self.gen_exp_data(sheet_obj))
            else:
                f.write(f'    Should Be Equal As Strings    ${{Resp_data.status_code}}    {self.error_status_code}' + '\n')
                f.write('    ${resp_json}    to json    ${Resp_data.text}' + '\n')
                f.write(self._format_exp_data(self.param_error_json))  # param_error_json 接口异常返回内容中的公共部分,比如code, result
            f.write('\n')
    logger.info("Demo case保存于:" + target_robot_name)

:⚠️ 部分依赖代码在上一篇文章中可以找到。

总结

通过抓包,我们获取了原始数据,然后根据抽象异常参数传递校验的方式,把接口测试异常用例抽取了出来,这个不管是在手工测试过程中,还是在自动化过程中都非常的有用,能节省大量的人力成本。

当然这个方式也有一定的局限性,需要整个团队来配置,比如脚本中使用到的param_error_json就是因为博主所在项目组有比较规范的接口返回定义才能做这样的统一断言。

推荐阅读