Pytest接口自动化测试实战演练

结合单元测试框架pytest+数据驱动模型+allure

目录

api: 存储测试接口

conftest.py :设置前置操作

             目前前置操作:1、获取token并传入headers,2、获取命令行参数给到环境变量,指定运行环境

commmon:存储封装的公共方法

         connect_mysql.py:连接数据库
         http_requests.py: 封装自己的请求方法
         logger.py: 封装输出日志文件
         read_yaml.py:读取yaml文件测试用例数据
         read_save_data.py:读取保存的数据文件

case: 存放所有的测试用例
      
data:存放测试需要的数据
      save_data: 存放接口返回数据、接口下载文件
      test_data: 存放测试用例依赖数据
      upload_data: 存放上传接口文件

logs: 存放输出的日志文件

report: 存放测试输出报告

getpathinfo.py :封装项目测试路径

pytest.int :配置文件

requirement.txt: 本地python包(pip install -r requirements.txt 安装项目中所有python包)

run_main.py: 项目运行文件

结构设计

1.每一个接口用例组合在一个测试类里面生成一个py文件

2.将每个用例调用的接口封装在一个测试类里面生成一个py文件

3.将测试数据存放在yml文件中通过parametrize进行参数化,实现数据驱动

4.通过allure生成测试报告

代码展示

api/api_service.py #需要测试的一类接口

'''
Code description:服务相关接口
Create time: 2020/12/3
Developer: 叶修
'''
import os
from common.http_requests import HttpRequests


class Api_Auth_Service(object):

    def __init__(self):
        self.headers = HttpRequests().headers

    def api_home_service_list(self):
        # 首页服务列表
        # url = "http://192.168.2.199:9092/v1/auth/auth_service/findAuthService"
        url = os.environ["host"] + "/v1/auth/auth_service/findAuthService"  # 读取conftest.py文件地址进行拼接
        response = HttpRequests().get(url, headers=self.headers, verify=False)
        # print(response.json())
        return response

    def get_service_id(self):
        #获取银行卡三要素认证服务id
        url = "http://192.168.2.199:9092/v1/auth/auth_service/findAuthService"
        #url = os.environ["host"] + "/v1/auth/auth_service/findAuthService"  # 读取conftest.py文件地址进行拼接
        response = HttpRequests().get(url,headers=self.headers)
        #print(response.json()['data'][0]['service_list'][0]['id'])
        service_id = response.json()['data'][0]['service_list'][1]['id']
        return service_id

    def api_service_info(self,serviceId='0b6cf45bec757afa7ee7209d30012ce1',developerId=''):
        #服务详情
        body = {
            "serviceId" :serviceId,
            "developerId":developerId
        }
        url = "http://192.168.2.199:9092/v1/auth/auth_service/findServiceDetail"
        #url = os.environ["host"] + "/v1/auth/auth_service/findServiceDetail"#读取conftest.py文件地址进行拼接
        response = HttpRequests().get(url,headers=self.headers,params = body,verify=False)
        #print(response.json())
        return response

    def api_add_or_update_service(self,api_param_req,api_param_res,description,error_code,icon,id,interface_remarks,
                              name,product_info,request_method,sample_code,sort,type,url):
        #服务添加或者更新
        body={
                "api_param_req": api_param_req,
                "api_param_res": api_param_res,
                "description": description,
                "error_code": error_code,
                "icon": icon,
                "id": id,
                "interface_remarks": interface_remarks,
                "name": name,
                "product_info": product_info,
                "request_method": request_method,
                "sample_code": sample_code,
                "sort": sort,
                "type": type,
                "url": url,
        }
        #url = "http://192.168.2.199:9092/v1/auth/auth_service/insertOrUpdateService"
        url = os.environ["host"] + "/v1/auth/auth_service/insertOrUpdateService"  # 读取conftest.py文件地址进行拼接
        response = HttpRequests().post(url,json=body,headers=self.headers,verify=False)
        return response

    def api_add_service_price(self,id,max_number,money,service_id,small_number):
        #服务价格添加
        body = {
            "id": id,
            "max_number": max_number,
            "money": money,
            "service_id": service_id,
            "small_number": small_number
        }
        # url = "http://192.168.2.199:9092/v1/auth/auth_service/insertServicePrice"
        url = os.environ["host"] + "/v1/auth/auth_service/insertServicePrice"  # 读取conftest.py文件地址进行拼接
        response = HttpRequests().post(url, json=body, headers=self.headers, verify=False)
        return response

    def api_apply_service(self,developer_id,service_id):
        #申请服务
        body ={
            "developer_id": developer_id,
            "service_id": service_id
        }
        # url = "http://192.168.2.199:9092/v1/auth/auth_service/applyService"
        url = os.environ["host"] + "/v1/auth/auth_service/applyService"  # 读取conftest.py文件地址进行拼接
        response = HttpRequests().post(url, json=body, headers=self.headers, verify=False)
        return response


