自动化测试实战—petstore实战
补充:环境配置
# 配置日志
import logging
import osfrom logging.handlers import RotatingFileHandler# 绑定绑定句柄到logger对象
logger = logging.getLogger(__name__)
# 获取当前工具文件所在的路径
root_path = os.path.dirname(os.path.abspath(__file__))
# 拼接当前要输出日志的路径
log_dir_path = os.sep.join([root_path, '..', f'/logs'])
if not os.path.isdir(log_dir_path):os.mkdir(log_dir_path)
# 创建日志记录器,指明日志保存路径,每个日志的大小,保存日志的上限
file_log_handler = RotatingFileHandler(os.sep.join([log_dir_path, 'log.log']), maxBytes=1024 * 1024, backupCount=10)
# 设置日志的格式
date_string = '%Y-%m-%d %H:%M:%S'
formatter = logging.Formatter('[%(asctime)s] [%(levelname)s] [%(filename)s]/[line: %(lineno)d]/[%(funcName)s] %(message)s ', date_string)
# 日志输出到控制台的句柄
stream_handler = logging.StreamHandler()
# 将日志记录器指定日志的格式
file_log_handler.setFormatter(formatter)
stream_handler.setFormatter(formatter)
# 为全局的日志工具对象添加日志记录器
# 绑定绑定句柄到logger对象
logger.addHandler(stream_handler)
logger.addHandler(file_log_handler)
# 设置日志输出级别
一、单个接口冒烟用例完成
import requestsfrom pet_shop.utils.log_utils import loggerclass TestPetstoreSearch:'''宠物商店宠物查询单接口测试,冒烟'''def setup_class(self):self.base_url="https://petstore.swagger.io/v2/pet"#拼接宠物查询接口self.search_url = self.base_url+"/findByStatus"def test_search_pet(self):#宠物查询接口请求参数pet_status={"status":"available"}#发出查询接口请求r=requests.get(self.search_url,params=pet_status)#打印响应结果,t添加日志logger.info(r.text)#断言响应状态码为200assert r.status_code==200#断言响应结果中包含可用状态的宠物assert r.json()!= []#响应反回来的是一个数组,数组中每个对象都包含id'字段assert "id" in r.json()[0]
二、多个接口冒烟测试
import pytest
import requests
from pet_shop.utils.log_utils import loggerclass TestPetstoreSearch:"""宠物商店宠物查询单接口测试,冒烟"""def setup_class(self):self.base_url = "https://petstore.swagger.io/v2/pet"self.search_url = self.base_url + "/findByStatus"@pytest.mark.parametrize("status",["available", "pending", "sold"],ids=["available_pets", "pending_pets", "sold_pets"])def test_search_pet_params(self, status):"""参数化测试:分别查询 available, pending, sold 状态的宠物"""pet_status = {"status": status # 修复:这里应该是传入的参数 `status`,而不是字符串 "status"}r = requests.get(self.search_url, params=pet_status)logger.info(f"Response for status='{status}': {r.text}")assert r.status_code == 200assert r.json() != [] # 确保返回至少一个宠物assert "id" in r.json()[0] # 第一个宠物包含 id 字段
三、异常处理
1.测试用例设计
import pytest
import requests
from pet_shop.utils.log_utils import loggerclass TestPetstoreSearch:"""宠物商店宠物查询单接口测试,冒烟"""def setup_class(self):self.base_url = "https://petstore.swagger.io/v2/pet"self.search_url = self.base_url + "/findByStatus"@pytest.mark.parametrize("status",["available", "pending", "sold"],ids=["available_pets", "pending_pets", "sold_pets"])def test_search_pet_params(self, status):"""参数化测试:分别查询 available, pending, sold 状态的宠物"""pet_status = {"status": status # 修复:这里应该是传入的参数 `status`,而不是字符串 "status"}r = requests.get(self.search_url, params=pet_status)logger.info(f"Response for status='{status}': {r.text}")assert r.status_code == 200assert r.json() != [] # 确保返回至少一个宠物assert "id" in r.json()[0] # 第一个宠物包含 id 字段
#传入非指定状态数字,响应为空
#【异常】sraus 传入空字符串,响应为空
#不传入sarus,响应为空@pytest.mark.parametrize("status",["petstatus", "123456", ""],ids=["wrong_value", "number", "none_str"])def test_search_pet_failure(self,status):#查询异常pet_status = {"status": status # 修复:这里应该是传入的参数 `status`,而不是字符串 "status"}r = requests.get(self.search_url, params=pet_status)logger.info(f"Response for status='{status}': {r.text}")assert r.status_code == 200assert r.json() == [] # 确保返回至少一个宠物
AssertionError: assert r.json() == []
失败了
但实际返回的是一个包含多个宠物对象的列表,即使传入了一个无效的 status=""
(空字符串)。
这个测试的初衷是:
“当传入非法或无效的
status
值时,期望接口返回空数组[]
,表示没有匹配的宠物。”
但现实是:
- 即使你传
status=""
(空字符串),Swagger Petstore API 仍然返回了一些宠物,这些宠物的"status": ""
。 - 所以
r.json() != []
,断言失败。
✅ 结论
Swagger Petstore 的 /pet/findByStatus
接口 并没有严格校验 status
参数是否为 "available", "pending", "sold" 之一。它会尝试模糊匹配,甚至对空字符串也会返回 status
字段为空的对象。
#不传入参数 def test_search_pet_none_param(self):r = requests.get(self.search_url,)logger.info(r.text)assert r.status_code == 200assert r.json() == [] def test_search_pet_wrong_param(self):pet_status = {"key": "available" # 修复:这里应该是传入的参数 `status`,而不是字符串 "status"}r = requests.get(self.search_url, params=pet_status)logger.info(r.text)assert r.status_code == 200assert r.json() == [] # 确保返回至少一个宠
四、生成测试报告
环境:
- 目的:生成可视化报告展示测试结果。
- 操作:
- 安装依赖:pip install pytest allure-pytest
- 运行测试:pytest --alluredir=./allure-results
- 生成报告:allure serve ./allure-results
- 报告效果:
- 自动汇总通过/失败用例
- 显示Bug细节(如除数为0的异常)
- 图表展示用例覆盖率
pytest -vs test_pet_search.py --alluredir=./report --clean-alluredir
参数说明:
pytest
调用 pytest 测试框架运行测试。
-v
以详细模式(verbose)运行测试,显示每个测试用例的执行结果。
-s
允许在控制台输出测试用例中的标准输出(如 print 或日志信息)。
test_pet_search.py
指定要运行的测试文件。
--alluredir=./report
指定 Allure 报告的生成目录为当前路径下的 report 文件夹。
--clean-alluredir
在生成 Allure 报告前清空指定的报告目录(./report)。
生成测试报告中出现一个错误
❌ 问题 1:ModuleNotFoundError: No module named 'pet_shop'
test_pet_search.py
中写了:
from pet_shop.utils.log_utils import logger
这意味着 Python 会去 Python 的模块搜索路径(sys.path) 中找一个叫 pet_shop
的包。
但问题是:
pet_shop
是你的项目目录名,但它 不是一个被正确安装或加入到 Python 路径中的包。- Python 并不知道
pet_shop
是一个模块,所以报错No module named 'pet_shop'
。
✅ 解决方案
✅ 方法一:使用相对导入(推荐用于项目内部)
修改 test_pet_search.py
中的导入方式:
# 改成相对导入(如果 log_utils.py 在同级 utils 目录下)
from utils.log_utils import logger
所以下次要注意路径是否正确,以及尽量避免开头使用自建目录