本文还有配套的精品资源,点击获取
简介:直接导入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_sessionfixture。如果把它放进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.txtrequirements.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 smokeStep 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_sessionfixture的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_sessionfixture有问题时,在测试函数上加@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提供环境准备与运行指引,适合刚接触接口自动化的新手快速验证真实业务场景。
本文还有配套的精品资源,点击获取