Python测试框架搭建:Pytest与PyCharm环境配置与项目结构设计
1. 项目概述:为什么选择 Pytest 与 PyCharm 的组合?
如果你刚开始接触 Python 测试,或者厌倦了 unittest 那略显繁琐的 setUp 、 tearDown 和 assert 语句,那么 Pytest 绝对是一个能让你“真香”的框架。它用起来有多爽?简单来说,它允许你用最接近自然语言的 assert 来写断言,自动发现测试用例,并且拥有极其丰富的插件生态。而 PyCharm,作为 JetBrains 出品的 Python IDE,其智能提示、代码导航和集成的测试运行器,能让开发和测试的体验无缝衔接。今天,我就带你从零开始,在 Python 3.8 环境下,用 PyCharm 搭建一个功能完备、结构清晰的 Pytest 测试框架。这个框架不仅适用于单元测试,其良好的扩展性也能轻松支撑接口自动化、Web UI 自动化(如结合 Selenium)等更复杂的场景。无论你是测试新手想系统入门,还是开发同学想提升代码质量,这套组合拳都能让你事半功倍。
2. 环境准备与核心工具安装
搭建环境是第一步,也是最容易出问题的一步。很多人在这里卡住,不是因为步骤复杂,而是因为环境冲突或版本不匹配。我们一步步来,确保你的基础环境是干净、稳定的。
2.1 Python 3.8 的安装与配置
为什么选择 Python 3.8?这是一个在稳定性和新特性之间取得很好平衡的版本。它完全支持 Pytest 的所有现代功能,同时避免了 Python 3.7 之前的一些兼容性问题,也比 3.9+ 的版本在部分老库的兼容性上更友好。
首先,去 Python 官网下载安装包。这里有个关键点: 务必勾选“Add Python 3.8 to PATH” 。这个选项会将 Python 和 pip 添加到系统环境变量,让你能在任何命令行窗口直接使用 python 和 pip 命令。如果不勾选,后续手动配置会比较麻烦。
安装完成后,打开命令行(CMD 或 PowerShell),验证安装是否成功:
python --version
pip --version
应该分别显示 Python 3.8.x 和 pip 20.x.x 类似的版本信息。
接下来,我强烈建议你为这个测试项目创建一个独立的虚拟环境。虚拟环境可以理解为项目专属的“沙箱”,它能隔离不同项目所需的第三方库,避免版本冲突。使用 Python 自带的 venv 模块来创建:
# 切换到你的项目目录,例如 D:\Projects
cd D:\Projects
# 创建一个名为 `pytest_env` 的虚拟环境
python -m venv pytest_env
创建完成后,激活虚拟环境:
- Windows (CMD):
pytest_env\Scripts\activate.bat - Windows (PowerShell):
pytest_env\Scripts\Activate.ps1(可能需要先执行Set-ExecutionPolicy RemoteSigned允许脚本运行) - macOS/Linux:
source pytest_env/bin/activate
激活后,命令行提示符前会出现 (pytest_env) 字样,表示你已进入该虚拟环境。之后所有包的安装都只影响这个环境。
2.2 PyCharm 的安装与基础设置
前往 JetBrains 官网下载 PyCharm 社区版(Community)。社区版对于 Python 开发和测试来说功能已经完全足够,并且免费。专业版(Professional)主要多了对 Web 框架(如 Django)、数据库工具和科学计算模式的支持,对于纯测试框架搭建并非必需。
安装过程基本一路“Next”即可。安装完成后首次启动,我们需要进行几项关键配置:
-
配置 Python 解释器 :这是连接 PyCharm 和你刚创建的虚拟环境的关键一步。
- 打开 PyCharm,创建新项目(
New Project)。 - 在
Location选择你的项目路径(例如D:\Projects\my_pytest_framework)。 - 重点来了 :在
Python Interpreter部分,不要选择“New environment”,而是选择“Previously configured interpreter”。点击右侧的...按钮,然后选择Add Interpreter->Add Local Interpreter。 - 在弹出的窗口中,选择
Virtualenv Environment->Existing environment,然后导航到你刚才创建的pytest_env目录下的Scripts(Windows)或bin(macOS/Linux)文件夹,选择python.exe(或python)文件。 - 点击确定。这样,PyCharm 就会使用我们虚拟环境中的 Python 和 pip 来管理项目依赖。
- 打开 PyCharm,创建新项目(
-
优化测试运行配置 :为了让 PyCharm 更好地识别和运行 Pytest 测试,我们需要告诉它默认的测试运行器。
- 进入
File->Settings(Windows) 或PyCharm->Preferences(macOS)。 - 导航到
Tools->Python Integrated Tools。 - 在
Testing部分,将Default test runner从Unittests改为pytest。 - 这样,当你右键点击测试文件或测试函数时,“Run”和“Debug”选项就会默认使用 Pytest 来执行,并能在 PyCharm 的“Run”工具窗口看到漂亮的 Pytest 风格输出。
- 进入
注意 :有些教程会教你在 PyCharm 中直接创建虚拟环境,但我更推荐先在命令行创建好再关联。这样做的好处是,你对虚拟环境的控制力更强,比如可以方便地用命令行批量安装依赖,或者在无 GUI 的服务器环境下复现相同环境。这是一种更“工程化”的习惯。
3. Pytest 框架核心组件与项目结构设计
环境搭好了,我们开始设计框架本身。一个好的项目结构是后续可维护性和扩展性的基石。很多人一开始把所有测试用例、数据和逻辑混在一个文件里,随着用例增多,很快就会变得难以管理。
3.1 初始化项目与安装核心依赖
在 PyCharm 中打开你的项目,首先在项目根目录下创建一个 requirements.txt 文件。这个文件用于记录项目所有依赖包及其版本,方便在任何地方一键复现环境。初始内容如下:
pytest>=6.2.5
pytest-html>=3.1.1
pytest-xdist>=2.4.0
pytest-rerunfailures>=10.2
pytest-ordering>=0.6
- pytest : 核心框架。
- pytest-html : 生成美观的 HTML 测试报告。
- pytest-xdist : 实现测试用例的分布式执行,加速测试过程。
- pytest-rerunfailures : 对失败的测试用例进行重跑,应对网络抖动等偶发问题。
- pytest-ordering : 控制测试用例的执行顺序(谨慎使用,测试最好独立)。
然后,在 PyCharm 的终端(Terminal)中,确保虚拟环境已激活,执行安装:
pip install -r requirements.txt
如果下载慢,可以临时使用国内镜像源: pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
3.2 设计清晰的项目目录结构
接下来,创建以下目录结构。你可以直接在 PyCharm 的项目视图中右键新建目录和文件。
my_pytest_framework/
├── requirements.txt # 依赖清单
├── pytest.ini # Pytest 主配置文件
├── conftest.py # 全局夹具和钩子函数定义
├── common/ # 公共模块
│ ├── __init__.py
│ ├── logger.py # 日志模块
│ └── config.py # 配置管理(读取yaml/ini等)
├── test_cases/ # 测试用例集
│ ├── __init__.py
│ ├── test_api/ # 接口测试用例
│ │ ├── __init__.py
│ │ └── test_user_api.py
│ ├── test_web/ # Web UI测试用例
│ │ ├── __init__.py
│ │ └── test_login.py
│ └── test_unit/ # 单元测试用例
│ ├── __init__.py
│ └── test_calculator.py
├── test_data/ # 测试数据管理
│ ├── api_data.yaml
│ └── web_data.json
├── utils/ # 工具函数
│ ├── __init__.py
│ ├── http_client.py # 封装的HTTP请求客户端
│ └── file_reader.py # 文件读取工具
├── reports/ # 测试报告输出目录(.gitignore忽略)
│ └── assets/ # 报告所需的静态资源
└── logs/ # 日志文件输出目录(.gitignore忽略)
为什么这样设计?
- 按功能/层级分离 :
common放跨模块共享的代码;test_cases按测试类型分目录,用例文件以test_开头,Pytest 才能自动发现;test_data独立存放,实现数据与脚本分离;utils放可复用的辅助函数。 -
conftest.py的特殊性 :这个文件是 Pytest 的“魔法”文件。它可以放在任何目录下,其作用域是该目录及其所有子目录。根目录的conftest.py里定义的夹具(fixture)对整个项目生效。我们用它来定义那些最常用、最基础的夹具,比如驱动初始化、日志对象、配置读取等。 -
pytest.ini的作用 :这是 Pytest 的配置文件,用于定义默认的命令行选项、标记(marks)以及自定义一些行为。它能让你在运行pytest命令时不用每次都敲一长串参数。
4. 核心配置文件与基础夹具(Fixture)编写
有了结构,我们来填充核心内容。 pytest.ini 和 conftest.py 是框架的“大脑”和“中枢神经”。
4.1 配置 pytest.ini 文件
在项目根目录创建 pytest.ini ,内容如下:
[pytest]
# 指定测试用例的查找路径和文件名模式
testpaths = test_cases
python_files = test_*.py
python_classes = Test*
python_functions = test_*
# 定义自定义标记,用于分类运行测试
markers =
smoke: 冒烟测试用例
regression: 回归测试用例
slow: 运行缓慢的测试用例
# 添加默认的命令行参数
addopts =
-v # 显示详细结果
--strict-markers # 严格检查标记,未注册的标记会报错
--html=reports/report.html # 生成HTML报告
--self-contained-html # 将CSS等嵌入HTML,使报告单文件可独立查看
--capture=sys # 捕获输出,测试失败时打印
-p no:warnings # 不显示警告信息(可选,保持输出整洁)
# 配置日志(需要与你的logger模块配合)
log_cli = true
log_cli_level = INFO
log_cli_format = %(asctime)s [%(levelname)s] %(message)s
log_cli_date_format = %Y-%m-%d %H:%M:%S
这个配置做了几件事:
- 告诉 Pytest 去
test_cases目录下找以test_开头的.py文件,并识别其中以Test开头的类和以test_开头的函数作为测试用例。 - 定义了
smoke、regression、slow三个标记。你可以在测试用例上用@pytest.mark.smoke来标记它,然后通过pytest -m smoke只运行冒烟测试。 addopts里的参数会在每次执行pytest命令时自动生效。比如这里配置了自动生成 HTML 报告到reports目录。
4.2 编写 conftest.py 定义全局夹具
夹具(Fixture)是 Pytest 的灵魂,用于提供测试所需的环境准备和清理工作,比如数据库连接、临时文件、WebDriver 实例等。我们在根目录的 conftest.py 中定义最通用的夹具。
import pytest
import logging
import sys
from datetime import datetime
from common.logger import get_logger # 假设我们有一个自定义的日志模块
from common.config import Config # 假设我们有一个配置管理模块
# 1. 获取日志记录器的夹具
@pytest.fixture(scope="session", autouse=True)
def logger():
"""为整个测试会话提供一个日志记录器"""
log = get_logger(name="pytest_framework", level=logging.INFO)
yield log
# 测试会话结束后,可以在这里添加日志归档等清理操作
log.info("测试会话结束于 %s", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
# 2. 读取配置的夹具
@pytest.fixture(scope="session")
def config():
"""加载全局配置,如基础URL、数据库连接信息等"""
cfg = Config("config.ini") # 或 config.yaml
return cfg
# 3. 模拟一个Web Driver初始化的夹具(示例)
@pytest.fixture(scope="function") # 每个测试函数都新建一个driver
def browser_driver(config, logger):
"""为Web UI测试提供浏览器驱动实例"""
from selenium import webdriver
browser_type = config.get("browser", "chrome")
driver = None
if browser_type.lower() == "chrome":
options = webdriver.ChromeOptions()
options.add_argument("--headless") # 无头模式,不打开GUI,适合CI环境
options.add_argument("--disable-gpu")
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=options)
elif browser_type.lower() == "firefox":
# ... Firefox 配置
pass
else:
raise ValueError(f"不支持的浏览器类型: {browser_type}")
logger.info(f"初始化 {browser_type} 浏览器驱动")
yield driver # 将driver对象提供给测试用例使用
# 测试函数执行完毕后,执行清理
if driver:
driver.quit()
logger.info(f"关闭 {browser_type} 浏览器驱动")
# 4. 模拟一个HTTP客户端的夹具(示例)
@pytest.fixture(scope="class") # 每个测试类共享一个client
def api_client(config, logger):
"""为API测试提供预配置的HTTP客户端"""
from utils.http_client import HttpClient
base_url = config.get("api", "base_url")
client = HttpClient(base_url=base_url, logger=logger)
# 可以在这里添加通用的请求头,如认证token
# client.set_default_headers({"Authorization": f"Bearer {token}"})
yield client
# 可选的清理工作,如关闭持久连接
client.close()
# 5. 处理测试失败时截图的夹具(钩子函数形式)
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""在测试用例执行后生成报告,可用于失败时截图"""
outcome = yield
rep = outcome.get_result()
# 如果测试失败,且该测试用例使用了 `browser_driver` 夹具
if rep.when == "call" and rep.failed:
for fixture_name in item.fixturenames:
if "browser_driver" in fixture_name:
driver = item.funcargs[fixture_name]
if driver:
try:
# 截图并保存
screenshot_dir = "./reports/screenshots/"
os.makedirs(screenshot_dir, exist_ok=True)
screenshot_path = os.path.join(screenshot_dir, f"{item.name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png")
driver.save_screenshot(screenshot_path)
# 将截图路径添加到HTML报告中
if hasattr(rep, 'extra'):
from pytest_html import extras
rep.extra.append(extras.image(screenshot_path))
rep.extra.append(extras.html(f'<div><a href="{screenshot_path}" target="_blank">查看截图</a></div>'))
except Exception as e:
print(f"截图失败: {e}")
夹具(Fixture)的核心理解 :
-
scope参数 :定义了夹具的作用域和生命周期。function(默认):每个测试函数运行一次。class:每个测试类运行一次,该类中的所有测试方法共享这个夹具实例。module:每个.py文件运行一次。session:整个 Pytest 执行过程(一次命令行调用)只运行一次。像logger、config这种全局资源非常适合用session作用域。
-
autouse=True:表示该夹具会自动应用于所有符合条件的测试用例,无需在测试函数参数中显式声明。谨慎使用,避免不必要的开销。 -
yield语句 :这是夹具提供数据和执行清理的关键。yield之前的代码是“设置”阶段,返回值通过yield传递给测试用例。测试用例执行完毕后,会回到yield这里,执行其后的代码,即“清理”阶段。这比传统的setup/teardown更清晰。 - 钩子函数(Hook) :如
pytest_runtest_makereport,允许你在 Pytest 执行的特定生命周期插入自定义逻辑,功能非常强大。
5. 编写与组织测试用例
框架搭好了,现在我们来写一些实际的测试用例,看看如何利用我们设计的结构和夹具。
5.1 单元测试示例:测试一个计算器类
首先在 test_cases/test_unit/ 下创建 test_calculator.py 。假设我们有一个简单的计算器类 Calculator (可以定义在同目录或 utils 下)。
# 被测试的类 (可以放在 ../utils/calculator.py)
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
if b == 0:
raise ValueError("除数不能为零")
return a / b
# 测试用例文件 test_calculator.py
import pytest
from utils.calculator import Calculator
class TestCalculator:
"""测试计算器类"""
# 在每个测试方法前创建新的Calculator实例
@pytest.fixture(autouse=True)
def setup_calculator(self):
self.calc = Calculator()
yield
# 如果需要,可以在这里清理资源
self.calc = None
# 标记为冒烟测试
@pytest.mark.smoke
def test_add(self):
"""测试加法"""
result = self.calc.add(2, 3)
assert result == 5, f"预期 2+3=5,实际得到 {result}"
# Pytest的assert非常强大,可以直接比较列表、字典等复杂对象
assert self.calc.add(-1, 1) == 0
assert self.calc.add(0, 0) == 0
def test_subtract(self):
"""测试减法"""
assert self.calc.subtract(5, 3) == 2
assert self.calc.subtract(0, 5) == -5
# 参数化测试:用一组数据测试同一个逻辑
@pytest.mark.parametrize("a, b, expected", [
(6, 3, 2),
(10, 2, 5),
(0, 1, 0),
(-4, 2, -2),
])
def test_divide_normal(self, a, b, expected):
"""测试正常除法"""
result = self.calc.divide(a, b)
assert result == expected
def test_divide_by_zero(self):
"""测试除零异常"""
with pytest.raises(ValueError) as exc_info:
self.calc.divide(5, 0)
# 可以进一步断言异常信息
assert "除数不能为零" in str(exc_info.value)
要点解析 :
- 测试类与测试方法 :类名以
Test开头,方法以test_开头。Pytest 都能发现。 - 类级别的夹具 :
setup_calculator夹具使用autouse=True,这个类里的每个测试方法执行前都会先执行它,创建一个新的Calculator实例。这比在每个方法里写self.calc = Calculator()更清晰,也便于未来扩展(比如需要更复杂的初始化逻辑)。 - 标记(Mark) :
@pytest.mark.smoke将test_add标记为冒烟测试。之后可以用pytest -m smoke单独运行它。 - 参数化测试 :
@pytest.mark.parametrize是 Pytest 的杀手锏之一。它允许你用多组数据驱动同一个测试函数,极大减少了重复代码。上面的例子用四组数据测试了divide方法。 - 异常断言 :
pytest.raises上下文管理器用于断言代码块抛出了预期的异常。exc_info对象包含了异常的详细信息,可以用于进一步验证。
5.2 接口测试示例:使用夹具中的 HTTP 客户端
假设我们在 conftest.py 中定义了 api_client 夹具。现在在 test_cases/test_api/ 下创建 test_user_api.py 。
import pytest
import allure # 可选:使用Allure报告增强描述,需要额外安装 pytest-allure
class TestUserAPI:
"""用户相关API测试"""
# 测试获取用户列表
@pytest.mark.regression
def test_get_user_list(self, api_client, logger):
"""测试获取用户列表接口"""
# 使用夹具注入的 api_client 和 logger
endpoint = "/api/users"
logger.info(f"请求接口: GET {endpoint}")
response = api_client.get(endpoint)
# 断言状态码
assert response.status_code == 200, f"预期状态码200,实际为 {response.status_code}"
# 断言响应体结构
data = response.json()
assert isinstance(data, list), "响应体应该是一个用户列表"
# 如果有特定业务逻辑,例如第一个用户必须有id和name字段
if data:
first_user = data[0]
assert "id" in first_user
assert "name" in first_user
logger.info(f"成功获取到 {len(data)} 个用户")
# 测试创建用户
@pytest.mark.parametrize("user_data, expected_status", [
({"name": "Alice", "email": "alice@example.com"}, 201),
({}, 400), # 缺失必填字段,预期返回400
({"name": "Bob"}, 400), # 缺失email字段
])
def test_create_user(self, api_client, user_data, expected_status):
"""测试创建用户接口,参数化验证不同输入"""
endpoint = "/api/users"
response = api_client.post(endpoint, json=user_data)
assert response.status_code == expected_status
if expected_status == 201:
created_user = response.json()
assert created_user["name"] == user_data["name"]
assert "id" in created_user # 创建成功后应返回id
# 使用 allure 添加更丰富的报告描述(可选)
@allure.feature("用户管理")
@allure.story("删除用户")
@allure.severity(allure.severity_level.CRITICAL)
def test_delete_user(self, api_client, config):
"""测试删除用户接口(关键路径)"""
# 可能先创建一个测试用户,获取其ID
test_user = {"name": "TempUserForDelete"}
create_resp = api_client.post("/api/users", json=test_user)
if create_resp.status_code == 201:
user_id = create_resp.json()["id"]
delete_endpoint = f"/api/users/{user_id}"
delete_resp = api_client.delete(delete_endpoint)
assert delete_resp.status_code == 204 # 成功删除通常返回204 No Content
# 验证用户已被删除:再次查询应返回404
get_resp = api_client.get(f"/api/users/{user_id}")
assert get_resp.status_code == 404
接口测试要点 :
- 依赖注入 :测试函数通过参数
api_client和logger直接使用我们在conftest.py中定义的夹具,无需关心它们是如何初始化和清理的。这是依赖注入模式的典型应用,让测试逻辑非常干净。 - 断言层次 :从 HTTP 状态码到响应体结构,再到具体的业务字段,层层递进地进行断言。
- 测试数据分离 :参数化测试中的数据可以进一步外移到
test_data/目录下的 YAML 或 JSON 文件中,通过@pytest.mark.parametrize动态读取,实现彻底的数据驱动。 - Allure 集成 :虽然 Pytest-html 报告不错,但 Allure 报告在美观度和信息组织上更胜一筹。通过
@allure装饰器可以为测试用例添加功能模块、用户故事、严重等级等标签,生成非常专业的测试报告。这需要额外安装pytest-allure和 Allure 命令行工具。
6. 高级技巧、问题排查与最佳实践
框架跑起来之后,我们会遇到一些实际问题和优化需求。这部分分享一些我踩过坑后总结的经验。
6.1 测试用例的依赖、顺序与跳过
理想的单元测试应该是独立的、无状态的。但有时难免有特殊情况。
- 处理测试依赖(慎用) :Pytest 不鼓励测试之间有依赖。如果实在需要(比如B接口依赖A接口创建的资源),可以通过
@pytest.mark.dependency()装饰器来显式声明依赖关系(需要安装pytest-dependency插件)。但更好的做法是,每个测试自己准备所需的数据状态,或者在setup_class/setup_module夹具中准备公共前置条件。 - 控制执行顺序(慎用) :Pytest 默认按文件名和测试函数名排序执行。使用
pytest-ordering插件可以强行指定顺序(@pytest.mark.run(order=1)),但这违背了测试独立性原则。仅在极少数情况下(如性能测试有严格顺序)使用。 - 跳过测试 :使用
@pytest.mark.skip(reason="原因")无条件跳过,或@pytest.mark.skipif(condition, reason="原因")在条件满足时跳过。例如:import sys @pytest.mark.skipif(sys.platform != "win32", reason="仅需在Windows上运行") def test_windows_specific_feature(): pass
6.2 并发执行与失败重试
当测试用例成百上千时,串行执行会非常耗时。 pytest-xdist 插件可以让我们并行运行测试。
- 基本使用 :安装后,使用
-n参数指定并行进程数(auto表示自动检测CPU核心数)。pytest -n auto --html=reports/report.html - 注意事项 :并行测试时,要确保测试用例之间没有资源冲突(如写入同一个文件、使用同一个端口)。夹具的作用域需要仔细考虑,
session和module作用域的夹具在并行模式下可能会被多个进程共享或重复初始化,需要确保它们是线程/进程安全的。对于function作用域的夹具,通常没有问题。
pytest-rerunfailures 插件用于处理偶发性失败(如网络超时)。
- 使用 :通过
--reruns参数指定重试次数,--reruns-delay指定重试间隔。pytest --reruns 3 --reruns-delay 2 - 最佳实践 :不要滥用重试来掩盖真正的代码缺陷。它只应用于处理已知的、外部的、偶发的不稳定因素。
6.3 常见问题排查(FAQ)
-
Pytest 找不到测试用例?
- 检查文件名是否以
test_开头,或者类名以Test开头,函数名以test_开头。 - 检查
pytest.ini中的testpaths和python_files配置是否正确。 - 在项目根目录下运行
pytest --collect-only命令,查看 Pytest 发现了哪些测试项。
- 检查文件名是否以
-
夹具(Fixture)未找到?
- 确保夹具定义在测试文件本身、或该文件所在/上级目录的
conftest.py中。 - 检查夹具名称在测试函数参数中是否拼写正确。
- 检查夹具的作用域(scope)。如果一个
session作用域的夹具依赖于一个function作用域的夹具,会导致错误。
- 确保夹具定义在测试文件本身、或该文件所在/上级目录的
-
导入模块失败(ModuleNotFoundError)?
- 确保你的项目根目录(包含
pytest.ini的目录)在 Python 的模块搜索路径中。一个简单的方法是在根目录下创建一个空的__init__.py文件(使其成为一个包),或者在运行测试时确保当前工作目录是项目根目录。 - 在 PyCharm 中,右键点击项目根目录 ->
Mark Directory as->Sources Root。这会将此目录添加到 PyCharm 的源代码路径。
- 确保你的项目根目录(包含
-
生成的 HTML 报告没有样式或图片?
- 确保
pytest.ini中addopts包含了--self-contained-html参数,这样 CSS 会内嵌到 HTML 中。 - 如果报告需要引用外部截图,确保截图路径是相对路径且能被报告文件访问。
pytest-html的extras功能可以帮助嵌入图片。
- 确保
-
测试用例中有打印语句,但控制台没输出?
- Pytest 默认会捕获所有标准输出(stdout/stderr),只在测试失败时显示。使用
-s参数可以禁用捕获,看到所有打印信息:pytest -s。 - 或者,使用
logger.info()来记录信息,并通过配置log_cli = true在控制台实时查看日志。
- Pytest 默认会捕获所有标准输出(stdout/stderr),只在测试失败时显示。使用
6.4 持续集成(CI)集成建议
一个成熟的测试框架最终要融入 CI/CD 流水线。这里有一些关键点:
- 环境隔离 :在 CI 服务器(如 Jenkins、GitLab CI、GitHub Actions)上,每次构建都应在一个全新的虚拟环境或容器中安装依赖(
pip install -r requirements.txt)。 - 命令与参数 :CI 脚本中运行测试的命令应包含生成机器可读报告(如 JUnit XML)和人工可读报告(HTML)的参数。
pytest -v --junitxml=reports/junit.xml --html=reports/report.html --self-contained-html - 结果收集 :配置 CI 工具收集
reports/junit.xml文件,以便在流水线界面展示测试通过率、趋势图等。将reports/report.html作为构建产物存档,供随时查看。 - 并行与重试 :在 CI 中充分利用
pytest-xdist并行执行以缩短反馈时间。对于不稳定测试,可以配置pytest-rerunfailures。 - 失败通知 :配置 CI 在测试失败时通过邮件、钉钉、Slack 等渠道通知相关人员。
从零搭建一个 Pytest 测试框架,远不止是安装一个包和写几个 assert 。它涉及到项目结构设计、配置管理、夹具的生命周期、测试数据的组织、报告的生成以及与整个开发流程的集成。这套基于 Python 3.8 和 PyCharm 的方案,提供了一个兼顾灵活性、可维护性和工程实践性的起点。你可以根据实际项目需求,轻松地引入 Page Object 模型来组织 UI 测试,或者集成 Requests、Selenium、Appium 等库来扩展测试能力。最重要的是,理解每个组件背后的“为什么”,这样你才能在其基础上进行有效的定制和优化,让它真正成为提升你和团队研发效率的利器。
更多推荐
所有评论(0)