
测试框架的功能:
命名规则:
test_ 开头的文件Test 开头的类test_ 开头的函数或方法pytest 以函数/方法作为一个用例,且命名规则必须符合上述规则
命名不符合规则,则为普通函数/方法/类
启动方式:终端输入pytest,会自动执行用例,并输出用例的收集、执行、汇总信息
执行用例终端输出的用例执行情况:
如:
test_first.py .E
test_first.py ..
缩写字母表示对应情况

pytest 直接使用 Python 原生的 assert 语句进行断言,无需记忆复杂的断言方法(如 unittest 的 self.assertEqual),更简洁灵活。
# 1. 基础断言
def test_basic_assert():
assert 10 == 10 # 相等
assert 20 != 30 # 不等
assert 5 > 3 # 大于
assert 2 <= 2 # 小于等于
assert "hello" in "hello world" # 包含
assert "x" not in [1, 2, 3] # 不包含
# 2. 异常断言
def test_exception_assert():
# 断言:字符串转整数时,非数字字符串会抛ValueError
with pytest.raises(ValueError):
int("not_a_number")
# 进阶:捕获异常对象,验证异常信息
with pytest.raises(ValueError) as exc_info:
int("invalid")
assert "invalid literal for int()" in str(exc_info.value) # 验证异常描述
# 3. 布尔值断言
def test_boolean_assert():
assert True # 断言条件为真
assert not False # 断言条件为假
assert len([1, 2, 3]) > 0 # 间接断言(列表非空)
# 4. 近似值断言
def test_approx_assert():
# 直接比较可能失败(因浮点数精度)
# assert 0.1 + 0.2 == 0.3 # 实际结果是0.30000000000000004,会失败
# 用approx()允许微小误差
assert 0.1 + 0.2 == pytest.approx(0.3) # 成功
# 5. 集合/列表断言
def test_collection_assert():
# 断言列表包含指定元素(不考虑顺序)
assert [1, 2, 3] == pytest.assertion.rewrite.util.same_elements([3, 2, 1])
# 断言集合相等
assert {1, 2, 3} == {3, 2, 1}
用于在测试前准备资源(如初始化数据)、测试后清理资源(如关闭连接),pytest 支持函数级和类级的前后置:
def setup_function():
print("\n测试函数开始前:准备数据")
def teardown_function():
print("测试函数结束后:清理数据")
def test_case1():
print("执行测试用例1")
def test_case2():
print("执行测试用例2")
运行结果(加 -s 查看 print):
测试函数开始前:准备数据
执行测试用例1
测试函数结束后:清理数据
测试函数开始前:准备数据
执行测试用例2
测试函数结束后:清理数据
class TestDB:
@classmethod
def setup_class(cls):
print("\n测试类开始前:连接数据库")
@classmethod
def teardown_class(cls):
print("测试类结束后:关闭数据库")
def test_query(self):
print("执行查询测试")
def test_insert(self):
print("执行插入测试")
运行结果:
测试类开始前:连接数据库
执行查询测试
执行插入测试
测试类结束后:关闭数据库
这里的
@classmethod是一个装饰器,等同于类的静态方法,第一个参数固定为cls,代表类本身,通过cls,可以访问类的属性和其他类方法,常用于定义与类相关的工具方法
Fixture 用于定义测试前的准备(如初始化资源)和测试后的清理(如释放资源),替代传统的 setup/teardown,支持跨用例、跨模块共享,是自动化测试中管理公共逻辑的核心工具。
核心特性:
作用域(scope):控制 fixture 的执行次数,可选值:
function(默认,每个用例执行一次)class(每个类执行一次,类内测试共享)module(每个测试文件执行一次,文件内测试共享)session(整个测试会话调用一次,全局共享)。依赖传递:fixture 可以依赖其他 fixture,形成依赖链。
自动使用(autouse):设置 autouse=True 时,作用域内的所有用例会自动调用该 fixture,无需显式声明。
参数化(params):通过 params 为 fixture 传入多组参数,配合 request 对象获取参数,实现多场景复用。
通过 @pytest.mark.parametrize 为测试用例传入多组数据,实现 “一套逻辑,多组数据” 的测试,尤其适合接口测试、表单验证等场景。
进阶用法:
method 和 data 组合)。yaml/json 等文件,实现测试数据与代码分离。通过 @pytest.mark.xxx 为用例打标记(如 smoke 冒烟用例、regression 回归用例、prod 生产环境用例),运行时通过 -m 筛选指定标记的用例,灵活控制执行范围。
通过 mark,可以实现对测试用例的分类、筛选、条件执行等功能,灵活控制测试范围(例如只运行 “冒烟测试” 用例,或跳过某些暂时不执行的用例)。
自定义标记需在项目根目录的 pytest.ini 中注册(否则运行时会警告 “未知标记”):
[pytest]
markers =
smoke: 冒烟测试用例(核心功能)
regression: 回归测试用例
payment: 支付模块用例
high: 高优先级
low: 低优先级
pytest 的强大很大程度依赖于插件,自动化测试中常用插件:
pytest-html:生成美观的 HTML 测试报告(含用例详情、失败截图等)。pytest-xdist:多进程并行执行用例,大幅缩短执行时间(尤其适合用例量大的场景)。pytest-rerunfailures:失败用例自动重试(解决网络波动、偶发超时等问题)。pytest-ordering:控制用例执行顺序(虽然不推荐强依赖,但特殊场景下有用,如流程性测试)。场景:测试一个用户登录接口(POST /api/login),需要覆盖 “正确账号密码”“账号不存在”“密码错误” 等场景,且后续接口需依赖登录返回的 token。
实现步骤:
token 给后续依赖接口。import pytest
import requests
import json
# 读取测试数据(从外部json文件)
with open("login_data.json", "r") as f:
login_cases = json.load(f)
# 格式:[{"case": "正确账号", "data": {...}, "expected": {...}}, ...]
# Fixture:全局会话(复用请求连接)
@pytest.fixture(scope="session")
def session():
s = requests.Session()
yield s
s.close() # 测试结束后关闭会话
# Fixture:基础URL(从配置文件读取,这里简化)
@pytest.fixture(scope="session")
def base_url():
return "https://api-test.example.com"
# Fixture:登录并返回token(依赖session和base_url)
@pytest.fixture(scope="module")
def login_token(session, base_url):
# 前置:执行登录(用正确账号,为后续接口准备token)
login_data = {"username": "test_user", "password": "test_pass"}
res = session.post(f"{base_url}/api/login", json=login_data)
assert res.status_code == 200
token = res.json()["data"]["token"]
yield token
# 后置:登出(清理状态)
session.post(f"{base_url}/api/logout", headers={"Authorization": f"Bearer {token}"})
# 测试登录接口(参数化多场景)
@pytest.mark.parametrize("case", login_cases)
def test_login(session, base_url, case):
res = session.post(
f"{base_url}/api/login",
json=case["data"]
)
# 断言状态码和响应信息
assert res.status_code == case["expected"]["status_code"]
assert res.json()["message"] == case["expected"]["message"]
# 测试依赖token的接口(如获取用户信息)
def test_get_user_info(session, base_url, login_token):
res = session.get(
f"{base_url}/api/user/info",
headers={"Authorization": f"Bearer {login_token}"}
)
assert res.status_code == 200
assert res.json()["data"]["username"] == "test_user"
核心亮点:
session fixture 复用 HTTP 连接,减少开销;login_token fixture 前置登录、后置登出,确保测试环境干净;场景:用 Selenium 测试某网站的搜索功能,需要支持失败重试、并行执行,并生成带截图的报告。
实现步骤:
pytest-rerunfailures 处理偶发失败(如元素加载慢)。pytest-xdist 并行执行多浏览器用例。import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Fixture:浏览器驱动(支持多浏览器参数化)
@pytest.fixture(scope="function", params=["chrome", "firefox"])
def driver(request):
browser = request.param
if browser == "chrome":
driver = webdriver.Chrome()
else:
driver = webdriver.Firefox()
driver.maximize_window()
yield driver
driver.quit() # 每个用例结束后关闭浏览器
# 失败时自动截图(结合pytest-html)
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item):
outcome = yield
report = outcome.get_result()
if report.when == "call" and report.failed:
# 获取driver实例
driver = item.funcargs.get("driver")
if driver:
# 截图并保存到报告
screenshot = driver.get_screenshot_as_base64()
report.extra = [pytest_html.extras.image(screenshot, "失败截图")]
# 测试搜索功能
@pytest.mark.smoke # 标记为冒烟用例
def test_search(driver):
driver.get("https://example.com")
# 等待搜索框加载
search_box = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "search-input"))
)
search_box.send_keys("pytest")
driver.find_element(By.ID, "search-btn").click()
# 断言结果
assert "pytest" in driver.title