接口自动化框架2-升级版(Pytest+request+Allure)

前言:

接口自动化是指模拟程序接口层面的自动化,由于接口不易变更,维护成本更小,所以深受各大公司的喜爱。
第一版入口:接口自动化框架(Pytest+request+Allure)
本次版本做了一些升级,增加了自动生成testcase等,一起来看看吧!~~


一、简单介绍

环境:Mac+Python 3+Pytest+Allure+Request

pytest==3.6.0
pytest-allure-adaptor==1.7.10
pytest-rerunfailures==5.0
allure-python-commons==2.7.0
configparser==3.5.0
PyYAML==3.12
requests==2.18.4
simplejson==3.16.0

流程:Charles导出接口数据-自动生成测试用例-修改测试用例-执行测试用例-生成Allure报告
开源: 点击这里,跳转到github
备注⚠️:Charles导出接口应选择文件类型为JSON Session File(.chlsj)

重要模块介绍:
1、writeCase.py :自动读取新的Charles文件,并自动生成测试用例
2、apiMethod.py:封装request方法,可以支持多协议扩展(get\post\put)
3、checkResult.py:封装验证response方法
4、setupMain.py: 核心代码,定义并执行用例集,生成报告

⚠️2020-5-29更新:
pytest-allure-adaptor已弃用,更换为allure-pytest
pip uninstall pytest-allure-adaptor
pip install allure-pytest
代码已更新。 点击这里,跳转到github

__

二、目录介绍

Aff_service.png

三、代码分析

1、测试数据yml(自动生成的yml文件)

# 用例基本信息
test_info:
      # 用例标题,在报告中作为一级目录显示
      title: blogpost
      # 用例ID
      id: test_reco_01
      # 请求的域名,可写死,也可写成模板关联host配置文件
      host: ${host}$
      # 请求地址 选填(此处不填,每条用例必填)
      address: /api/v2/recomm/blogpost/reco

# 前置条件,case之前需关联的接口
premise:

# 测试用例
test_case:
    - test_name: reco_1
      # 第一条case,info可不填
      info: reco
      # 请求协议
      http_type: https
      # 请求类型
      request_type: POST
      # 参数类型
      parameter_type: application/json
      # 请求地址
      address: /api/v2/recomm/blogpost/reco
      # 请求头
      headers:
      # parameter为文件路径时
      parameter: reco.json
      # 是否需要获取cookie
      cookies: False
      # 是否为上传文件的接口
      file: false
      # 超时时间
      timeout: 20

      # 校验列表  list or dict
      # 不校验时 expected_code, expected_request 均可不填
      check:
        expected_request: result_reco.json
        check_type: only_check_status
        expected_code: 503

      # 关联键
      relevance:

2、测试case(自动生成的case.py)

@allure.feature(case_dict["test_info"]["title"])
class TestReco:

    @pytest.mark.parametrize("case_data", case_dict["test_case"], ids=[])
    @allure.story("reco")
    @pytest.mark.flaky(reruns=3, reruns_delay=3)
    def test_reco(self, case_data):
        """

        :param case_data: 测试用例
        :return:
        """
        self.init_relevance = ini_request(case_dict, PATH)
        # 发送测试请求
        api_send_check(case_data, case_dict, self.init_relevance, PATH)

3、writeCase.py (封装方法:自动生成测试case)

def write_case(_path):
    yml_list = write_case_yml(_path)
    project_path = str(os.path.abspath('.').split('/bin')[0])
    test_path = project_path+'/aff/testcase/'
    src = test_path+'Template.py'

    for case in yml_list:
        yml_path = case.split('/')[0]
        yml_name = case.split('/')[1]
        case_name = 'test_' + yml_name + '.py'
        new_case = test_path + yml_path + '/' + case_name
        mk_dir(test_path + yml_path)
        if case_name in os.listdir(test_path + yml_path):
            pass
        else:
            shutil.copyfile(src, new_case)
            with open(new_case, 'r') as fw:
                source = fw.readlines()
            n = 0
            with open(new_case, 'w') as f:
                for line in source:
                    if 'PATH = setupMain.PATH' in line:
                        line = line.replace("/aff/page/offer", "/aff/page/%s" % yml_path)
                        f.write(line)
                        n = n+1
                    elif 'case_dict = ini_case' in line:
                        line = line.replace("Template", yml_name)
                        f.write(line)
                        n = n + 1
                    elif 'class TestTemplate' in line:
                        line = line.replace("TestTemplate", "Test%s" % yml_name.title().replace("_", ""))
                        f.write(line)
                        n = n + 1
                    elif '@allure.story' in line:
                        line = line.replace("Template", yml_name)
                        f.write(line)
                        n = n + 1
                    elif 'def test_template' in line:
                        line = line.replace("template", yml_name.lower())
                        f.write(line)
                        n = n + 1

                    else:
                        f.write(line)
                        n += 1
                for i in range(n, len(source)):
                    f.write(source[i])

4、apiMethod.py(封装方法:http多协议)

