一、自动化接口测试流程详解
分层设计
各模块之间的逻辑关系
1. 数据准备阶段:读测试剧本
-
做什么:就像拍电影需要剧本,测试也需要提前写好的“测试剧本”(YAML文件)。
-
怎么做:
- TestApi类用
YamlTools
工具读取car_login.yaml
、add_cars.yaml
文件。
- TestApi类用
class TestApi:
# 读取YAML文件中的测试数据,并存储在类变量中
car_login_data = YamlTools.read_yaml_file("api\\car_login.yaml")[0] # 读取登录接口的测试数据
add_cars_data = YamlTools.read_yaml_file("api\\add_cars.yaml")[0] # 读取添加车辆接口的测试数据
delete_cars_data = YamlTools.read_yaml_file("api\\delete_cars.yaml")[0] # 读取删除车辆接口的测试数据
-
这些文件里写了(.yaml):要测试哪些接口(登录,添加,删除)、请求的URL、参数、预期结果。
-
登录所需的驱动数据
-
request:
epic: carRental项目
feature: 租车系统
story: 登录接口
method: post
url: /carRental/login/login.action
headers: null
cookies: null
parametrize:
- {title: 输入正确的用户名称和密码,登录成功,data: {loginname: "admin",pwd: "123456"},expect: {status_code: '0'},params: null,json: null,files: null}
- 添加车辆所需的驱动数据
-
request:
epic: carRental项目
feature: 租车系统
story: 添加车辆接口
method: post
url: /carRental/car/addCar.action
headers: null
cookies: null
parametrize:
- {title: 输入合法车辆信息,data: {carnumber: "010",cartype: null,color: null,carimg: {name: "mf",path: "C:\\car_pictures\\red_car.jpg"},description: null,price: "234",rentprice: 987,deposit: 9876,isrenting: 0},expect: {code: '0',msg: "添加成功"},params: null,json: {code: "carnumber"},files: null}
- {title: 输入空车牌号添加失败,data: {carnumber: null,cartype: null,color: null,carimg: {name: "mf",path: "C:\\car_pictures\\red_car.jpg"},description: null,price: "234",rentprice: 987,deposit: 9876,isrenting: 0},expect: {code: '-1',msg: "添加失败"},params: null,json: {code: "carnumber"},files: null}
- {title: 车辆信息全部为空,data: {carnumber: null,cartype: null,color: null,carimg: {name: "mf",path: "C:\\car_pictures\\red_car.jpg"},description: null,price: null,rentprice: null,deposit: null,isrenting: 0},expect: {code: '-1',msg: "添加失败"},params: null,json: {code: "carnumber"},files: null}
- 删除车辆信息所需的驱动数据
-
request:
epic: carRental项目
feature: 租车系统
story: 删除车辆接口
method: post
url: /carRental/car/deleteBatchCar.action
headers: null
cookies: null
parametrize:
- {title: 输入正确车牌号,删除成功,data: {ids: '007'},expect: {code: 0,msg: "删除成功"},params: null,json: null,files: null}
- {title: 输入错误车牌号,失败,data: {ids: '009'},expect: {code: -1,msg: "删除失败"},params: null,json: null,files: null}
2. 测试执行阶段:按剧本演戏
-
第一步:登录系统
-
TestApi类调用
Event.car_login()
方法,发送账号密码到登录接口 -
def test_car_login(self, parametrize): """ 测试车辆登录接口 :param parametrize: 参数化数据,包含请求参数和预期结果 """ # 调用Event类的car_login方法,发送登录请求 `rp = Event.car_login(request=TestApi.car_login_data["request"], parametrize=parametrize)
Event类中Event.car_login()
方法 -
@classmethod @allure.step("登录系统") def car_login(self, request, parametrize): # 调用BaseRequest类的execute_api_request方法,执行API请求。 # request参数包含API请求的详细信息,parametrize参数包含请求的参数。 rp = BaseRequest.execute_api_request(request=request, parametrize=parametrize) # 返回API请求的响应结果。 return rp
-
-
第二步:上传车辆图片和添加车辆
-
1.上传图片
- TestApi类调用
Event.upload_car_picturce()
,从本地选择图片(C:\car_pictures\red_car.jpg
)。
- TestApi类调用
@classmethod
@allure.step("上传图片")
def upload_car_picturce(self, request, parametrize):
# 从parametrize参数中获取图片的文件路径和文件名。
file_path = parametrize["data"]["carimg"]["path"]
filename = parametrize["data"]["carimg"]["name"]
# 以二进制读取模式打开图片文件。
with open(file_path, 'rb') as f:
# 构造文件字典,用于上传文件。
files = {filename: (file_path, f)}
# 将文件字典添加到parametrize参数中。
parametrize["files"] = files
# 保存原始的URL,以便后续恢复。
url1 = request["url"]
# 修改请求的URL为上传文件的API地址。
request["url"] = "/carRental/file/uploadFile.action"
# 调用BaseRequest类的execute_api_request方法,执行上传文件的API请求。
rp = BaseRequest.execute_api_request(request=request, parametrize=parametrize)
# 上传完成后,清空parametrize中的files字段。
parametrize["files"] = None
# 恢复原始的URL。
request["url"] = url1
# 关闭文件。
f.close()
# 返回API请求的JSON格式响应结果。
return rp.json()
-
程序自动把图片包装成HTTP请求,发送到服务器的“上传接口”。
2.添加车辆
-
TestApi类调用
Event.add_cars()
,把车牌号、价格、图片地址等信息发送到“添加车辆接口”。 -
@classmethod @allure.step("添加车辆") def add_cars(cls, request, parametrize, request2, parametrize2): # 调用car_login方法,执行登录操作。 Event.car_login(request=request, parametrize=parametrize) # 调用upload_car_picturce方法,上传车辆图片,并获取响应结果。 rp = Event.upload_car_picturce(request=request2, parametrize=parametrize2) # 从响应结果中获取图片的URL。 data1 = rp["data"] # 将图片的URL添加到parametrize2参数中。 parametrize2["data"]["carimg"] = data1["src"] # 调用BaseRequest类的execute_api_request方法,执行添加车辆的API请求。 rp = BaseRequest.execute_api_request(request=request2, parametrize=parametrize2) # 返回API请求的响应结果。 return rp
正向用例结果
-
程序会自动检查:是否用了刚上传的图片地址?参数是否合法?
-
第三步:删除车辆
-
TestApi类调用
Event.delete_car()
,发送要删除的车牌号到“删除接口”。 -
# 使用@classmethod装饰器定义类方法,表示该方法属于类而不是实例。 # 使用@allure.step装饰器,表示在Allure报告中标记该步骤为“删除车辆信息”。 @classmethod @allure.step("删除车辆信息") def delete_car(cls, request3, parametrize3): # 调用BaseRequest类的execute_api_request方法,执行删除车辆信息的API请求。 rp = BaseRequest.execute_api_request(request=request3, parametrize=parametrize3) # 返回API请求的响应结果。 return rp
服务器会检查车牌是否存在,返回“删除成功”或“删除失败”。
-
3. 结果验证阶段:检查演得对不对
-
检查点1:状态码是否为200(表示网络请求成功)。
-
检查点2:返回的
msg
字段是否和预期一致(如“添加成功”)。 -
例如:
assert rp.json()["msg"] == "添加成功" # 如果实际结果不符,测试失败
4. 报告生成阶段:制作成绩单
- 用
Allure
生成漂亮的测试报告:-
展示每个步骤的请求参数、服务器响应。
-
用颜色标记成功(绿色)和失败(红色)的测试用例。
-
报告中会显示“上传图片”步骤的URL、上传的文件名。
源代码:
import logging # 日志包 import logging.handlers import os.path import time from config.api.conf import LOG_DIR import colorlog # 对日志信息进行颜色划分输出 log_colors_config = { 'DEBUG': 'white', 'INFO': 'green', 'WARNING': 'yellow', 'ERROR': 'red', 'CRITICAL': 'bold_red', } # 创建日志管理器 log = logging.getLogger('log_name') # api_log = logging.getLogger('api')# # 准备时间字符串,用在log文件名 #daytime = time.strftime('%Y-%m-%d_%H-%M-%S') daytime = time.strftime('%Y-%m-%d') # 项目根目录下 的 log 文件夹 if not os.path.exists(LOG_DIR): os.makedirs(LOG_DIR) print("目录已经创建") # 目录不存在时创建 目录 # 创建文件日志的路径文件名 filename = LOG_DIR + f'/run_api_log_{daytime}.log' # 命令台 输出对象 # 创建命令台处理器 sh = logging.StreamHandler() # 创建文件处理器 # when 是 日志分割标志 # interval 一天生成一个 # 日志的数量 控制 30 fh = logging.handlers.TimedRotatingFileHandler(filename, when='midnight', interval=1, backupCount=15,encoding='utf-8') # 文件日志的格式 fmt = "[%(levelname)s] [%(asctime)s.%(msecs)03d] : %(message)s : %(filename)s -> %(funcName)s line:%(lineno)d" # 命令台的日志格式 smt = "[%(levelname)s] %(log_color)s [%(asctime)s.%(msecs)03d] : %(message)s : %(filename)s -> %(funcName)s line:%(lineno)d" # 日志级别设置, 命令台日志 和 文件日志级别,可以不同,进行单独设置,设置冲突时,谁高以谁为准 # 利用日志格式,生成 文件 和日志格式处理器 file_formatter = logging.Formatter(fmt, datefmt='%Y_%m_%d %H:%M:%S') console_formattre = colorlog.ColoredFormatter(smt,datefmt='%Y_%m_%d %H:%M:%S',log_colors=log_colors_config) sh.setFormatter(console_formattre) fh.setFormatter(file_formatter) log.setLevel(logging.DEBUG) fh.setLevel(logging.DEBUG) sh.setLevel(logging.DEBUG)
-
5. 通知阶段:发邮件给审核,准备上映!!!
-
用
send_email_with_attachment()
函数:-
把测试报告(HTML文件)作为附件。
-
自动登录QQ邮箱,发送给指定收件人(如项目经理)。
-
邮件正文会写:“测试已完成,请查收附件”。
-
源代码
-
from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText import smtplib import os from data.api.env import MAIL_ENV def send_email_with_attachment(sender_email=MAIL_ENV["发件人邮箱"], auth_code=MAIL_ENV["授权码"], receiver_emails=MAIL_ENV["收件人列表"], subject=MAIL_ENV["邮件标题"], body_html=MAIL_ENV["邮件正文"], attachment_path=MAIL_ENV["附件目录"]): # 创建邮件 mail = MIMEMultipart() mail['From'] = sender_email mail['To'] = ', '.join(receiver_emails) # 这只是为了显示,实际发送时不需要 mail['Subject'] = subject # 添加附件 if os.path.exists(attachment_path): with open(attachment_path, 'r', encoding='utf-8') as file: attachment_content = file.read() attachment = MIMEText(attachment_content, 'html', 'utf-8') # 如果附件是 HTML,使用 'html' attachment['Content-Disposition'] = f'attachment; filename="{os.path.basename(attachment_path)}"' mail.attach(attachment) else: print(f"警告: 附件路径 {attachment_path} 不存在") # 添加正文 body = MIMEText(body_html, "html", "utf-8") mail.attach(body) # 邮箱设置 host = 'smtp.qq.com' port = 465 # 使用 SSL 连接 # 发送邮件 try: smtp = smtplib.SMTP_SSL(host, port) # 使用 SMTP_SSL 类进行 SSL 连接 smtp.login(sender_email, auth_code) smtp.sendmail(sender_email, receiver_emails, mail.as_string()) smtp.quit() # 使用 quit() 退出连接 print("邮件发送完毕") except Exception as e: print(f"邮件发送失败: {e}")
-
二、代码模块逻辑关系详解
- TestApi类:导演,负责指挥整个测试流程。
- 告诉
Event
类:“先登录,再上传图片,最后添加车辆”。 - 通过
@pytest.mark.parametrize
读取YAML中的不同测试场景(如正确密码、错误密码)。
- 告诉
- Event类:演员,执行具体的操作。
- 每个方法(如
car_login
)对应一个业务步骤。 - 依赖
BaseRequest
发送HTTP请求,就像演员需要道具组准备道具。
- 每个方法(如
- BaseRequest类:道具组,负责处理HTTP请求细节。
- 维护一个
Session
对象,自动保存Cookies(如登录后的身份信息)。 - 把请求参数、URL拼接完整,并记录到日志和Allure报告中。
- 维护一个
- YamlTools类:剧本管理员,负责读取和解析YAML文件。
- 将YAML中的测试数据转换成Python字典,供
TestApi
使用。
- 将YAML中的测试数据转换成Python字典,供
- 日志模块:场记,记录每一步发生了什么。
- 控制台显示彩色日志(绿色是成功,红色是错误)。
- 每天自动生成一个日志文件,最多保留15天。
- 邮件模块:播报员,测试结束后发通知。
- 把Allure生成的报告打包成邮件附件,通过QQ邮箱发送。
三、核心代码模块功能说明
1. TestApi类:测试用例组织
-
功能:定义具体的测试用例,如“测试登录接口”。
-
关键技术:
-
参数化测试:用
@pytest.mark.parametrize
实现多场景测试。# @pytest.mark.parametrize("parametrize", car_login_data["parametrize"]) def test_car_login(self, parametrize): # 调用Event.car_login()发送请求 # 断言检查结果
-
Allure报告标记:用
@allure.epic
、@allure.feature
分层展示测试报告。
-
2. BaseRequest类:HTTP请求处理
-
功能:发送HTTP请求,处理响应,记录日志。
-
关键技术:
-
Session保持:自动携带Cookies,实现登录状态维持。
sess = requests.session() # 创建一个会“记住”Cookies的会话 rp = sess.post(url, data=data) # 第二次请求自动带上登录后的Cookies
-
Allure附件:把请求参数添加到测试报告中。
allure.attach(url, "请求地址") # 在报告中显示“url地址:http://http://localhost:63342/test_car/reports/”
-
3. Event类:业务流程封装
-
功能:串联多个接口,实现完整业务流。
-
关键技术:
-
步骤串联:添加车辆前必须先登录和上传图片。
def add_cars(...): Event.car_login(...) # 先登录 可以省略 rp = Event.upload_car_picturce(...) # 再上传图片 # 最后添加车辆
-
文件上传处理:自动读取本地图片并包装成HTTP文件格式。
with open(file_path, 'rb') as f: files = {filename: (file_path, f)} # 构造文件字典 parametrize["files"] = files # 添加到请求参数
-
4. YamlTools类:数据驱动管理
-
功能:从YAML文件读取测试数据,实现数据和代码分离。
-
关键技术:
-
动态路径解析:自动拼接项目路径,避免硬编码。
path = YamlTools.get_project_path() + "testdata\\" + path
-
YAML解析:将YAML内容转换为Python字典。
data = yaml.load(file_content, Loader=yaml.FullLoader)
-
5. 日志模块:执行过程跟踪
-
功能:记录测试过程中的详细信息,便于排查问题。
-
关键技术:
-
日志轮转:每天生成一个新日志文件,最多保留15天。
fh = logging.handlers.TimedRotatingFileHandler(filename, when='midnight', backupCount=15)
-
彩色输出:不同级别的日志显示不同颜色(如错误是红色)。
-
console_formatter = colorlog.ColoredFormatter(..., log_colors=log_colors_config)
6. 邮件模块:结果通知
-
功能:测试完成后自动发送邮件报告。
-
关键技术:
-
MIME协议:构建带附件的邮件。
mail = MIMEMultipart()
mail.attach(MIMEText(attachment_content, 'html', 'utf-8'))
SSL加密传输:通过QQ邮箱的465端口安全发送邮件。
smtp = smtplib.SMTP_SSL('smtp.qq.com', 465)
四、关键代码段说明
1. 参数化测试用例设计
@pytest.mark.parametrize("parametrize", car_login_data["parametrize"])
def test_car_login(self, parametrize):
# 动态加载YAML中的测试场景
rp = Event.car_login(...)
assert rp.status_code == 200
2. 文件上传处理逻辑
with open(file_path, 'rb') as f:
files = {filename: (file_path, f)} # 符合requests的文件格式
# 临时修改URL实现接口切换
3. 日志模块配置
fh = logging.handlers.TimedRotatingFileHandler(
filename, when='midnight', backupCount=15) # 自动日志轮转
4. 邮件内容构建
mail.attach(MIMEText(attachment_content, 'html', 'utf-8')) # 支持HTML附件
五、执行效果预览
- Allure报告展示
- 分层展示Epic/Feature/Story
- 每个步骤的请求/响应详情
- 失败用例的错误上下文
- 邮件报告效果
- 带HTML附件的专业测试报告
with open(file_path, ‘rb’) as f:
files = {filename: (file_path, f)} # 符合requests的文件格式
临时修改URL实现接口切换
- 带HTML附件的专业测试报告
**3. 日志模块配置**
```python
fh = logging.handlers.TimedRotatingFileHandler(
filename, when='midnight', backupCount=15) # 自动日志轮转
4. 邮件内容构建
mail.attach(MIMEText(attachment_content, 'html', 'utf-8')) # 支持HTML附件
详细源代码以及租车管理系统如何配置可以私聊我,私发哈,