• 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 #需要测试的一类接口

    1. '''
    2. Code description:服务相关接口
    3. Create time: 2020/12/3
    4. Developer: 叶修
    5. '''
    6. import os
    7. from common.http_requests import HttpRequests
    8. class Api_Auth_Service(object):
    9. def __init__(self):
    10. self.headers = HttpRequests().headers
    11. def api_home_service_list(self):
    12. # 首页服务列表
    13. # url = "http://192.168.2.199:9092/v1/auth/auth_service/findAuthService"
    14. url = os.environ["host"] + "/v1/auth/auth_service/findAuthService" # 读取conftest.py文件地址进行拼接
    15. response = HttpRequests().get(url, headers=self.headers, verify=False)
    16. # print(response.json())
    17. return response
    18. def get_service_id(self):
    19. #获取银行卡三要素认证服务id
    20. url = "http://192.168.2.199:9092/v1/auth/auth_service/findAuthService"
    21. #url = os.environ["host"] + "/v1/auth/auth_service/findAuthService" # 读取conftest.py文件地址进行拼接
    22. response = HttpRequests().get(url,headers=self.headers)
    23. #print(response.json()['data'][0]['service_list'][0]['id'])
    24. service_id = response.json()['data'][0]['service_list'][1]['id']
    25. return service_id
    26. def api_service_info(self,serviceId='0b6cf45bec757afa7ee7209d30012ce1',developerId=''):
    27. #服务详情
    28. body = {
    29. "serviceId" :serviceId,
    30. "developerId":developerId
    31. }
    32. url = "http://192.168.2.199:9092/v1/auth/auth_service/findServiceDetail"
    33. #url = os.environ["host"] + "/v1/auth/auth_service/findServiceDetail"#读取conftest.py文件地址进行拼接
    34. response = HttpRequests().get(url,headers=self.headers,params = body,verify=False)
    35. #print(response.json())
    36. return response
    37. def api_add_or_update_service(self,api_param_req,api_param_res,description,error_code,icon,id,interface_remarks,
    38. name,product_info,request_method,sample_code,sort,type,url):
    39. #服务添加或者更新
    40. body={
    41. "api_param_req": api_param_req,
    42. "api_param_res": api_param_res,
    43. "description": description,
    44. "error_code": error_code,
    45. "icon": icon,
    46. "id": id,
    47. "interface_remarks": interface_remarks,
    48. "name": name,
    49. "product_info": product_info,
    50. "request_method": request_method,
    51. "sample_code": sample_code,
    52. "sort": sort,
    53. "type": type,
    54. "url": url,
    55. }
    56. #url = "http://192.168.2.199:9092/v1/auth/auth_service/insertOrUpdateService"
    57. url = os.environ["host"] + "/v1/auth/auth_service/insertOrUpdateService" # 读取conftest.py文件地址进行拼接
    58. response = HttpRequests().post(url,json=body,headers=self.headers,verify=False)
    59. return response
    60. def api_add_service_price(self,id,max_number,money,service_id,small_number):
    61. #服务价格添加
    62. body = {
    63. "id": id,
    64. "max_number": max_number,
    65. "money": money,
    66. "service_id": service_id,
    67. "small_number": small_number
    68. }
    69. # url = "http://192.168.2.199:9092/v1/auth/auth_service/insertServicePrice"
    70. url = os.environ["host"] + "/v1/auth/auth_service/insertServicePrice" # 读取conftest.py文件地址进行拼接
    71. response = HttpRequests().post(url, json=body, headers=self.headers, verify=False)
    72. return response
    73. def api_apply_service(self,developer_id,service_id):
    74. #申请服务
    75. body ={
    76. "developer_id": developer_id,
    77. "service_id": service_id
    78. }
    79. # url = "http://192.168.2.199:9092/v1/auth/auth_service/applyService"
    80. url = os.environ["host"] + "/v1/auth/auth_service/applyService" # 读取conftest.py文件地址进行拼接
    81. response = HttpRequests().post(url, json=body, headers=self.headers, verify=False)
    82. return response
    83. if __name__ == '__main__':
    84. #Auth_Service().api_home_service_list()
    85. Api_Auth_Service().get_service_id()
    86. #Auth_Service().api_service_info()

     api/get_token.py#获取登录token

    1. '''
    2. Code description:获取token
    3. Create time:2020-12-03
    4. Developer:叶修
    5. '''
    6. import os
    7. import urllib3
    8. from common.http_requests import HttpRequests
    9. class Get_Token(object):
    10. def get_token(self,account='****',password='****'):
    11. #url = "http://192.168.2.199:9092/v1/auth/developer/accountLogin"
    12. url = os.environ["host"]+"/v1/auth/developer/accountLogin"
    13. body = {
    14. "account": account,
    15. "password": password,
    16. }
    17. urllib3.disable_warnings()
    18. r = HttpRequests().post(url, json=body,verify=False)
    19. #print(r.json())
    20. token = r.json()['data']['token']
    21. params = {
    22. "access_token": token
    23. }
    24. HttpRequests().params.update(params)#更新token到session
    25. return token
    26. if __name__ == '__main__':
    27. print(Get_Token().get_token())

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

    1. '''
    2. Code description: 服务详情
    3. Create time: 2020/12/3
    4. Developer: 叶修
    5. '''
    6. import sys
    7. import allure
    8. import pytest
    9. from common.logger import Log
    10. from common.read_yaml import ReadYaml
    11. from api.api_auth_service.api_auth_service import Api_Auth_Service
    12. testdata = ReadYaml("auth_service.yml").get_yaml_data() # 读取数据
    13. @allure.feature('服务详情')
    14. class Test_Service_Info(object):
    15. log = Log()
    16. @pytest.mark.process
    17. @pytest.mark.parametrize('serviceId,developerId,expect', testdata['service_info'],
    18. ids=['服务详情'])
    19. def test_service_info(self,serviceId,developerId,expect):
    20. self.log.info('%s{%s}' % ((sys._getframe().f_code.co_name,'------服务详情接口-----')))
    21. with allure.step('获取服务id'):
    22. serviceId = Api_Auth_Service().get_service_id()
    23. with allure.step('服务详情'):
    24. msg = Api_Auth_Service().api_service_info(serviceId,developerId)
    25. self.log.info('%s:%s' % ((sys._getframe().f_code.co_name, '获取请求结果:%s' % msg.json())))
    26. # 断言
    27. assert msg.json()["result_message"] == expect['result_message']
    28. assert msg.json()['result_code'] == expect['result_code']
    29. assert 'url' in msg.json()['data']

     conftest.py

    1. '''
    2. Code description:配置信息
    3. Create time: 2020/12/3
    4. Developer: 叶修
    5. '''
    6. import os
    7. import pytest
    8. from api.get_token import Get_Token
    9. from common.http_requests import HttpRequests
    10. @pytest.fixture(scope="session")
    11. def get_token():
    12. '''前置操作获取token并传入headers'''
    13. Get_Token().get_token()
    14. if not HttpRequests().params.get("access_token", ""):#没有get到token,跳出用例
    15. pytest.skip("未获取token跳过用例")
    16. yield HttpRequests().req
    17. HttpRequests().req.close()
    18. def pytest_addoption(parser):
    19. parser.addoption(
    20. "--cmdhost", action="store", default="http://192.168.1.54:32099",
    21. help="my option: type1 or type2"
    22. )
    23. @pytest.fixture(scope="session",autouse=True)
    24. def host(request):
    25. '''获取命令行参数'''
    26. #获取命令行参数给到环境变量
    27. #pytest --cmdhost 运行指定环境
    28. os.environ["host"] = request.config.getoption("--cmdhost")
    29. print("当前用例运行测试环境:%s" % os.environ["host"])

     common/connect_mysql.py

    1. '''
    2. Code description: 配置连接数据库
    3. Create time: 2020/12/3
    4. Developer: 叶修
    5. '''
    6. import pymysql
    7. dbinfo = {
    8. "host":"******",
    9. "user":"root",
    10. "password":"******",
    11. "port":31855
    12. }
    13. class DbConnect():
    14. def __init__(self,db_conf,database=""):
    15. self.db_conf = db_conf
    16. #打开数据库
    17. self.db = pymysql.connect(database = database,
    18. cursorclass = pymysql.cursors.DictCursor,
    19. **db_conf)
    20. #使用cursor()方式获取操作游标
    21. self.cursor = self.db.cursor()
    22. def select(self,sql):
    23. #sql查询
    24. self.cursor.execute(sql)#执行sql
    25. results = self.cursor.fetchall()
    26. return results
    27. def execute(self,sql):
    28. #sql 删除 提示 修改
    29. try:
    30. self.cursor.execute(sql)#执行sql
    31. self.db.commit()#提交修改
    32. except:
    33. #发生错误时回滚
    34. self.db.rollback()
    35. def close(self):
    36. self.db.close()#关闭连接
    37. def select_sql(select_sql):
    38. '''查询数据库'''
    39. db = DbConnect(dbinfo,database='auth_platform')
    40. result = db.select(select_sql)
    41. db.close()
    42. return result
    43. def execute_sql(sql):
    44. '''执行SQL'''
    45. db = DbConnect(dbinfo,database='auth_platform')
    46. db.execute(sql)
    47. db.close()
    48. if __name__ == '__main__':
    49. sql = "SELECT * FROM auth_platform.auth_service where name='四要素认证'"
    50. sel = select_sql(sql)[0]['name']
    51. print(sel)

     common/http_requests.py

    1. '''
    2. Code description: 封装自己的请求类型
    3. Create time: 2020/12/3
    4. Developer: 叶修
    5. '''
    6. import requests
    7. # 定义一个HttpRequests的类
    8. class HttpRequests(object):
    9. req = requests.session()#定义session会话
    10. # 定义公共请求头
    11. headers = {
    12. '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',
    13. 'cookie':''
    14. }
    15. params = {
    16. 'access_token':''
    17. }
    18. # 封装自己的get请求,获取资源
    19. def get(self, url='', params='', data='', headers=None, cookies=None,stream=None,verify=None):
    20. response = self.req.get(url,params=params,data=data,headers=headers,cookies=cookies,stream=stream,verify=verify)
    21. return response
    22. # 封装自己的post方法,创建资源
    23. def post(self, url='', params='',data='', json='', headers=None, cookies=None,stream=None,verify=None):
    24. response = self.req.post(url,params=params,data=data,json=json,headers=headers,cookies=cookies,stream=stream,verify=verify)
    25. return response
    26. # 封装自己的put方法,更新资源
    27. def put(self, url='', params='', data='', headers=None, cookies=None,verify=None):
    28. response = self.req.put(url, params=params, data=data, headers=headers, cookies=cookies,verify=verify)
    29. return response
    30. # 封装自己的delete方法,删除资源
    31. def delete(self, url='', params='', data='', headers=None, cookies=None,verify=None):
    32. response = self.req.delete(url, params=params, data=data, headers=headers, cookies=cookies,verify=verify)
    33. return response

     common/logger.py

    1. '''
    2. Code description: 封装输出日志文件
    3. Create time: 2020/12/3
    4. Developer: 叶修
    5. '''
    6. import logging,time
    7. import os
    8. import getpathinfo
    9. path = getpathinfo.get_path()#获取本地路径
    10. log_path = os.path.join(path,'logs')# log_path是存放日志的路径
    11. # 如果不存在这个logs文件夹,就自动创建一个
    12. if not os.path.exists(log_path):os.mkdir(log_path)
    13. class Log():
    14. def __init__(self):
    15. #文件的命名
    16. self.logname = os.path.join(log_path,'%s.log'%time.strftime('%Y_%m_%d'))
    17. self.logger = logging.getLogger()
    18. self.logger.setLevel(logging.DEBUG)
    19. #日志输出格式
    20. self.formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    21. def __console(self,level,message):
    22. #创建一个fileHander,用于写入本地
    23. fh = logging.FileHandler(self.logname,'a',encoding='utf-8')
    24. fh.setLevel(logging.DEBUG)
    25. fh.setFormatter(self.formatter)
    26. self.logger.addHandler(fh)
    27. #创建一个StreamHandler,用于输入到控制台
    28. ch = logging.StreamHandler()
    29. ch.setLevel(logging.DEBUG)
    30. ch.setFormatter(self.formatter)
    31. self.logger.addHandler(ch)
    32. if level == 'info':
    33. self.logger.info(message)
    34. elif level == 'debug':
    35. self.logger.debug(message)
    36. elif level == 'warning':
    37. self.logger.warning(message)
    38. elif level == 'error':
    39. self.logger.error(message)
    40. #避免日志重复
    41. self.logger.removeHandler(fh)
    42. self.logger.removeHandler(ch)
    43. #关闭打开文件
    44. fh.close()
    45. def debug(self,message):
    46. self.__console('debug',message)
    47. def info(self,message):
    48. self.__console('info',message)
    49. def warning(self,message):
    50. self.__console('warning',message)
    51. def error(self,message):
    52. self.__console('error',message)
    53. if __name__ == '__main__':
    54. log = Log()
    55. log.info('测试')
    56. log.debug('测试')
    57. log.warning('测试')
    58. log.error('测试')

     common/read_save_data.py

    1. '''
    2. Code description: 读取保存数据
    3. Create time: 2020/12/8
    4. Developer: 叶修
    5. '''
    6. import os
    7. import yaml
    8. import getpathinfo
    9. class Read_Save_Date():
    10. def __init__(self):
    11. path = getpathinfo.get_path()#获取本地路径
    12. self.head_img_path = os.path.join(path,'data','save_data')+"/"+'head_img_path.txt'# head_img_path文件地址
    13. self.order_id_path = os.path.join(path, 'data', 'save_data') + "/" + 'order_id.txt' # order_id.txt文件地址
    14. def get_head_img_path(self):
    15. # 获取head_img_path
    16. with open(self.head_img_path, "r", encoding="utf-8")as f:
    17. return f.read()
    18. def get_order_id(self):
    19. # 获取order_id
    20. with open(self.order_id_path, "r", encoding="utf-8")as f:
    21. return f.read()
    22. if __name__ == '__main__':
    23. print(Read_Save_Date().get_head_img_path())
    24. print(Read_Save_Date().get_order_id())

     common/read_yaml.py

    1. '''
    2. Code description: 读取yml文件测试数据
    3. Create time: 2020/12/3
    4. Developer: 叶修
    5. '''
    6. import os
    7. import yaml
    8. import getpathinfo
    9. class ReadYaml():
    10. def __init__(self,filename):
    11. path = getpathinfo.get_path()#获取本地路径
    12. self.filepath = os.path.join(path,'data','test_data')+"/"+filename#拼接定位到data文件夹
    13. def get_yaml_data(self):
    14. with open(self.filepath, "r", encoding="utf-8")as f:
    15. # 调用load方法加载文件流
    16. return yaml.load(f,Loader=yaml.FullLoader)
    17. if __name__ == '__main__':
    18. data = ReadYaml("auth_service.yml").get_yaml_data()['add_or_update_service']
    19. print(data)

    data/

     data/test_data/auth_service.yml

    1. home_service_list:
    2. - [{'result_code': '0', 'result_message': '处理成功'}]
    3. service_info:
    4. - ['','',{'result_code': '0', 'result_message': '处理成功'}]
    5. add_or_update_service:
    6. - [['1','1','1','1','1','123456','1','测试','1','1','1','1','1','1'],{'result_code': '0', 'result_message': '处理成功'}]
    7. add_service_price:
    8. - ['123456789','10','0','','0',{'result_code': '0', 'result_message': '处理成功'}]
    9. apply_service:
    10. - ['','',{'result_code': '0', 'result_message': '处理成功'}]

     logs/

     report/

     getpathinfo.py

    1. '''
    2. Code description:配置文件路径
    3. Create time: 2020/12/3
    4. Developer: 叶修
    5. '''
    6. import os
    7. def get_path():
    8. # 获取当前路径
    9. curpath = os.path.dirname(os.path.realpath(__file__))
    10. return curpath
    11. if __name__ == '__main__':# 执行该文件,测试下是否OK
    12. print('测试路径是否OK,路径为:', get_path())

    pytest.ini

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

    requirements.txt

    1. allure-pytest==2.8.18
    2. allure-python-commons==2.8.18
    3. BeautifulReport==0.1.3
    4. beautifulsoup4==4.9.3
    5. ddt==1.4.1
    6. Faker==4.18.0
    7. Flask==1.1.1
    8. httpie==1.0.3
    9. httplib2==0.9.2
    10. HttpRunner==1.5.8
    11. py==1.9.0
    12. PyMySQL==0.10.1
    13. pytest==6.1.1
    14. pytest-base-url==1.4.2
    15. pytest-cov==2.10.1
    16. pytest-forked==1.3.0
    17. pytest-html==2.1.1
    18. pytest-instafail==0.4.2
    19. pytest-metadata==1.10.0
    20. pytest-mock==3.3.1
    21. pywin32==228
    22. PyYAML==5.3.1
    23. requests==2.22.0
    24. requests-oauthlib==1.3.0
    25. requests-toolbelt==0.9.1

    run_main.py

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

    最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

    这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你! 

  • 相关阅读:
    打工毁一生,创业治百病,零经验零成本创业三个月收益10W+
    IDEA创建父子项目
    docker三剑客compose+machine+swarm小结
    微软外服工作札记②——聊聊微软的知识管理服务平台和一些编程风格
    java毕业生设计在线玩具租赁系统计算机源码+系统+mysql+调试部署+lw
    [附源码]Python计算机毕业设计SSM家政服务预约小程序(程序+LW)
    SQL必需掌握的100个重要知识点:检索数据
    C primer plus学习笔记 —— 5、指针
    内卷下的智能投影行业,未来何去何从?
    常见视频传输接口
  • 原文地址:https://blog.csdn.net/weixin_71807218/article/details/136680269