def post(header, address, request_parameter_type, timeout=8, data=None, files=None):
    """
    post请求
    :param header: 请求头
    :param address: 请求地址
    :param request_parameter_type: 请求参数格式(form_data,raw)
    :param timeout: 超时时间
    :param data: 请求参数
    :param files: 文件路径
    :return:
    """
    if 'form_data' in request_parameter_type:
        for i in files:
            value = files[i]
            if '/' in value:
                file_parm = i
                files[file_parm] = (os.path.basename(value), open(value, 'rb'))
        enc = MultipartEncoder(
            fields=files,
            boundary='--------------' + str(random.randint(1e28, 1e29 - 1))
        )
        header['Content-Type'] = enc.content_type

        response = requests.post(url=address, data=enc, headers=header, timeout=timeout)
    else:
        response = requests.post(url=address, data=data, headers=header, timeout=timeout, files=files)
    try:
        if response.status_code != 200:
            return response.status_code, response.text
        else:
            return response.status_code, response.json()
    except json.decoder.JSONDecodeError:
        return response.status_code, ''
    except simplejson.errors.JSONDecodeError:
        return response.status_code, ''
    except Exception as e:
        logging.exception('ERROR')
        logging.error(e)
        raise

5、checkResult.py(封装方法:校验response结果)

def check_result(test_name, case, code, data, _path, relevance=None):
    """
    校验测试结果
    :param test_name: 测试名称
    :param case: 测试用例
    :param code: HTTP状态
    :param data: 返回的接口json数据
    :param relevance: 关联值对象
    :param _path: case路径
    :return:
    """
    # 不校验结果
    if case["check_type"] == 'no_check':
        with allure.step("不校验结果"):
            pass
    # json格式校验
    elif case["check_type"] == 'json':
        expected_request = case["expected_request"]
        if isinstance(case["expected_request"], str):
            expected_request = readExpectedResult.read_json(test_name, expected_request, _path, relevance)
        with allure.step("JSON格式校验"):
            allure.attach("期望code", str(case["expected_code"]))
            allure.attach('期望data', str(expected_request))
            allure.attach("实际code", str(code))
            allure.attach('实际data', str(data))
        if int(code) == case["expected_code"]:
            if not data:
                data = "{}"
            check_json(expected_request, data)
        else:
            raise Exception("http状态码错误!\n %s != %s" % (code, case["expected_code"]))
    # 只校验状态码
    elif case["check_type"] == 'only_check_status':
        with allure.step("校验HTTP状态"):
            allure.attach("期望code", str(case["expected_code"]))
            allure.attach("实际code", str(code))
            allure.attach('实际data', str(data))
        if int(code) == case["expected_code"]:
            pass
        else:
            raise Exception("http状态码错误!\n %s != %s" % (code, case["expected_code"]))
    # 完全校验
    elif case["check_type"] == 'entirely_check':
        expected_request = case["expected_request"]
        if isinstance(case["expected_request"], str):
            expected_request = readExpectedResult.read_json(test_name, expected_request, _path, relevance)
        with allure.step("完全校验"):
            allure.attach("期望code", str(case["expected_code"]))
            allure.attach('期望data', str(expected_request))
            allure.attach("实际code", str(code))
            allure.attach('实际data', str(data))
        if int(code) == case["expected_code"]:
            result = operator.eq(expected_request, data)
            if result:
                pass
            else:
                raise Exception("完全校验失败! %s ! = %s" % (expected_request, data))
        else:
            raise Exception("http状态码错误!\n %s != %s" % (code, case["expected_code"]))
    # 正则校验
    elif case["check_type"] == 'Regular_check':
        if int(code) == case["expected_code"]:
            try:
                result = ""
                if isinstance(case["expected_request"], list):
                    for i in case[""]:
                        result = re.findall(i.replace("\"","\""), str(data))
                        allure.attach('校验完成结果\n',str(result))
                else:
                    result = re.findall(case["expected_request"].replace("\"", "\'"), str(data))
                    with allure.step("正则校验"):
                        allure.attach("期望code", str(case["expected_code"]))
                        allure.attach('正则表达式', str(case["expected_request"]).replace("\'", "\""))
                        allure.attach("实际code", str(code))
                        allure.attach('实际data', str(data))
                        allure.attach(case["expected_request"].replace("\"", "\'") + '校验完成结果',
                                      str(result).replace("\'", "\""))
                if not result:
                    raise Exception("正则未校验到内容! %s" % case["expected_request"])
            except KeyError:
                raise Exception("正则校验执行失败! %s\n正则表达式为空时" % case["expected_request"])
        else:
            raise Exception("http状态码错误!\n %s != %s" % (code, case["expected_code"]))

    else:
        raise Exception("无该校验方式%s" % case["check_type"])

6、setupMain.py(执行用例集,生成测试报告)

def invoke(md):
    output, errors = subprocess.Popen(md, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
    o = output.decode("utf-8")
    return o


if __name__ == '__main__':
    LogConfig(PATH)
    write_case(har_path)
    args = ['-s', '-q', '--alluredir', xml_report_path]
    pytest.main(args)
    cmd = 'allure generate %s -o %s' % (xml_report_path, html_report_path)
    invoke(cmd)

7、测试报告
Allure报告.png

以上,喜欢的话请点赞❤️吧~
欢迎关注我的简书,博客,TesterHome,Github~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容