Python+pytest+request+allure+yaml接口自动化

Python+pytest+request+allure接口自动化

一、脚本结构

二、配置文件

​ pytest.ini这个文件他是pytest的单元测试框架的核心配置文件

​ 1、位置:项目跟目录

​ 2、编码:必须是ANSI,可以使用notpad++修改编码格式

​ 3、作用:改变pytest默认行为。

​ 4、运行的规则:不管是主函数的模式运行,命令行模式运行,都回去读取这个配置文件

[pytest]
addopts = -vs --alluredir ./temp
testpaths = ./testcases
python_files = test_*.py

项目统一安装模块,放到requirements.txt中,通过pip install -r requirements.txt

验证是否安装成功:pytest -V

pytest
pytest-html  (生成htm格式的自动化测试报告)
pytest-xdist   测试用例分布式执行,多cpu分发
pytest-ordering   用户改版测试用例的执行顺序(从上到下)
pytest-rerunfailures   用例失败后重跑
allure-pytest   用于生成美观的测试报告

三、公共模块部分代码

conparator.py

class comparators:
    # 判断接口相应码
    def assertEqual(self, check, expect):
        if check == expect:
            assert check == expect
            logger.info("断言 ==>> 结果 ==>>通过 ")
            return True
        else:
            logger.info("断言 ==>> 结果 ==>>失败 ")
            logger.info("接口返回码是 【 {} 】, 预计接口返回码:{} ".format(check, expect))
            return False

    # 判断接口返回内容是否包含关键字
    def assertIn(self, check, expect):
        if check == expect:
            assert check in expect
            logger.info("断言 ==>> 结果 ==>>通过 ")
            return True
        else:
            logger.info("断言 ==>> 结果 ==>>失败 ")
            logger.info("接口关键字是 【 {} 】, 预计接口关键字:{} ".format(check, expect))
            return False

logger.py

BASE_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
# 定义日志文件路径
LOG_PATH = os.path.join(BASE_PATH, "log")
if not os.path.exists(LOG_PATH):
    os.mkdir(LOG_PATH)


class Logger:
    def __init__(self):
        self.logname = os.path.join(LOG_PATH, "{}.log".format(time.strftime("%Y%m%d")))
        self.logger = logging.getLogger("log")
        self.logger.setLevel(logging.DEBUG)

        self.formater = logging.Formatter(
            '[%(asctime)s][%(filename)s %(lineno)d][%(levelname)s]: %(message)s')

        self.filelogger = logging.FileHandler(self.logname, mode='a', encoding="UTF-8")
        self.console = logging.StreamHandler()
        self.console.setLevel(logging.DEBUG)
        self.filelogger.setLevel(logging.DEBUG)
        self.filelogger.setFormatter(self.formater)
        self.console.setFormatter(self.formater)
        self.logger.addHandler(self.filelogger)
        self.logger.addHandler(self.console)

read_yaml.py

class ReadFileYaml:
    def __init__(self):
        pass

    def read_yaml(self, file_path):
        """读取yaml文件"""

        logger.info("加载{}文件...........".format(file_path))
        with open(file_path, encoding='utf-8') as f:
            data = yaml.safe_load(f)
        logger.info("读取到的数据===》{}".format(data))
        return data

    def write_yaml(self, data, file_path):
        """向yaml文件写入数据"""
        with open(file_path, encoding='utf-8', mode='w') as f:
            return yaml.dump(data, stream=f, allow_unicode=True)

    def update_yaml(self, path, key, value):
        """向yaml文件修改数据"""
        logger.info("开始修改数据")
        doc = self.read_yaml(path)
        doc[key] = value
        self.write_yaml(doc,path)
        logger.info("修改后到的数据===》{}".format(doc))

rest_client.py

