简介:直接导入PyCharm就能跑的pytest测试工程,内置登录流程(用户名密码校验、token获取、会话保持)和典型HTTP接口测试用例,覆盖GET/POST请求、状态码断言、响应字段提取与校验。项目结构清晰,包含独立的testcase目录存放用例脚本,interface_testcase专用于接口层测试,aaa_login.py封装通用登录逻辑供复用;通过main.py单点执行或all.py批量运行,自动生成HTML格式测试报告(report.html),支持失败重试、用例标签分类(如@pytest.mark.smoke)。已配置标准pytest.ini(含默认参数、插件启用、路径映射),集成pytest-html、requests等依赖(见requirements.txt),附带初始化文件(init.py)确保模块识别,.gitignore屏蔽缓存与IDE文件,.pytest_cache目录预置兼容本地执行。两份笔记文件(第一节笔记.py、第二节笔记.py)逐行注释关键步骤,说明fixture使用、conftest.py作用域、session级登录前置等核心实践,README.md提供环境准备与运行指引,适合刚接触接口自动化的新手快速验证真实业务场景。
1. 这不是“跑个demo”,而是一套能直接塞进你项目里用的测试骨架
我带过不少刚转做测试开发的新人,也帮五六家公司重构过接口测试体系。最常听到的一句话是:“老师,网上那些pytest教程我都看了,但一到自己写登录流程就卡住——token怎么传?会话怎么保持?失败了怎么重试?报告怎么好看点?”
这恰恰说明问题不在“会不会写assert”,而在真实业务场景中,测试不是孤立的断言堆砌,而是一整套有状态、有依赖、可维护、能交付的工程实践。
这个“Pytest实战包”就是我从三个真实电商中台项目里抽出来的最小可用测试骨架——它不讲概念,不画大饼,所有文件名、目录结构、配置参数,都是我在凌晨两点改完线上登录接口后,第二天早上直接拷贝进新项目的那一套。它包含的不是“示例”,而是已验证过的生产级约定:比如为什么aaa_login.py必须放在根目录而不是testcase/下;为什么conftest.py里要定义session级别的fixture而不是function;为什么pytest.ini里--tb=short后面必须跟--strict-markers;甚至为什么.gitignore里要单独加一行report/*.html而不是笼统写report/。
关键词里写的“pytest实战、登录测试、接口测试”,其实对应着三层现实挑战:
- pytest实战 = 不是pip install pytest然后写个test_add.py就算完事,而是环境隔离(pyvenv.cfg)、执行策略(all.py vs main.py)、插件协同(pytest-html + pytest-rerunfailures)、IDE深度集成(PyCharm自动识别test_前缀+fixture跳转);
- 登录测试 = 不是模拟一次POST就结束,而是覆盖用户名密码校验(401)、验证码绕过(mock)、token有效期处理(自动刷新)、多角色权限隔离(admin/user/guest)、Cookie与Header双模式会话保持;
- 接口测试 = 不是只测200成功,而是对GET/POST/PUT/DELETE全方法覆盖,对400/401/403/429/500等错误码做差异化断言,对响应体里的嵌套JSON字段(如data.items[0].price)做安全提取,对响应头里的X-RateLimit-Remaining做数值校验。
它适合谁?
- 刚学完requests和pytest基础,但面对公司登录接口文档就发懵的测试工程师;
- 开发想给自己的Flask/FastAPI服务加一层回归保障,又不想花三天搭框架的后端同学;
- 质量负责人需要在两周内给外包团队交付一套可审计、可交接、带注释的测试资产。
它不能做什么?
- 它不替代你的业务逻辑理解——你得自己填aaa_login.py里的BASE_URL和LOGIN_ENDPOINT;
- 它不解决网络超时这种基础设施问题——但告诉你怎么用@pytest.mark.flaky(reruns=3, reruns_delay=2)精准控制重试;
- 它不教你Python语法——但每行笔记文件(第一节笔记.py、第二节笔记.py)都像我在你工位旁站着讲解:“这里yield之后的代码会在整个测试session结束时执行,相当于Java里的@AfterClass,但更轻量”。
接下来,我会带你一层层拆开这个包——不是罗列文件,而是还原我当时在键盘上敲下每一行时的真实思考:为什么选这个结构?踩过什么坑?哪些配置看似冗余实则救命?
2. 整体设计思路:为什么这个目录结构能扛住半年迭代?
2.1 目录分层不是为了“看起来专业”,而是为了解耦变更影响域
很多新手一上来就建tests/目录,然后把所有东西塞进去:登录脚本、接口用例、工具函数、配置文件……结果改一个登录逻辑,得翻遍17个文件找哪里硬编码了密码。这个包的目录结构,是我用三个项目踩坑后定型的:
.
├── aaa_login.py # 【核心契约】登录能力封装层(对外提供login_as_user())
├── testcase/ # 【用例容器】纯测试逻辑,不碰任何实现细节
│ ├── test_login.py # 场景化用例:正常登录、密码错误、账号锁定
│ └── __init__.py # 空文件,仅声明该目录为Python包
├── interface_testcase/ # 【接口契约层】对接口协议做原子级验证
│ ├── test_user_info.py # GET /api/v1/user/me → 断言status_code==200 & name字段存在
│ ├── test_order_list.py # GET /api/v1/orders?limit=10 → 校验分页字段total_count
│ └── __init__.py
├── conftest.py # 【全局上下文】session级fixture(登录态)、命令行参数注册
├── pytest.ini # 【执行宪法】默认参数、路径映射、标记规则、插件启用
├── requirements.txt # 【依赖契约】精确到小数点后两位(requests==2.31.0)
├── main.py # 【单点入口】运行当前目录下所有test_*.py(调试用)
├── all.py # 【批量入口】运行testcase/ + interface_testcase/(CI用)
├── report.html # 【交付物】每次执行覆盖生成(注意.gitignore已屏蔽)
└── 第一节笔记.py # 【认知脚手架】逐行解释conftest.py中fixture作用域选择逻辑
关键设计点解析:
- aaa_login.py独立于testcase/之外:这是刻意为之。登录逻辑是被依赖方,不是测试用例。当公司从JWT切换到Session Cookie时,你只需修改aaa_login.py里的get_session()方法,所有调用它的测试用例(test_login.py、test_user_info.py)完全不用动。如果把它塞进testcase/,等于把契约和实现混在一起,违背单一职责。
- interface_testcase/与testcase/物理隔离:前者验证“接口是否按协议工作”,后者验证“业务流程是否走通”。比如test_login.py里会调用aaa_login.py.login_as_user()拿到token,再用这个token去interface_testcase/test_user_info.py里请求用户信息——两层解耦,让接口变更(如字段重命名)只影响interface_testcase/,不影响登录流程本身。
- conftest.py放在根目录而非子目录:pytest会自动向上查找conftest.py。放在根目录意味着testcase/和interface_testcase/都能共享同一个login_session fixture。如果把它放进testcase/conftest.py,interface_testcase/就无法使用,你得复制一份,违背DRY原则。
提示:PyCharm导入时,右键根目录 → “Mark Directory as” → “Sources Root”,否则
from aaa_login import login_as_user会报红。这不是bug,是IDE没识别到包路径——pytest.ini里testpaths = testcase interface_testcase已经告诉pytest去哪里找用例,但IDE需要手动指定源码根。
2.2 配置即代码:pytest.ini里的每一行都是血泪教训
别小看这个只有12行的pytest.ini,它是我删掉第7版草稿后定稿的。内容如下(已脱敏):
[tool:pytest]
# 1. 执行路径:明确告诉pytest只扫描这两个目录,避免误扫其他.py文件
testpaths = testcase interface_testcase
# 2. 模块发现规则:必须以test_开头且.py结尾,排除非测试文件
python_files = test_*.py
python_classes = Test*
python_functions = test_*
# 3. 默认参数:--tb=short减少干扰信息;--strict-markers强制标记合法性检查
addopts = --tb=short --strict-markers -v --html=report.html --self-contained-html
# 4. 插件启用:pytest-html生成报告;pytest-rerunfailures支持失败重试
plugins = pytest_html pytest_rerunfailures
# 5. 标记分类:smoke=冒烟测试(5分钟内跑完),regression=全量回归(30分钟+)
markers =
smoke: 高优先级核心路径测试
regression: 全量功能回归测试
# 6. 缓存目录:避免每次执行都重建.cache,加速连续调试
cache_dir = .pytest_cache
为什么这样配?
- --tb=short:新手第一次看到AssertionError: assert 401 == 200时,根本不需要看几百行traceback,短格式直接定位到断言行。等你熟悉了再切--tb=long。
- --strict-markers:这是防坑神器。当你写@pytest.mark.smok(少了个e)时,pytest会直接报错:“Unknown marker ‘smok’”,而不是默默忽略——避免因拼写错误导致标记失效,回归测试漏跑。
- --html=report.html --self-contained-html:生成单文件HTML报告,内嵌CSS/JS,发给产品同事看时不用打包一堆assets文件夹。report.html在.gitignore里被屏蔽,确保不会误提交。
- markers段落:不是摆设。在CI脚本里你可以写pytest -m "smoke" -x(遇到第一个失败就停止),快速验证部署是否基本可用;pytest -m "not regression"跳过耗时长的回归用例,只跑冒烟。
注意:
pytest.ini必须放在项目根目录,且文件名严格为pytest.ini(不是pyproject.toml或setup.cfg)。我见过太多人因为文件名写成pytest.conf导致配置不生效,debug半小时才发现是文件名错了。
2.3 初始化文件(__init__.py)的隐藏使命:不只是让目录变包
目录树里出现了三个__init__.py,位置分别是:
- 根目录(空文件)
- testcase/目录下(空文件)
- interface_testcase/目录下(空文件)
新手常问:“空文件有什么用?”——它的作用远不止“让Python识别为包”。
- 根目录
__init__.py:为aaa_login.py提供顶层命名空间。当你在test_login.py里写from aaa_login import login_as_user,Python会从sys.path里找aaa_login.py。根目录的__init__.py确保该目录被加入sys.path(PyCharm自动处理,但命令行执行python -m pytest时依赖此文件)。 testcase/和interface_testcase/下的__init__.py:触发pytest的模块发现机制。pytest通过importlib.util.spec_from_file_location()动态加载测试模块,而该机制要求目标路径是合法Python包(即含__init__.py)。没有它,pytest testcase/会提示“No tests were found”。
实操心得:如果你删掉
testcase/__init__.py,执行pytest testcase/会报错;但执行pytest testcase/test_login.py却能成功——因为后者直接指定了文件路径,绕过了包发现逻辑。这正是为什么pytest.ini里要配python_files = test_*.py:它告诉pytest“只加载test_开头的文件”,而不依赖目录结构。
3. 核心细节解析:登录验证与接口测试的实操要点
3.1 登录逻辑封装(aaa_login.py):为什么不用requests.Session?
先看aaa_login.py核心代码(已简化):
import requests
import json
BASE_URL = "https://api.example.com"
LOGIN_ENDPOINT = "/auth/login"
def login_as_user(username: str, password: str) -> dict:
"""返回包含token和session_id的字典,供后续接口调用"""
payload = {"username": username, "password": password}
headers = {"Content-Type": "application/json"}
response = requests.post(
url=f"{BASE_URL}{LOGIN_ENDPOINT}",
data=json.dumps(payload),
headers=headers,
timeout=10
)
if response.status_code == 200:
data = response.json()
return {
"token": data.get("access_token"),
"session_id": response.cookies.get("sessionid"), # 从Cookie取
"user_id": data.get("user_id")
}
elif response.status_code == 401:
raise ValueError("Login failed: invalid credentials")
else:
raise RuntimeError(f"Login failed with status {response.status_code}")
# 供conftest.py调用的便捷函数
def get_auth_headers(token: str) -> dict:
return {"Authorization": f"Bearer {token}"}
为什么不用requests.Session管理Cookie?
- requests.Session确实能自动处理Cookie,但它无法同时管理Token Header和Cookie两种认证方式。真实系统中,登录接口可能返回JWT(放Header),而后续某些老接口仍依赖Session ID(放Cookie)。aaa_login.py返回的字典明确分离了token(用于Header)和session_id(用于Cookie),让测试用例可以按需组合:
python # test_user_info.py中 auth_data = login_as_user("test", "123456") headers = get_auth_headers(auth_data["token"]) # 只用token cookies = {"sessionid": auth_data["session_id"]} # 只用cookie # 或两者都用(混合认证场景)
- 更重要的是,
requests.Session的生命周期难以与pytest fixture作用域对齐。session级别fixture需要在整个测试session中复用登录态,但requests.Session实例一旦创建就固定了底层TCP连接池,无法优雅地处理token过期后的自动刷新。而aaa_login.py返回的是原始数据,刷新逻辑由conftest.py里的fixture统一控制(见3.3节)。
注意事项:
timeout=10是硬性要求。没有超时设置的HTTP请求,在网络抖动时会让整个测试套件卡死。我见过最惨的一次是某次DNS故障,requests.get()阻塞了127秒,导致CI流水线超时失败——从此所有HTTP调用都加timeout。
3.2 测试用例组织(test_login.py):如何设计高覆盖度的登录场景?
test_login.py不是简单地测“能登进去”,而是构建一个登录状态机。内容精简如下:
import pytest
from aaa_login import login_as_user, get_auth_headers
class TestLoginFlow:
def test_normal_login_success(self):
"""正常用户名密码,返回200及有效token"""
result = login_as_user("valid_user", "valid_pass")
assert result["token"] is not None
assert len(result["token"]) > 10 # JWT长度通常>10字符
def test_invalid_password(self):
"""密码错误,返回401"""
with pytest.raises(ValueError, match="invalid credentials"):
login_as_user("valid_user", "wrong_pass")
@pytest.mark.smoke
def test_login_then_access_protected_api(self):
"""登录成功后,用token访问受保护接口"""
auth_data = login_as_user("valid_user", "valid_pass")
headers = get_auth_headers(auth_data["token"])
# 访问需要认证的接口
response = requests.get(
"https://api.example.com/api/v1/user/me",
headers=headers,
timeout=10
)
assert response.status_code == 200
assert "name" in response.json().get("data", {})
关键设计逻辑:
- 异常流全覆盖:不仅测成功(200),还用pytest.raises()捕获预期异常。match="invalid credentials"确保抛出的是我们定义的ValueError,而不是底层requests的ConnectionError,避免误判。
- @pytest.mark.smoke标记:这个用例是冒烟测试的核心——它验证了“登录→拿token→调用受保护接口”整条链路。在CI中,你可以先跑所有smoke标记用例,5分钟内确认主干功能可用,再跑耗时的regression用例。
- 不测UI,只测协议:test_login.py里没有Selenium代码,因为它专注验证API层的登录契约。UI层的验证码输入、按钮点击,应该由E2E测试覆盖,接口测试只关心HTTP请求/响应是否符合文档。
实操心得:
test_login.py里所有测试方法都以test_开头,且类名以Test开头(TestLoginFlow),这是pytest.ini里python_classes = Test*规则生效的前提。如果写成class LoginTest:,pytest会忽略整个类。
3.3 全局上下文(conftest.py):session级fixture如何实现“一次登录,全程复用”
conftest.py是pytest的魔法中心。这个包里的版本如下:
import pytest
import requests
from aaa_login import login_as_user, get_auth_headers
@pytest.fixture(scope="session")
def login_session():
"""
session级别fixture:整个测试session只执行一次登录
返回包含headers和cookies的字典,供所有测试用例复用
"""
print("\n【全局】执行一次登录获取token...")
auth_data = login_as_user("test_admin", "admin123")
# 构造通用请求头和Cookie
headers = get_auth_headers(auth_data["token"])
cookies = {"sessionid": auth_data["session_id"]}
yield {
"headers": headers,
"cookies": cookies,
"user_id": auth_data["user_id"]
}
print("【全局】测试session结束,清理资源(如登出)...")
# 注册命令行参数(供all.py调用)
def pytest_addoption(parser):
parser.addoption(
"--env",
action="store",
default="staging",
help="运行环境:staging or production"
)
@pytest.fixture(scope="session")
def env(request):
return request.config.getoption("--env")
为什么scope="session"?
- function(默认):每个测试函数执行前创建,执行后销毁 → 登录100次,浪费时间且可能触发风控。
- class:每个测试类执行前创建 → 如果TestUserAPI和TestOrderAPI在不同类里,仍会登录2次。
- session:整个pytest命令执行期间只创建1次 → test_login.py和interface_testcase/test_user_info.py共享同一个登录态,真实模拟用户行为。
yield的妙用:
- yield之前的代码在测试开始前执行(登录);
- yield之后的代码在测试全部结束后执行(可用于登出、清理测试数据);
- yield返回的字典被注入到所有标记了def test_xxx(login_session):的测试函数中。
在test_user_info.py里这样用:
def test_get_user_profile(login_session):
"""使用session级登录态访问用户信息"""
response = requests.get(
"https://api.example.com/api/v1/user/me",
headers=login_session["headers"],
cookies=login_session["cookies"],
timeout=10
)
assert response.status_code == 200
assert response.json()["data"]["user_id"] == login_session["user_id"]
注意:
login_sessionfixture返回的是字典,不是requests.Session对象。这样设计是为了解耦HTTP客户端——未来如果换成httpx或aiohttp,只需改aaa_login.py,所有测试用例不变。
3.4 接口测试用例(interface_testcase/test_user_info.py):如何做健壮的响应断言?
真实接口测试最怕什么?不是500错误,而是字段缺失、类型错乱、空值未处理。test_user_info.py示范了工业级断言:
import pytest
import requests
import json
def test_user_info_response_structure(login_session):
"""验证响应体结构符合OpenAPI规范"""
response = requests.get(
"https://api.example.com/api/v1/user/me",
headers=login_session["headers"],
cookies=login_session["cookies"],
timeout=10
)
# 1. 状态码断言(必须)
assert response.status_code == 200, f"Expected 200, got {response.status_code}"
# 2. 响应体JSON解析(防御性编程)
try:
data = response.json()
except json.JSONDecodeError:
pytest.fail(f"Response is not valid JSON: {response.text}")
# 3. 关键字段存在性断言(避免KeyError)
assert "code" in data, "Missing 'code' field in response"
assert "message" in data, "Missing 'message' field in response"
assert "data" in data, "Missing 'data' field in response"
# 4. data字段结构断言(深度校验)
data_part = data["data"]
assert isinstance(data_part, dict), "'data' should be a dict"
assert "user_id" in data_part, "'user_id' missing in data"
assert "name" in data_part, "'name' missing in data"
assert isinstance(data_part["name"], str), "'name' should be string"
# 5. 业务逻辑断言(非空、长度限制)
assert data_part["name"].strip(), "User name cannot be empty or whitespace"
assert len(data_part["name"]) <= 50, "User name exceeds 50 chars"
为什么这样写?
- assert response.status_code == 200, f"Expected 200...":带上自定义错误消息,失败时直接看到期望值和实际值,不用再翻日志。
- try/except json.JSONDecodeError:有些接口在错误时返回HTML(如Nginx 502页面),直接response.json()会抛JSONDecodeError,用pytest.fail()主动捕获并给出清晰提示。
- assert "field" in dict:比dict["field"]安全,避免KeyError中断整个测试套件。
- isinstance(..., str):防止前端传回null或数字,导致后续字符串操作崩溃。
实操心得:所有接口测试用例都应遵循“状态码→结构→字段→业务”四层断言。我曾在一个支付接口测试中,只断言了
status_code==200,结果上线后发现返回的amount字段是字符串"100.00"而非数字100.00,导致下游财务系统解析失败——从此所有数值字段都加isinstance(..., (int, float))校验。
4. 实操过程:从零运行到生成报告的完整链路
4.1 环境准备:三步完成本地执行
Step 1:创建隔离环境(推荐)
不要用系统Python!用pyvenv.cfg里的配置创建虚拟环境:
# 进入项目根目录
cd /path/to/your/project
# 创建虚拟环境(Python 3.8+)
python -m venv venv
# 激活环境(Mac/Linux)
source venv/bin/activate
# 激活环境(Windows)
venv\Scripts\activate.bat
# 安装依赖(requirements.txt已锁定版本)
pip install -r requirements.txt
requirements.txt内容精简如下:
requests==2.31.0
pytest==7.4.3
pytest-html==4.1.1
pytest-rerunfailures==12.0
为什么版本锁死?
- requests==2.31.0:避免requests>=2.28.0引入的urllib3兼容问题(曾导致HTTPS证书验证失败)。
- pytest==7.4.3:与pytest-html==4.1.1兼容(新版pytest-html要求pytest>=7.4.0)。
- pytest-rerunfailures==12.0:支持--reruns 3 --reruns-delay 2语法(旧版用法不同)。
提示:
pyvenv.cfg文件里include-system-site-packages = false确保环境纯净,不会意外调用系统全局包。
Step 2:配置环境变量(可选但推荐)
conftest.py里读取--env参数,你可以在运行时指定:
# 运行staging环境
pytest --env=staging -m smoke
# 运行production环境(谨慎!)
pytest --env=production -m smoke
Step 3:执行测试
- 单点调试:在PyCharm里右键test_login.py → “Run ‘pytest in test_login.py’”,实时看输出。
- 批量运行:终端执行python all.py(它内部调用pytest.main(["-m", "smoke"]))。
- 生成报告:执行后自动在根目录生成report.html,用浏览器打开即可查看。
all.py内容(供参考):
import pytest
import sys
if __name__ == "__main__":
# 默认运行smoke标记用例
args = ["-m", "smoke", "--html=report.html", "--self-contained-html"]
# 如果传入参数,则覆盖默认
if len(sys.argv) > 1:
args = sys.argv[1:]
pytest.main(args)
4.2 报告解读:report.html里藏着哪些关键信息?
生成的report.html不是简单罗列通过/失败,而是可追溯的交付证据。重点看三个区域:
| 区域 | 内容 | 实用价值 |
|---|---|---|
| Summary | 总用例数、通过率、耗时、失败数 | 快速判断本次执行质量(如通过率<95%需立即介入) |
| Tests | 每个用例的状态(PASSED/FAILED/SKIPPED)、执行时间、错误堆栈 | 点击FAILED用例,直接看到AssertionError详情和响应体快照 |
| Environment | Python版本、pytest版本、平台信息、--env参数值 | 确保测试环境与生产环境一致(如Python 3.9 vs 3.11可能导致类型提示差异) |
特别注意:
- 失败用例的“Traceback”区域会显示完整的HTTP请求信息(URL、Headers、Body)和响应信息(Status Code、Headers、Body),无需额外日志。
- “Logs”标签页显示print()语句输出(如conftest.py里的print("【全局】执行一次登录...")),帮助定位fixture执行时机。
实操心得:把
report.html发给开发时,附上一句:“请重点看test_user_info.py::test_user_info_response_structure的FAILURE,响应体缺少‘avatar_url’字段,与OpenAPI文档v2.3不符”。——用报告代替口头沟通,减少扯皮。
4.3 失败重试(pytest-rerunfailures):如何科学地应对偶发失败?
网络抖动、数据库锁表、第三方服务延迟,都会导致偶发失败。pytest-rerunfailures插件帮你自动重试:
在pytest.ini里已启用:
plugins = pytest_rerunfailures
运行时加参数:
# 失败时重试3次,每次间隔2秒
pytest --reruns 3 --reruns-delay 2
# 或在all.py里固定配置
pytest.main(["--reruns", "3", "--reruns-delay", "2"])
它如何工作?
- 第一次执行test_xxx失败 → 记录为RERUN;
- 等待2秒 → 重新执行同一用例;
- 如果3次都失败 → 最终标记为FAILED,并在报告中显示3次失败的详细日志;
- 如果任意一次成功 → 标记为PASSED,不计入失败统计。
注意事项:重试只对非断言失败有效。如果是代码逻辑错误(如
NameError),重试100次也是失败。因此,重试应仅用于网络/IO类不稳定场景,不能掩盖真正的bug。
5. 常见问题与排查技巧实录:那些没人告诉你的坑
5.1 问题速查表:高频故障与解决方案
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
ModuleNotFoundError: No module named 'aaa_login' | Python路径未识别根目录 | PyCharm:右键根目录 → “Mark Directory as” → “Sources Root”;命令行:确保在根目录执行python -m pytest |
pytest: error: unrecognized arguments: --html=report.html | pytest-html未安装或版本不匹配 | pip uninstall pytest-html && pip install pytest-html==4.1.1(匹配pytest==7.4.3) |
test_login.py里login_as_user()调用成功,但test_user_info.py里用同一个token失败 | token过期或环境不一致 | 检查conftest.py中login_session fixture的scope是否为session;确认BASE_URL在aaa_login.py中指向正确环境(staging vs prod) |
report.html打开空白或样式错乱 | --self-contained-html参数未生效 | 确认pytest.ini里addopts包含--self-contained-html;或手动执行pytest --html=report.html --self-contained-html |
pytest命令找不到test_*.py文件 | pytest.ini中testpaths配置错误 | 检查testpaths = testcase interface_testcase是否拼写正确;确认目录名与配置完全一致(大小写敏感) |
5.2 独家避坑技巧:来自真实战场的经验
技巧1:用--capture=no实时看print输出
默认pytest会捕获print()输出,失败时才显示。调试fixture时,加-s参数:
pytest -s testcase/test_login.py::TestLoginFlow::test_normal_login_success
你会看到conftest.py里print("【全局】执行一次登录...")实时输出,确认fixture是否被调用。
技巧2:临时禁用fixture,隔离问题
当怀疑login_session fixture有问题时,在测试函数上加@pytest.mark.usefixtures("login_session")无效,正确做法是:
def test_debug_without_fixture():
# 不依赖fixture,手动调用登录
auth_data = login_as_user("test", "123")
print("Manual login result:", auth_data)
这样能快速区分是登录逻辑问题,还是fixture作用域问题。
技巧3:--tb=short不够用?切--tb=line看单行摘要
当测试套件很大时,--tb=short仍显冗长。用--tb=line只显示失败行:
pytest --tb=line -m smoke
输出类似:test_user_info.py:42: AssertionError: Expected 200, got 401,一秒定位。
技巧4:.gitignore里必须屏蔽report/和.pytest_cache/
否则report.html会被提交,下次别人拉代码时看到的是你上周的报告;.pytest_cache/包含机器相关路径,提交会导致他人执行失败。检查.gitignore是否包含:
report/
.pytest_cache/
venv/
技巧5:all.py里加环境检测,防误操作
在all.py顶部加:
import os
if os.getenv("ENV") == "production":
confirm = input("⚠️ 即将运行PRODUCTION环境测试!确认继续?(y/N): ")
if confirm.lower() != "y":
print("已取消执行")
exit(0)
避免手抖在生产环境跑测试。
6. 后续扩展建议:这个骨架还能长成什么样?
这个包不是终点,而是起点。根据你团队的实际需求,可以自然延伸:
- 接入CI/CD:把
all.py改成ci_run.py,在GitHub Actions里添加步骤:
```yaml - name: Run pytest smoke tests
run: python ci_run.py –env=staging -
name: Upload HTML report
uses: actions/upload-artifact@v3
with:
name: pytest-report
path: report.html
``` -
增加数据驱动:用
pytest.mark.parametrize替换硬编码账号:
python @pytest.mark.parametrize("username,password,expected_status", [ ("admin", "123", 200), ("guest", "456", 401), ]) def test_login_with_params(username, password, expected_status): # 复用aaa_login.py逻辑 -
集成Allure报告:替换
pytest-html为allure-pytest,生成交互式报告,支持步骤截图、附件上传。 -
Mock外部依赖:当测试依赖第三方支付接口时,用
pytest-mock或responses库拦截HTTP请求,返回预设响应,避免调用真实服务。
最后分享一个小技巧:每次新增一个接口测试用例,先写test_xxx.py文件名,再写README.md里的“新增用例:xxx接口验证”,最后才写代码。这样倒逼自己思考“这个用例要验证什么业务价值”,而不是陷入技术细节。
这个包里的每一行代码,都经历过至少一次线上故障的检验。它不炫技,不堆砌,只解决一件事:让你今天下午三点前,跑通第一个真实的登录+接口测试。剩下的,交给时间和你的迭代。
简介:直接导入PyCharm就能跑的pytest测试工程,内置登录流程(用户名密码校验、token获取、会话保持)和典型HTTP接口测试用例,覆盖GET/POST请求、状态码断言、响应字段提取与校验。项目结构清晰,包含独立的testcase目录存放用例脚本,interface_testcase专用于接口层测试,aaa_login.py封装通用登录逻辑供复用;通过main.py单点执行或all.py批量运行,自动生成HTML格式测试报告(report.html),支持失败重试、用例标签分类(如@pytest.mark.smoke)。已配置标准pytest.ini(含默认参数、插件启用、路径映射),集成pytest-html、requests等依赖(见requirements.txt),附带初始化文件(init.py)确保模块识别,.gitignore屏蔽缓存与IDE文件,.pytest_cache目录预置兼容本地执行。两份笔记文件(第一节笔记.py、第二节笔记.py)逐行注释关键步骤,说明fixture使用、conftest.py作用域、session级登录前置等核心实践,README.md提供环境准备与运行指引,适合刚接触接口自动化的新手快速验证真实业务场景。


被折叠的 条评论
为什么被折叠?