if __name__ == '__main__':
    #Auth_Service().api_home_service_list()
    Api_Auth_Service().get_service_id()
    #Auth_Service().api_service_info()

 api/get_token.py#获取登录token

'''
Code description:获取token
Create time:2020-12-03
Developer:叶修
'''
import os

import urllib3
from common.http_requests import HttpRequests


class Get_Token(object):

    def get_token(self,account='****',password='****'):
        #url = "http://192.168.2.199:9092/v1/auth/developer/accountLogin"
        url = os.environ["host"]+"/v1/auth/developer/accountLogin"
        body = {
            "account": account,
            "password": password,
        }
        urllib3.disable_warnings()
        r = HttpRequests().post(url, json=body,verify=False)
        #print(r.json())
        token = r.json()['data']['token']
        params = {
            "access_token": token
        }
        HttpRequests().params.update(params)#更新token到session
        return token

if __name__ == '__main__':
    print(Get_Token().get_token())

case/test_service_info.py #上面接口某一测试用例

'''
Code description: 服务详情
Create time: 2020/12/3
Developer: 叶修
'''
import sys
import allure
import pytest
from common.logger import Log
from common.read_yaml import ReadYaml
from api.api_auth_service.api_auth_service import Api_Auth_Service

testdata = ReadYaml("auth_service.yml").get_yaml_data()  # 读取数据


@allure.feature('服务详情')
class Test_Service_Info(object):
    log = Log()

    @pytest.mark.process
    @pytest.mark.parametrize('serviceId,developerId,expect', testdata['service_info'],
                             ids=['服务详情'])
    def test_service_info(self,serviceId,developerId,expect):
        self.log.info('%s{%s}' % ((sys._getframe().f_code.co_name,'------服务详情接口-----')))
        with allure.step('获取服务id'):
            serviceId = Api_Auth_Service().get_service_id()
        with allure.step('服务详情'):
            msg = Api_Auth_Service().api_service_info(serviceId,developerId)
        self.log.info('%s:%s' % ((sys._getframe().f_code.co_name, '获取请求结果:%s' % msg.json())))
        # 断言
        assert msg.json()["result_message"] == expect['result_message']
        assert msg.json()['result_code'] == expect['result_code']
        assert 'url' in msg.json()['data']

 conftest.py

'''
Code description:配置信息
Create time: 2020/12/3
Developer: 叶修
'''
import os
import pytest
from api.get_token import Get_Token
from common.http_requests import HttpRequests


@pytest.fixture(scope="session")
def get_token():
    '''前置操作获取token并传入headers'''
    Get_Token().get_token()
    if not HttpRequests().params.get("access_token", ""):#没有get到token,跳出用例
        pytest.skip("未获取token跳过用例")
    yield HttpRequests().req
    HttpRequests().req.close()

def pytest_addoption(parser):
    parser.addoption(
        "--cmdhost", action="store", default="http://192.168.1.54:32099",
        help="my option: type1 or type2"
    )
@pytest.fixture(scope="session",autouse=True)
def host(request):
    '''获取命令行参数'''
    #获取命令行参数给到环境变量
    #pytest --cmdhost 运行指定环境
    os.environ["host"] = request.config.getoption("--cmdhost")
    print("当前用例运行测试环境:%s" % os.environ["host"])

 common/connect_mysql.py

'''
Code description: 配置连接数据库
Create time: 2020/12/3
Developer: 叶修
'''
import pymysql

