Pytest接口自动化测试实战演练
结合单元测试框架pytest+数据驱动模型+allure
结合单元测试框架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 为存放报告的源文件目录
总结:
感谢每一个认真阅读我文章的人!!!
作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

这份文档,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!
以上均可以分享,只需要你搜索vx公众号:程序员雨果,即可免费领取
更多推荐




所有评论(0)