class RestClient:

    def __init__(self, api_root_url):
        self.api_root_url = api_root_url
        self.session = requests.session()

    def get(self, url, **kwargs):
        return self.request(url, "GET", **kwargs)

    def post(self, url, data=None, json=None, **kwargs):
        return self.request(url, "POST", data, json, **kwargs)

    def put(self, url, data=None, **kwargs):
        return self.request(url, "PUT", data, **kwargs)

    def delete(self, url, **kwargs):
        return self.request(url, "DELETE", **kwargs)

    def patch(self, url, data=None, **kwargs):
        return self.request(url, "PATCH", data, **kwargs)

    def request(self, url, method, data=None, json=None, **kwargs):
        url = self.api_root_url + url
        headers = dict(**kwargs).get("headers")
        params = dict(**kwargs).get("params")
        files = dict(**kwargs).get("files")
        cookies = dict(**kwargs).get("cookies")

        self.request_log(url, method, data, json, params, headers, files, cookies)
        if method == "GET":
            return self.session.get(url, **kwargs)
        if method == "POST":
            return requests.post(url, data, json, **kwargs)
        if method == "PUT":
            if json:
                # PUT 和 PATCH 中没有提供直接使用json参数的方法,因此需要用data来传入
                data = complexjson.dumps(json)
            return self.session.put(url, data, **kwargs)
        if method == "DELETE":
            return self.session.delete(url, **kwargs)
        if method == "PATCH":
            if json:
                data = complexjson.dumps(json)
            return self.session.patch(url, data, **kwargs)

    def request_log(self, url, method, data=None, json=None, params=None, headers=None, files=None, cookies=None, **kwargs):
        logger.info("接口请求地址 ==>> {}".format(url))
        logger.info("接口请求方式 ==>> {}".format(method))
        # Python3中,json在做dumps操作时,会将中文转换成unicode编码,因此设置 ensure_ascii=False
        logger.info("接口请求头 ==>> {}".format(complexjson.dumps(headers, indent=4, ensure_ascii=False)))
        logger.info("接口请求 params 参数 ==>> {}".format(complexjson.dumps(params, indent=4, ensure_ascii=False)))
        logger.info("接口请求体 data 参数 ==>> {}".format(complexjson.dumps(data, indent=4, ensure_ascii=False)))
        logger.info("接口请求体 json 参数 ==>> {}".format(complexjson.dumps(json, indent=4, ensure_ascii=False)))
        logger.info("接口上传附件 files 参数 ==>> {}".format(files))
        logger.info("接口 cookies 参数 ==>> {}".format(complexjson.dumps(cookies, indent=4, ensure_ascii=False)))

四、逻辑层部分代码

​ 逻辑层代码,使用request二次封装请求接口传入参数。我的几口基本都是post,所以基本每一个api逻辑层都是一样的,就是名字不一样。

from core.rest_client import RestClient

class BasicUserInfo(RestClient):
    def __init__(self, api_root_url,**kwargs):
        super(BasicUserInfo, self).__init__(api_root_url, **kwargs)

    def basic_user_info(self, url, **kwargs):
        return self.post(url, **kwargs)

五、操作层

​ api在请求接口前有什么操作,需要什么动态参数,或者接口关联测试(上一个接口的返回内容,作为这个接口的请求参数),我这里关联接口处理方法是把数据存入yaml文件,然后请求的时候把数据读取出来,这只是我想的方法(可以参考),处理方法很多,根据自身的接口可以做相应的处理。

六、应用层

​ 使用的测试框架是pytest,也可以使用unittest框架,根据自己的要求来,我觉得pytest更加灵活比unittest好用,个人意见勿喷。(这里对失败用例,和跳过用例等,没有做任何处理)

now_file = os.path.abspath(".")
file_path = now_file.replace('\\', '/')
readfileyaml = ReadFileYaml()
case = readfileyaml.read_yaml(file_path + '/data/dim_trend_case.yaml')

@allure.severity(allure.severity_level.NORMAL)
@allure.feature("监控概括数据")
class TestGeneralize:
    @pytest.mark.parametrize('data', case)
    def test_01_dimension_trend_this_week(self, data):
        allure.dynamic.title(data['detail'])
        allure.dynamic.description(data['detail'])
        # 发起请求
        result = get_dimension_trend(data['url'], data['data'], data['headers'])
        # 断言判断
        asse = comparators().assertEqual(str(result['code']), str(data['resp']['code']))
        if not asse:
            pytest.xfail(reason="这个用例实际返回结果与预期一致")

七、启动文件

run.py

if __name__ == "__main__": 
   pytest.main()    
   os.system('allure generate ./temp -o ./report  --clean-alluredir')   
    # 我这里的cookies十二个小时过期,一般自动化脚本一天执行一次,我执行完了就把cookie清楚了    
    open(file_path + "/common/cookies.txt", 'w').close()

七、运行成果在这里插入图片描述

  • 5
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值