python结合excel进行自动化测试_Python接口自动化测试框架: pytest+allure+jsonpath+requests+excel实现的接口自动化测试框架(学习成果)...

废话

最近在自己学习接口自动化测试,这里也算是完成一个小的成果,欢迎大家交流指出不合适的地方,源码在文末

问题

整体代码结构优化未实现,导致最终测试时间变长,其他工具单接口测试只需要39ms,该框架中使用了101ms,考虑和频繁读写用例数据导致

环境与依赖

名称

版本

作用

python

3.7.8

pytest

6.0.1

底层单元测试框架,用来实现参数化,自动执行用例

allure-pytest

2.8.17

allure与pytest的插件可以生成allure的测试报告

jsonpath

0.82

用来进行响应断言操作

loguru

0.54

记录日志

PyYAML

5.3.1

读取yml/yaml格式的配置文件

Allure

2.13.5

要生成allure测试报告必须要在本机安装allure并配置环境变量

xlrd

1.2.0

用来读取excel中用例数据

xlutils

2.0.0

用来向excel中写入实际的响应结果

yagmail

0.11.224

测试完成后发送邮件

requests

2.24.0

发送请求

目录结构

执行顺序

运行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: ["[email protected]","[email protected]","[email protected]"]

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)

运行结果

致谢

这算是学习接口自动化的第一个成果,但是要应用生产环境,拿过去还需要改很多东西,欢迎交流。

源码地址

你可能感兴趣的:(python结合excel进行自动化测试_Python接口自动化测试框架: pytest+allure+jsonpath+requests+excel实现的接口自动化测试框架(学习成果)...)