基础用法
# test_math.py
def test_addition():
assert 1 + 1 == 2
def test_division():
assert 10 / 2 == 5
def test_raises():
import pytest
with pytest.raises(ZeroDivisionError):
1 / 0
def test_approx():
assert 3.14 == pytest.approx(3.14159, rel=1e-2) # 近似比较
pytest # 运行所有测试
pytest test_math.py # 指定文件
pytest -k "addition" # 按名称过滤
pytest -v # 详细输出
pytest -x # 首次失败即停止
pytest --lf # 只运行上次失败的
pytest --maxfail=3 # 3 个失败后停止
fixture — 测试夹具
import pytest
@pytest.fixture
def db():
"""创建测试数据库"""
conn = create_test_db()
yield conn # 测试用例拿到 conn
conn.close() # 测试后清理
def test_insert(db):
db.execute("INSERT INTO t VALUES (1)")
assert db.fetchone() == (1,)
# scope 控制生命周期
@pytest.fixture(scope="module") # 整个模块共享
def expensive_resource(): ...
@pytest.fixture(scope="session") # 整个测试会话共享
def global_config(): ...
# 自动使用(不需传参)
@pytest.fixture(autouse=True)
def reset_counter():
counter.reset()
参数化
@pytest.mark.parametrize("hex_str,expected", [
("00", 0),
("0A", 10),
("FF", 255),
("A5", 165),
])
def test_hex_to_int(hex_str, expected):
assert hex_to_int(hex_str) == expected
# 组合参数化
@pytest.mark.parametrize("a", [1, 2])
@pytest.mark.parametrize("b", [10, 20])
def test_combine(a, b):
... # 生成 4 个用例: (1,10)(1,20)(2,10)(2,20)
# 从 JSON 加载测试数据
import json
def load_cases():
with open("testdata/cases.json") as f:
return json.load(f)
@pytest.mark.parametrize("case", load_cases())
def test_from_json(case):
assert process(case["input"]) == case["expected"]
mock
from unittest.mock import Mock, patch, MagicMock
# 替换函数
@patch("mymodule.requests.get")
def test_fetch(mock_get):
mock_get.return_value.status_code = 200
mock_get.return_value.json.return_value = {"key": "val"}
result = fetch_data("http://api")
assert result == {"key": "val"}
mock_get.assert_called_once_with("http://api")
# 替换对象方法
def test_service():
mock_db = Mock()
mock_db.query.return_value = [1, 2, 3]
svc = Service(mock_db)
assert svc.get_ids() == [1, 2, 3]
# 验证调用
mock.assert_called_with(arg1, arg2)
mock.assert_called_once()
mock.assert_not_called()
mock.assert_any_call(arg)
conftest.py — 共享配置
# tests/conftest.py — 自动被同级及子目录测试加载
import pytest
@pytest.fixture(scope="session")
def app():
"""创建应用实例,整个测试会话共享"""
app = create_app(test_mode=True)
yield app
app.shutdown()
def pytest_configure(config):
config.addinivalue_line("markers", "slow: marks tests as slow")
# 命令行选项
def pytest_addoption(parser):
parser.addoption("--api-url", default="http://localhost")
常用插件
pip install pytest-cov # 覆盖率
pip install pytest-xdist # 并行运行(-n auto)
pip install pytest-timeout # 超时控制
pip install pytest-mock # 更简洁的 mock
pytest --cov=src --cov-report=html # 覆盖率 HTML 报告
pytest -n auto # 并行(CPU 核数个 worker)
pytest --timeout=10 # 单个用例 10 秒超时
pytest vs unittest
# unittest 风格(旧)
import unittest
class TestMath(unittest.TestCase):
def test_add(self):
self.assertEqual(1 + 1, 2)
# pytest 风格(推荐)
def test_add():
assert 1 + 1 == 2
CI 集成
# .github/workflows/test.yml
- name: Run tests
run: |
pip install pytest pytest-cov
pytest --cov=src --cov-report=xml --junitxml=report.xml
- name: Upload coverage
uses: codecov/codecov-action@v4