问题
环境与依赖
目录结构
执行顺序
运行test_api.py -> 读取config.yaml(tools.read_config.py) -> 读取excel用例文件(tools.read_data.py) -> test_api.py实现参数化 -> 处理是否依赖数据 ->base_requests.py发送请求 -> test_api.py断言 -> read_data.py回写实际响应到用例文件中(方便根据依赖提取对应的数据)。
config.ymal展示
server: # 服务器host地址,发送请求的url= host+ path test: http://127.0.0.1:8888/api/private/v1/ dev: http://47.115.124.102:8888/api/private/v1/ response_reg: # 提取token的表达式 token: $.data.token # 提取实际响应中的某部分来作为断言数据(实例中断言的是meta这个子字典,预期结果也是写的meta子字典中的内容) response: $.meta file_path: # 测试用例数据地址 case_data: ../data/case_data.xlsx # 运行测试存储的结果路径 report_data: ../report/data/ # 本地测试报告生成位置 report_generate: ../report/html/ # 压缩本地测试报告后的路径 report_zip: ../report/html/apiAutoTestReport.zip # 日志文件地址 log_path: ../log/运行日志{time}.log email: user: 发件人邮箱 password: 邮箱授权码(不是密码) host: smtp.163.com contents: 解压apiAutoReport.zip(接口测试报告)后,请使用已安装Live Server 插件的VsCode,打开解压目录下的index.html查看报告 # 发件人列表 addressees: ["123@qq.com","12067@qq.com","717@qq.com"] title: 接口自动化测试报告(见附件) # 测试报告附件 enclosures: ["../report/html/apiAutoTestReport.zip",] |
EXcel用例展示
脚本一览
#!/usr/bin/env/python3 # -*- coding:utf-8 -*- """ @project: apiAutoTest @author: zy7y @file: base_requests.py @ide: PyCharm @time: 2020/7/31 """ from loguru import logger import requests class BaseRequest(object): def __init__(self): pass # 请求 def base_requests(self, method, url, data=None, file_var=None, file_path=None, header=None): """ :param method: 请求方法 :param url: 接口path :param data: 数据,请传入dict样式的字符串 :param file_path: 上传的文件路径 :param file_var: 接口中接收文件对象的参数名 :param header: 请求头 :return: 完整的响应对象 """ session = requests.Session() if (file_var in [None, '']) and (file_path in [None, '']): files = None else: # 文件不为空的操作 files = {file_var: open(file_path, 'rb')} # get 请求参数传递形式 params if method == 'get': res = session.request(method=method, url=url, params=data, headers=header) else: res = session.request(method=method, url=url, data=data, files=files, headers=header) logger.info(f'请求方法:{method},请求路径:{url}, 请求参数:{data}, 请求文件:{files}, 请求头:{header})') return res.json() |
#!/usr/bin/env/python3 # -*- coding:utf-8 -*- """ @project: apiAutoTest @author: zy7y @file: read_data.py @ide: PyCharm @time: 2020/7/31 """ import json import jsonpath import xlrd from xlutils.copy import copy from loguru import logger class ReadData(object): def __init__(self, excel_path): self.excel_file = excel_path self.book = xlrd.open_workbook(self.excel_file) def get_data(self): """ :return: """ data_list = [] title_list = [] table = self.book.sheet_by_index(0) for norw in range(1, table.nrows): # 每行第4列 是否运行 if table.cell_value(norw, 3) == '否': continue # 每行第3列, 标题单独拿出来 title_list.append(table.cell_value(norw, 1)) # 返回该行的所有单元格组成的数据 table.row_values(0) 0代表第1列 case_number = table.cell_value(norw, 0) path = table.cell_value(norw, 2) is_token = table.cell_value(norw, 4) method = table.cell_value(norw, 5) file_var = table.cell_value(norw, 6) file_path = table.cell_value(norw, 7) dependent = table.cell_value(norw, 8) data = table.cell_value(norw, 9) expect = table.cell_value(norw, 10) actual = table.cell_value(norw, 11) value = [case_number, path, is_token, method, file_var, file_path, dependent, data, expect, actual] logger.info(value) # 配合将每一行转换成元组存储,迎合 pytest的参数化操作,如不需要可以注释掉 value = tuple(value) value = tuple(value) data_list.append(value) return data_list, title_list def write_result(self, case_number, result): """ :param case_number: 用例编号:case_001 :param result: 需要写入的响应值 :return: """ row = int(case_number.split('_')[1]) logger.info('开始回写实际响应结果到用例数据中.') result = json.dumps(result, ensure_ascii=False) new_excel = copy(self.book) ws = new_excel.get_sheet(0) # 11 是 实际响应结果栏在excel中的列数-1 ws.write(row, 11, result) new_excel.save(self.excel_file) logger.info(f'写入完毕:-写入文件: {self.excel_file}, 行号: {row + 1}, 列号: 11, 写入值: {result}') # 读实际的响应 def read_actual(self, depend): """ :param nrow: 列号 :param depend: 依赖数据字典格式,前面用例编号,后面需要提取对应字段的jsonpath表达式 {"case_001":["$.data.id",],} :return: """ depend = json.loads(depend) # 用来存依赖数据的字典 depend_dict = {} for k, v in depend.items(): # 得到行号 norw = int(k.split('_')[1]) table = self.book.sheet_by_index(0) # 得到对应行的响应, # 11 是 实际响应结果栏在excel中的列数-1 actual = json.loads(table.cell_value(norw, 11)) try: for i in v: logger.info(f'i {i}, v {v}, actual {actual} \n {type(actual)}') depend_dict[i.split('.')[-1]] = jsonpath.jsonpath(actual, i)[0] except TypeError as e: logger.error(f'实际响应结果中无法正常使用该表达式提取到任何内容,发现异常{e}') return depend_dict |
#!/usr/bin/env/python3 # -*- coding:utf-8 -*- """ @project: apiAutoTest @author: zy7y @file: test_api.py @ide: PyCharm @time: 2020/7/31 """ import json import shutil import jsonpath from loguru import logger import pytest import allure from api.base_requests import BaseRequest from tools.read_config import ReadConfig from tools.read_data import ReadData rc = ReadConfig() base_url = rc.read_serve_config('dev') token_reg, res_reg = rc.read_response_reg() case_data_path = rc.read_file_path('case_data') report_data = rc.read_file_path('report_data') report_generate = rc.read_file_path('report_generate') log_path = rc.read_file_path('log_path') report_zip = rc.read_file_path('report_zip') email_setting = rc.read_email_setting() data_list, title_ids = ReadData(case_data_path).get_data() br = BaseRequest() token_header = {} no_token_header = {} class TestApiAuto(object): def start_run_test(self): import os if os.path.exists('../report') and os.path.exists('../log'): shutil.rmtree(path='../report') shutil.rmtree(path='../log') logger.add(log_path) pytest.main(args=[f'--alluredir={report_data}']) # # 启动一个web服务的报告 # os.system('allure serve ./report/data') os.system(f'allure generate {report_data} -o {report_generate} --clean') logger.debug('报告已生成') def treating_data(self, is_token, dependent, data): if is_token == '': header = no_token_header else: header = token_header logger.info(f'处理依赖时data的数据:{data}') if dependent != '': dependent_data = ReadData(case_data_path).read_actual(dependent) logger.debug(f'依赖数据解析获得的字典{dependent_data}') if data != '': # 合并组成一个新的data dependent_data.update(json.loads(data)) data = dependent_data logger.debug(f'data有数据,依赖有数据时 {data}') else: # 赋值给data data = dependent_data logger.debug(f'data无数据,依赖有数据时 {data}') else: if data == '': data = None logger.debug(f'data无数据,依赖无数据时 {data}') else: data = json.loads(data) logger.debug(f'data有数据,依赖无数据 {data}') return data, header @pytest.mark.parametrize('case_number,path,is_token,method,file_var,' 'file_path,dependent,data,expect,actual', data_list, ids=title_ids) def test_main(self, case_number, path, is_token, method, file_var, file_path, dependent, data, expect, actual): with allure.step("处理相关数据依赖,header"): data, header = self.treating_data(is_token, dependent, data) with allure.step("发送请求,取得响应结果的json串"): res = br.base_requests(method=method, url=base_url + path, file_var=file_var, file_path=file_path, data=data, header=header) with allure.step("将响应结果的内容写入用例中的实际结果栏"): ReadData(case_data_path).write_result(case_number, res) # 写token的接口必须是要正确无误能返回token的 if is_token == '写': with allure.step("从登录后的响应中提取token到header中"): token_header['Authorization'] = jsonpath.jsonpath(res, token_reg)[0] logger.info(f'token_header: {token_header}, \n no_token_header: {no_token_header}') with allure.step("根据配置文件的提取响应规则提取实际数据"): really = jsonpath.jsonpath(res, res_reg)[0] with allure.step("处理读取出来的预期结果响应"): expect = eval(expect) with allure.step("预期结果与实际响应进行断言操作"): assert really == expect logger.info(f'完整的json响应: {res}\n 需要校验的数据字典: {really}\n 预期校验的数据字典: {expect}\n 测试结果: {really == expect}') if __name__ == '__main__': from tools.zip_file import zipDir from tools.send_email import send_email t1 = TestApiAuto() t1.start_run_test() zipDir(report_generate, report_zip) send_email(email_setting) |
运行结果
这只是简单的接口自动化,要应用生产环境,拿过去还需要改很多东西,欢迎交流。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理