dbinfo = {
    "host":"******",
    "user":"root",
    "password":"******",
    "port":31855
}
class DbConnect():
    def __init__(self,db_conf,database=""):
        self.db_conf = db_conf
        #打开数据库
        self.db = pymysql.connect(database = database,
                                  cursorclass = pymysql.cursors.DictCursor,
                                  **db_conf)
        #使用cursor()方式获取操作游标
        self.cursor = self.db.cursor()

    def select(self,sql):
        #sql查询
        self.cursor.execute(sql)#执行sql
        results = self.cursor.fetchall()
        return results

    def execute(self,sql):
        #sql 删除 提示 修改
        try:
            self.cursor.execute(sql)#执行sql
            self.db.commit()#提交修改
        except:
            #发生错误时回滚
            self.db.rollback()

    def close(self):
        self.db.close()#关闭连接

def select_sql(select_sql):
    '''查询数据库'''
    db = DbConnect(dbinfo,database='auth_platform')
    result = db.select(select_sql)
    db.close()
    return result

def execute_sql(sql):
    '''执行SQL'''
    db = DbConnect(dbinfo,database='auth_platform')
    db.execute(sql)
    db.close()

if __name__ == '__main__':
    sql = "SELECT * FROM auth_platform.auth_service where name='四要素认证'"
    sel = select_sql(sql)[0]['name']
    print(sel)

 common/http_requests.py

'''
Code description: 封装自己的请求类型
Create time: 2020/12/3
Developer: 叶修
'''

import requests


# 定义一个HttpRequests的类
class HttpRequests(object):

    req = requests.session()#定义session会话
    # 定义公共请求头
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36',
        'cookie':''
    }
    params = {
        'access_token':''
    }
    # 封装自己的get请求,获取资源
    def get(self, url='', params='', data='', headers=None, cookies=None,stream=None,verify=None):
        response = self.req.get(url,params=params,data=data,headers=headers,cookies=cookies,stream=stream,verify=verify)
        return response

    # 封装自己的post方法,创建资源
    def post(self, url='', params='',data='', json='', headers=None, cookies=None,stream=None,verify=None):
        response = self.req.post(url,params=params,data=data,json=json,headers=headers,cookies=cookies,stream=stream,verify=verify)
        return response

    # 封装自己的put方法,更新资源
    def put(self, url='', params='', data='', headers=None, cookies=None,verify=None):
        response = self.req.put(url, params=params, data=data, headers=headers, cookies=cookies,verify=verify)
        return response

    # 封装自己的delete方法,删除资源
    def delete(self, url='', params='', data='', headers=None, cookies=None,verify=None):
        response = self.req.delete(url, params=params, data=data, headers=headers, cookies=cookies,verify=verify)
        return response

 common/logger.py

'''
Code description: 封装输出日志文件
Create time: 2020/12/3
Developer: 叶修
'''
import logging,time
import os
import getpathinfo

path = getpathinfo.get_path()#获取本地路径
log_path = os.path.join(path,'logs')# log_path是存放日志的路径
# 如果不存在这个logs文件夹,就自动创建一个
if not os.path.exists(log_path):os.mkdir(log_path)

class Log():
    def __init__(self):
        #文件的命名
        self.logname = os.path.join(log_path,'%s.log'%time.strftime('%Y_%m_%d'))
        self.logger = logging.getLogger()
        self.logger.setLevel(logging.DEBUG)
        #日志输出格式
        self.formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

    def __console(self,level,message):
        #创建一个fileHander,用于写入本地
        fh = logging.FileHandler(self.logname,'a',encoding='utf-8')
        fh.setLevel(logging.DEBUG)
        fh.setFormatter(self.formatter)
        self.logger.addHandler(fh)

        #创建一个StreamHandler,用于输入到控制台
        ch = logging.StreamHandler()
        ch.setLevel(logging.DEBUG)
        ch.setFormatter(self.formatter)
        self.logger.addHandler(ch)

        if level == 'info':
            self.logger.info(message)
        elif level == 'debug':
            self.logger.debug(message)
        elif level == 'warning':
            self.logger.warning(message)
        elif level == 'error':
            self.logger.error(message)
        #避免日志重复
        self.logger.removeHandler(fh)
        self.logger.removeHandler(ch)
        #关闭打开文件
        fh.close()

    def debug(self,message):
        self.__console('debug',message)

    def info(self,message):
        self.__console('info',message)

    def warning(self,message):
        self.__console('warning',message)

    def error(self,message):
        self.__console('error',message)

