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 为存放报告的源文件目录

总结:

感谢每一个认真阅读我文章的人!!!

作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

这份文档,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!

以上均可以分享,只需要你搜索vx公众号:程序员雨果,即可免费领取

  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值