写一个最基本的接口自动测试
基本架构
写一个登录接口:login.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/6/23 15:48
# @Author : SYXXS
# @File : login.py
# @Software: PyCharm
from unittest import result
from public.logger import logger
from public.http_client import ApiRequest
from public.rw_xlsx import HandleExcel
import requests
import json
class Login:
def __init__(self):
self.request = ApiRequest()
def login(self):
"""
登录用户
:param username:用户名
:param password:密码
:param site: 站点
:return : 自定义返回结果 result
, url, data, **kwargs
"""
# result = ResultBase()
data = HandleExcel().get_case()
for case in data:
header = eval(case['content_type'])
data = case['data']
url = case['url']
response = requests.post(url=url, headers=header, data=json.dumps(data))
print(response.text)
# handlers = handler,
# result.success = False
# if response.json()["code"] == 0:
# result.success = True
# result.token = response.json()["login_info"]["token"]
# else:
# result.error = "接口返回码是: {},返回信息: {}".format(response.json()["code"], response.json()["msg"])
# result.msg = response.json()["msg"]
# result.response = response
# logger.info("登录用户 ===> 返回结果 ====> {}".format(result.response.text))
# return result
if __name__ == '__main__':
request_api = Login()
print(request_api.login())
#base_path.py 基础地址
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/6/23 17:19
# @Author : SYXXS
# @File : base_path.py
# @Software: PyCharm
import os
def root_dir():
"""获取根目录"""
# os.path.abspath(path) 返回绝对路径
# os.path.join(path1[, path2[, ...]]) 把目录和文件名合成一个路径
# os.path.dirname(path) 返回文件路径
# os.sep根据你所处的平台,自动采用相应的分隔符号。
base_path_temp = os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))
rootdir = base_path_temp.replace(r'\/'.replace(os.sep, ''), os.sep)
return rootdir
rootDir = root_dir()
#http_client.py基本请求
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/6/23 16:38
# @Author : SYXXS
# @File : http_client.py
# @Software: PyCharm
import requests
import json as js
from public.logger import logger
from public.read_config import load_ini
class ApiRequest:
def __init__(self):
self.api_root_url = load_ini('host')['root_url']
self.session = requests.session()
def get(self, url, **kwargs):
return self.request(url, "GET", **kwargs)
def post(self, url, data=None, json=None, **kwargs):
return self.request(url, "POST", data, json, **kwargs)
def put(self, url, data=None, **kwargs):
return self.request(url, "PUT", data, **kwargs)
def delete(self, url, **kwargs):
return self.request(url, "DELETE", **kwargs)
def patch(self, url, data=None, **kwargs):
return self.request(url, "PATCH", data, **kwargs)
def request(self, url, method, data=None, json=None, **kwargs):
url = self.api_root_url + url
headers = dict(**kwargs).get('headers')
params = dict(**kwargs).get('params')
files = dict(**kwargs).get('files')
cookies = dict(**kwargs).get('cookies')
self.request_log(url, method, data, json, params, headers, files, cookies)
if method == "get":
return self.session.get(url, **kwargs)
if method == "POST":
return self.session.post(url, data, json, **kwargs)
if method == "PUT":
if json:
# PUT 和 PATCH 中没有提供直接使用json参数的方法,因此需要用data来传入
data = js.dumps(json)
return self.session.put(url, data, **kwargs)
if method == "DELETE":
return self.session.delete(url, **kwargs)
if method == "PATCH":
if json:
data = js.dumps(json)
return self.session.patch(url, data, **kwargs)
def request_log(url, method, data=None, json=None, params=None, headers=None,
files=None, cookies=None, **kwargs):
logger.info("接口请求地址 ==>> {}".format(url))
logger.info("接口请求方式 ==>> {}".format(method))
# Python3中,json在做dumps操作时,会将中文转换成unicode编码,因此设置 ensure_ascii=False
logger.info("接口请求头 ==>> {}".format(js.dumps(headers, indent=4, ensure_ascii=False)))
logger.info("接口请求 params 参数 ==>> {}".format(js.dumps(params, indent=4, ensure_ascii=False)))
logger.info("接口请求体 data 参数 ==>> {}".format(js.dumps(data, indent=4, ensure_ascii=False)))
logger.info("接口请求体 json 参数 ==>> {}".format(js.dumps(json, indent=4, ensure_ascii=False)))
logger.info("接口上传附件 files 参数 ==>> {}".format(files))
logger.info("接口 cookies 参数 ==>> {}".format(js.dumps(cookies, indent=4, ensure_ascii=False)))
#http_req.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/6/25 17:44
# @Author : SYXSS
# @File : http_req.py
# @Software: PyCharm
import requests
class BaseRequest:
def get(self, url, **kwargs):
"""封装get方法"""
# 获取请求参数
params = kwargs.get("params")
headers = kwargs.get("headers")
try:
result = requests.get(url, params=params, headers=headers)
return result
except Exception as e:
print("get请求错误: %s" % e)
def post(self, url, **kwargs):
params = kwargs.get("params")
headers = kwargs.get("headers")
data = kwargs.get("data")
json = kwargs.get("json")
try:
result = requests.get(url, params=params, data=data, json=json)
return result
except Exception as e:
print("post请求错误: %s" % e)
def run_main(self, method, **kwargs):
"""判断请求类型
:param method: 请求接口类型
:param kwargs: 选填参数
:return: 接口返回内容
"""
if method == 'get':
result = self.get(**kwargs)
return result
elif method == 'post':
result = self.post(**kwargs)
return result
else:
print('请求接口类型错误')
if __name__ == '__main__':
# 以下是测试代码
# get请求接口
url = 'https://api.apiopen.top/getJoke?page=1&count=2&type=video'
res = RequestHandler().get(url)
# post请求接口
url2 = 'http://127.0.0.1:8000/user/login/'
payload = {
"username": "vivi",
"password": "123456"
}
res2 = RequestHandler().post(url2,json=payload)
print(res.json())
print(res2.json()
# logger.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/6/23 17:03
# @Author : SYXXS
# @File : logger.py
# @Software: PyCharm
import datetime
import logging
from public.base_path import rootDir
import os
def logFile(filename, output='all'):
# 设置路径
now_time = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M')
path_file = rootDir + r'\log\{}_{}.log'.format(filename, now_time).replace(r'\/'.replace(os.sep, ''), os.sep)
# 设置日志器
log = logging.getLogger()
log.setLevel(level=logging.INFO)
# 设置格式器
strean_formatter = logging.Formatter(fmt='[%(asctime)s]:[%(levelname)s] '
'->>%(message)s')
file_formatter = logging.Formatter(fmt='[%(filename)s]: [%(levelname)s] '
'[%(asctime)s] ''line:%(lineno)d->>%(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
# 设置控制台处理器
s_hand = logging.StreamHandler()
s_hand.setLevel(logging.ERROR) # 设置处理器级别
s_hand.setFormatter(strean_formatter) # 添加控制条格式器
# 设置文件处理器
f_hand = logging.FileHandler(filename=path_file, encoding='utf-8')
f_hand.setLevel(logging.INFO) # 设置处理器级别
f_hand.setFormatter(file_formatter) # 添加文件格式器
if output == "all":
# 日志器添加控制台处理器
log.addHandler(s_hand)
# 日志器添加文件处理器
log.addHandler(f_hand)
return log
elif output == 'strean':
log.addHandler(s_hand)
return log
elif output == 'file':
log.addHandler(f_hand)
return log
else:
raise ValueError("output参数错误")
logger = logFile('接口测试', output='file')
# read_config.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/6/23 17:39
# @Author : SYXXS
# @File : read_config.py
# @Software: PyCharm
import os
import yaml
from public.base_path import rootDir
from configparser import ConfigParser
from public.logger import logger
class MyConfigParser(ConfigParser):
# 重写 configparser 中的 optionxform 函数,解决 .ini 文件中的 键option 自动转为小写的问题
def __init__(self, defaults=None):
ConfigParser.__init__(self, defaults=defaults)
def optionxform(self, optionstr):
return optionstr
def load_yaml(file_path):
"""读取yaml并return"""
logger.info("加载 {} 文件........".format(file_path))
with open(file_path, mode='r', encoding='utf-8') as file:
date = yaml.load(file, Loader=yaml.FullLoader) # 读取yaml
return date
def load_ini(section, file_path=rootDir + r'\config\config.ini'):
"""读取ini文件键值对返回"""
file_path = file_path.replace(r'\/'.replace(os.sep, ''),
os.sep) # replace() 方法把字符串中的 old(旧字符串) 替换成 new(新字符串),如果指定第三个参数max,则替换不超过 max 次。
logger.info("加载 {} 文件.......".format(file_path))
config = MyConfigParser()
config.read(file_path, encoding="utf-8")
load_date = dict(config.items(section))
return load_date
def load(file_name, path=rootDir):
"""在项目内读取指定文件"""
ls = os.listdir(path) # os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表。
for i in ls:
c_path = os.path.join(path, i) # # 将目录和文件名拼接
if os.path.isdir(c_path): # 判断路径是否为目录
data = load(file_name, c_path)
if data is not None:
return data
elif file_name == i:
data = load_yaml(c_path)
return data
# data= load('register.yaml')
# print(data)
# data= load('user_mange_api_data.yaml')
# print(data)
# rw_xlsx.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/6/23 20:21
# @Author : SYXXS
# @File : rw_xlsx.py
# @Software: PyCharm
import pandas as pandas
import xlrd
import openpyxl
from public.base_path import rootDir
data_address = rootDir + r'/testdata/data.xlsx'
class HandleExcel:
# def __init__(self):
"""
打开工作表
:param index: 传入表号,默认为0
"""
# data_address = rootDir + r'/testdata/data.xlsx'
# workbook = xlrd.open_workbook(data_address)
# self.workbook = openpyxl.load_workbook(data_address)
# self.catalogue = self.workbook[sheetName]
# print(self.catalogue)
def load_Excel(self):
"""打开Excel"""
workbook = openpyxl.load_workbook(data_address)
return workbook
def active_table(self):
"""激活表"""
table = self.load_Excel().active
return table
def get_catalogue(self, sheetName='目录'):
"""获取目录页"""
# sheet_names = self.load_Excel().sheetnames # 获取所以sheet
catalogue = self.load_Excel()[sheetName] # 获取目录sheet页
return catalogue
def get_firstrow_value(self):
"""获取目录页第一行数据"""
first_row_list = []
for i in self.get_catalogue()[1]:
first_row_list.append(i.value)
return first_row_list
def search_caseid_site(self, keyword='用例ID'):
"""查看目录页用例ID的具体位置,"""
global caseid_row, caseid_col
for row in self.get_catalogue().iter_rows(min_row=1, max_row=1): #先遍历第一行数
for cell in row: # 遍历每列一列数据
if cell.value is not None: # 对比关键值
info = cell.value.find(keyword)
if info == 0:
# print(cell.value)
# print(cell.row)
# print(cell.column)
# keyword = cell.value
caseid_row = cell.row
caseid_col = cell.column
return [caseid_row, caseid_col]
def get_caseID_value(self):
"""获取用例目录页所有用例id,用例id就是详细用例的sheet页"""
rows = self.get_catalogue().max_row
caseid_value = []
for i in range(2, rows+1):
caseid = self.get_catalogue().cell(row=i, column=self.search_caseid_site()[1]).value
if caseid is not None:
caseid_value.append(caseid)
return caseid_value
def get_sheet_info(self):
"""
获取表信息
:return: 名字,行数,列数(多个sheet)
"""
# print(case_sheet_names)
# try:
case_sheet_info = []
case_sheet = []
case_sheet_names = self.get_caseID_value()
for sheet in case_sheet_names:
case_sheet_name = self.load_Excel()[sheet]
case_sheet_info.append(case_sheet_name.title) # 名称
case_sheet_info.append(case_sheet_name.max_row) # 最大行
case_sheet_info.append(case_sheet_name.max_column) # 最大列
# interval = 3 每3个切
if (len(case_sheet_info) % 3 == 0):
for idx in range(0, len(case_sheet_info), 3):
case_sheet.append([case_sheet_info[idx], case_sheet_info[idx+1], case_sheet_info[idx+2]])
return case_sheet
# except KeyError:
# pass
# print("KeyError:{}".format(KeyError))
def get_case(self):
"""
获取一行的数据
:param row: 传入行数从0开始
:return: 这一行数据
"""
case_list = []
for sheetname in self.get_sheet_info():
sheet = self.load_Excel()[sheetname[0]]
for i in range(3, sheetname[1]+1):
dict1 = dict(
case_id=sheet.cell(row=i, column=1).value,
interface_module=sheet.cell(row=i, column=2).value,
title=sheet.cell(row=i, column=3).value,
url=sheet.cell(row=i, column=4).value,
method=sheet.cell(row=i, column=5).value,
content_type=sheet.cell(row=i, column=6).value,
data=sheet.cell(row=i, column=7).value,
expected=sheet.cell(row=i, column=8).value
)
# print(type(dict1))
# print(dict1)
case_list.append(dict1)
# print(type(case_list))
return case_list
# def get_rows(self, min, max):
# """
# 获取指定行的数据
# :param min: 传入开始行,从0开始
# :param max: 传入结束行
# :return: 选择读取数据
# """
# rows = []
# for i in range(min, max):
# rows.append(self.get_row(i))
# return rows
#
#
# def get_cols(self, min, max):
# """
# 获取指定列的数据
# :param min: 传入开始列,从0开始
# :param max: 传入结束列
# :return: 选择读取数据
# """
# cols = []
# for i in range(min, max):
# cols.append(self.get_row(i))
# return cols
#
#
# def get_col(self, colx):
# """
# 获取一列的数据
# :param col: 传入列数从0开始
# :return: 这一列的数据
# """
# loc = self.table.col_values(colx)
# return loc
#
#
# def get_cell(self, cellx):
# """
# 获取单元格数据
# :param cellx: 元组类型的行和列
# :return: 数据
# """
# cell = self.table.cell_value(*cellx)
# return cell
# class WriteExcel():
# def __init__(self, sheet='Sheet1'):
# """
# 打开工作表
# :param sheet: 传入工作表名
# """
# self.data_address = rootDir + r'\data\webdata.xlsx'
# self.book = openpyxl.load_workbook(self.data_address)
# self.bs = self.book.get_sheet_by_name(sheet)
#
# def write_call(self, cellx, val):
# """
# 按单元格写入数据
# :param cellx: 传入单元格地址,例如:A1
# :param val: 传入写入值
# :return: None
# """
# self.bs[cellx] = val
#
# def write_row(self, val):
# """
# 按行在末尾逐行写入数据
# :param val: 列表数据类型
# :return: None
# """
# self.bs.append(val)
#
# def save_book(self):
# self.book.save(self.data_address)
if __name__ == '__main__':
e = HandleExcel()
# print(e.get_catalogue())
# print(e.get_firstrow_value())
# print(e.search_caseid_site())
# print(e.get_caseID_value())
# print(e.get_sheet_info())
print(e.get_case())
# print(e.get_sheet_info(1))
# e1 = e.get_rows(0, e.row_max1)
# print(e1, type(e1))
#
# w=WriteExcel()
# w.write_call('A1','WWWWWWWWW')
# w.save_book()
#环境配置 config.ini
[host] # 环境
root_url = http://**.**.**.**:****