if __name__ == '__main__':
    log = Log()
    log.info('测试')
    log.debug('测试')
    log.warning('测试')
    log.error('测试')

 common/read_save_data.py

'''
Code description: 读取保存数据
Create time: 2020/12/8
Developer: 叶修
'''
import os
import yaml
import getpathinfo
class Read_Save_Date():
    def __init__(self):
        path = getpathinfo.get_path()#获取本地路径
        self.head_img_path = os.path.join(path,'data','save_data')+"/"+'head_img_path.txt'# head_img_path文件地址
        self.order_id_path = os.path.join(path, 'data', 'save_data') + "/" + 'order_id.txt'  # order_id.txt文件地址

    def get_head_img_path(self):
        # 获取head_img_path
        with open(self.head_img_path, "r", encoding="utf-8")as f:
            return f.read()

    def get_order_id(self):
        # 获取order_id
        with open(self.order_id_path, "r", encoding="utf-8")as f:
            return f.read()

if __name__ == '__main__':
    print(Read_Save_Date().get_head_img_path())
    print(Read_Save_Date().get_order_id())

 common/read_yaml.py

'''
Code description: 读取yml文件测试数据
Create time: 2020/12/3
Developer: 叶修
'''
import os
import yaml
import getpathinfo
class ReadYaml():
    def __init__(self,filename):
        path = getpathinfo.get_path()#获取本地路径
        self.filepath = os.path.join(path,'data','test_data')+"/"+filename#拼接定位到data文件夹

    def get_yaml_data(self):
        with open(self.filepath, "r", encoding="utf-8")as f:
            # 调用load方法加载文件流
            return yaml.load(f,Loader=yaml.FullLoader)

if __name__ == '__main__':
    data = ReadYaml("auth_service.yml").get_yaml_data()['add_or_update_service']
    print(data)

data/

 data/test_data/auth_service.yml

home_service_list:
  - [{'result_code': '0', 'result_message': '处理成功'}]
service_info:
  - ['','',{'result_code': '0', 'result_message': '处理成功'}]
add_or_update_service:
  - [['1','1','1','1','1','123456','1','测试','1','1','1','1','1','1'],{'result_code': '0', 'result_message': '处理成功'}]
add_service_price:
  - ['123456789','10','0','','0',{'result_code': '0', 'result_message': '处理成功'}]
apply_service:
  - ['','',{'result_code': '0', 'result_message': '处理成功'}]

 logs/

 report/

 getpathinfo.py

'''
Code description:配置文件路径
Create time: 2020/12/3
Developer: 叶修
'''
import os

def get_path():
    # 获取当前路径
    curpath = os.path.dirname(os.path.realpath(__file__))
    return curpath

if __name__ == '__main__':# 执行该文件,测试下是否OK
    print('测试路径是否OK,路径为:', get_path())

pytest.ini

#pytest.ini
[pytest]
markers = process
addopts = -p no:warnings
#addopts = -v --reruns 1 --html=./report/report.html --self-contained-html
#addopts = -v --reruns 1 --alluredir ./report/allure_raw
#addopts = -v -s -p no:warnings --reruns  1 --pytest_report ./report/Pytest_Report.html

requirements.txt

allure-pytest==2.8.18
allure-python-commons==2.8.18
BeautifulReport==0.1.3
beautifulsoup4==4.9.3
ddt==1.4.1
Faker==4.18.0
Flask==1.1.1
httpie==1.0.3
httplib2==0.9.2
HttpRunner==1.5.8
py==1.9.0
PyMySQL==0.10.1
pytest==6.1.1
pytest-base-url==1.4.2
pytest-cov==2.10.1
pytest-forked==1.3.0
pytest-html==2.1.1
pytest-instafail==0.4.2
pytest-metadata==1.10.0
pytest-mock==3.3.1
pywin32==228
PyYAML==5.3.1
requests==2.22.0
requests-oauthlib==1.3.0
requests-toolbelt==0.9.1

run_main.py

'''
Code description: 运行主流程测试用例
Create time: 2020/11/5
Developer: 叶修
'''
import os
import pytest
if __name__ == '__main__':
    pytest.main(['-m','process', '-s','--alluredir', 'report/tmp'])#-m运行mark标记文件
    os.system('allure generate report/tmp -o report/html --clean') # /report/tmp 为存放报告的源文件目录

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/15172.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【计算机是怎么跑起来的】基础:计算机三大原则

【计算机是怎么跑起来的】基础:计算机三大原则 计算机的三个根本性基础1.计算机是执行输入,运算,输出的机器输入,运算,输出 2. 软件是指令和数据的集合指令数据 3. 计算机的处理方式有时与人们的思维习惯不同对计算机来…

如何做好采购计划和库存管理?

“销售计划不专业且不稳定”“准确性低” “目前只按照过往销量和采购周期做安全库存,但欠货和滞销依然严重” 题主的问题其实蛮有代表性的, 也是传统采购和库存管理常常面临的问题: ① 前后方协作困难 采购/销售/财务工作相互独立&#x…

NetXpert XG2帮您解决“布线安装与维护”难题

在传输大量数据时,光纤变得越来越重要,而铜缆在未来也将继续发挥重要作用,因此我们不仅要比较两种类型布线的优缺点,还要探究光纤传输中的错误来源。 测试光缆传输损耗的准确性对于故障排除至关重要,特别是在光纤情况下…

2023五一数学建模竞赛(五一赛)选题建议

提示&#xff1a;DS C君认为的难度&#xff1a;C<A<B&#xff0c;开放度&#xff1a;B<A<C 。 A题&#xff1a;无人机定点投放问题 这道题是传统的物理类题目&#xff0c;基本每次建模竞赛都会有。由于这道题目并未给明数据&#xff0c;所以数据获取和搜集资料是…

来了来了,我使用 ChatGPT 开发了一个 AI 应用

ChatGpt 实在太火爆了&#xff0c;很多人在问我怎么使用 chatgpt 开发一个 AI 应用程序。这不就来了吗~ 开始 你所需要准备的一个OpenAI 的密钥和一点点代码来发送提示并返回结果&#xff0c;例如下面这段代码&#xff1a; import { OpenAIApi, Configuration } from openai…

超火爆的ChatGPT课,送ChatGPT账号啦~~

HOT! HOT! HOT! &#x1f525; &#x1f525; &#x1f525; 上周&#xff0c;ChatGPT全栈开发课程一经推出&#xff0c;就在程序员圈子中引起了广泛关注。这两天 都被挤爆了&#xff0c;纷纷表示对课程内容很是期待呢。 明天就要开班直播啦&#xff0c;还未报名的同学&…

神经网络模型入门及蠓虫分类问题简单实战

学习知识要实时简单回顾&#xff0c;我把学习的神经网络模型简单梳理一下&#xff0c;方便入门与复习。 神经网络模型 神经网络简介 人工神经网络是在现代神经科学的基础上提出和发展起来的&#xff0c;旨在反映人脑结构及功能的一种抽象数学模型。自 1943 年美国心理学家W.M…

第十四章 代理模式

文章目录 前言一、静态代理完整代码接口 ITeacherDao &#xff08;代理类和被代理类都需要实现这个接口&#xff09;被代理类 TeacherDao代理类 TeacherDaoProxy测试类 Client 二、JDK动态代理完整代码接口 ITeacher实现类TeacherDao代理工厂 ProxyFacyoryclient 测试 三、Cgli…

企业本地文档如何实现规范在线管理?

随着企业数字化生产方式的不断推进&#xff0c;网络办公和在线协作越来越普遍&#xff0c;企业内部可能出现大量的文件和文档&#xff0c;这些文档多存在于不同的设备和存储介质上&#xff0c;这给企业的信息管理带来了一定程度的困难。为了提高企业的知识管理效率&#xff0c;…

Go基础篇:类型系统

目录 前言✨一、什么是类型&#xff1f;二、类型特性1、静态类型检查2、类型推断 三、类型别名和自定义类型1、类型别名2、自定义类型3、类型别名和自定义类型的区别 四、类型底层结构1、类型元数据2、其他描述信息3、uncommontype 五、小结 前言✨ 前段时间忙着春招面试&#…

移动端事件

文章目录 移动端事件概述兼容性Touch触摸事件事件类型是否支持事件使用event对象touch对象阻止浏览器默认行为单指拖拽 Pointer指针事件事件类型是否支持事件使用event对象阻止浏览器默认行为单指拖拽 移动端事件 概述 移动端事件可分为&#xff1a; Touch触摸事件Pointer指…

【Bard】谷歌的人工智能工具—Bard初体验

文章目录 一、Bard介绍二、Bard体验1、加入Bard的候补名单2、登入Bard篇3、使用Bard篇&#xff08;1&#xff09;提供三种预选方式✨&#xff08;2&#xff09;创作生成各类文案&#xff08;3&#xff09;无生成图画能力&#xff08;4&#xff09;支持语音转文本输入✨&#xf…

实景区剧本杀系统开发

实景区剧本杀系统开发需要考虑以下几个方面&#xff1a; 场地选取&#xff1a;选择合适的场地&#xff0c;足够容纳游戏人数和游戏内容&#xff0c;同时需要考虑安全性和便利性。 剧情设定&#xff1a;根据场地和游戏类型设计剧情&#xff0c;包括人物角色、任务目标、…

SpringBoot日志文件

文章目录&#xff1a;一.日志的作用 二.日志的使用&#xff08;1&#xff09;系统默认日志输出 &#xff08;2&#xff09;自定义日志输出 三.日志级别的分类 &#xff08;1&#xff09;默认级别 &#xff08;2&#xff09;自定义级别 四.日志的持久化 &#xff08;1&…

又一次503 service unavailable处理

出现了&#xff1a;503 service unavailable 1&#xff09;查看系统日志 通过事件查看器&#xff0c;查看iis的日志,如下&#xff1a; 在错误信息中提示是 应用程序池提供服务的进程中出现错误。 其他警告也可通过日志目录查看 C:\inetpub\ 出现上述问题的可能是&#xf…

Node第三方包 【Request】

文章目录 &#x1f31f;前言&#x1f31f;Request&#x1f31f;安装与使用&#x1f31f;流&#xff08;stream&#xff09;操作&#x1f31f;Form表单&#x1f31f;application/x-www-form-urlencoded (URL编码的Form)&#x1f31f;multipart/form-data (Multipart Form 上传) …

http协议(一)/应用层

学习目标&#xff1a;⭐理解应用层的作用&#xff0c;理解协议&#xff0c;理解序列化和反序列化&#xff0c;并且实现网络版计算器⭐HTTP协议。⭐手写一个简单的http协议。 应用层 我们写的一个个解决实际问题, 满足我们日常需求的网络程序, 都是在应用层。 协议/序列化与反…

ChatGPT原理剖析

文章目录 ChatGPT常见误解1. 罐头回应2. 网络搜寻重组 ChatGPT真正做的事——文字接龙ChatGPT背后的关键技术——预训练&#xff08;Pre-train&#xff09;一般机器是怎样学习的&#xff1f; ChatGPT带来的研究问题1. 如何精准提出需求2. 如何更改错误3. 侦测AI生成的物件4. 不…

十、v-model的基本使用

一、v-model的基本使用 表单提交是开发中非常常见的功能&#xff0c;也是和用户交互的重要手段&#xff1a; 比如用户在登录、注册时需要提交账号密码&#xff1b;比如用户在检索、创建、更新信息时&#xff0c;需要提交一些数据&#xff1b; 这些都要求我们可以在代码逻辑中…

系统分析师《企业信息化战略与实施》高频知识点

企业信息化战略与实施---企业信息化与电子商务 业务流程重组&#xff08;Business Process Reengineering BPR&#xff09;是针对企业业务流程的基本问题进行反思&#xff0c;并对它进行彻底的重新设计&#xff0c;使业绩取得显著性提高。与目标管理、全面质量管理、战略管理等…
最新文章