pytest-python

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Pytest Testing for Python

面向Python的Pytest测试

Quick Reference

快速参考

Test Discovery

测试发现

Pytest auto-discovers tests matching:
  • Files:
    test_*.py
    or
    *_test.py
  • Functions:
    test_*
    prefix
  • Classes:
    Test*
    prefix (no
    __init__
    )
Pytest会自动发现符合以下规则的测试:
  • 文件:
    test_*.py
    *_test.py
  • 函数:带
    test_
    前缀
  • 类:带
    Test*
    前缀(无
    __init__
    方法)

Running Tests

运行测试

bash
pytest                           # Run all tests
pytest test_mod.py               # Single module
pytest tests/                    # Directory
pytest -k "name"                 # By keyword
pytest -m slow                   # By marker
pytest test_mod.py::test_func    # Specific test
pytest --durations=10            # Show slowest tests
bash
pytest                           # Run all tests
pytest test_mod.py               # Single module
pytest tests/                    # Directory
pytest -k "name"                 # By keyword
pytest -m slow                   # By marker
pytest test_mod.py::test_func    # Specific test
pytest --durations=10            # Show slowest tests

Fixtures

Fixtures

Fixtures provide reusable test dependencies.
Fixtures用于提供可复用的测试依赖。

Basic Fixture

基础Fixture

python
import pytest

@pytest.fixture
def sample_data():
    return {"key": "value"}

def test_example(sample_data):
    assert sample_data["key"] == "value"
python
import pytest

@pytest.fixture
def sample_data():
    return {"key": "value"}

def test_example(sample_data):
    assert sample_data["key"] == "value"

Fixture Scopes

Fixture作用域

python
@pytest.fixture(scope="function")  # Default: per test
@pytest.fixture(scope="class")     # Per test class
@pytest.fixture(scope="module")    # Per module
@pytest.fixture(scope="session")   # Entire session
python
@pytest.fixture(scope="function")  # Default: per test
@pytest.fixture(scope="class")     # Per test class
@pytest.fixture(scope="module")    # Per module
@pytest.fixture(scope="session")   # Entire session

Fixture with Teardown (yield)

带销毁逻辑的Fixture(yield)

python
@pytest.fixture
def db_connection():
    conn = create_connection()
    yield conn
    conn.close()  # Cleanup after test
python
@pytest.fixture
def db_connection():
    conn = create_connection()
    yield conn
    conn.close()  # Cleanup after test

Factory Fixture

工厂Fixture

python
@pytest.fixture
def make_user():
    def _make_user(name, role="user"):
        return {"name": name, "role": role}
    return _make_user

def test_users(make_user):
    admin = make_user("Alice", role="admin")
    user = make_user("Bob")
python
@pytest.fixture
def make_user():
    def _make_user(name, role="user"):
        return {"name": name, "role": role}
    return _make_user

def test_users(make_user):
    admin = make_user("Alice", role="admin")
    user = make_user("Bob")

Parametrized Fixture

参数化Fixture

python
@pytest.fixture(params=["mysql", "postgres", "sqlite"])
def database(request):
    return create_db(request.param)
python
@pytest.fixture(params=["mysql", "postgres", "sqlite"])
def database(request):
    return create_db(request.param)

Parametrization

参数化

Run tests with multiple inputs.
python
@pytest.mark.parametrize("input,expected", [
    (1, 2),
    (2, 4),
    (3, 6),
])
def test_double(input, expected):
    assert input * 2 == expected
使用多组输入运行测试。
python
@pytest.mark.parametrize("input,expected", [
    (1, 2),
    (2, 4),
    (3, 6),
])
def test_double(input, expected):
    assert input * 2 == expected

Multiple Parameters (Combinations)

多参数(组合测试)

python
@pytest.mark.parametrize("x", [1, 2])
@pytest.mark.parametrize("y", [10, 20])
def test_multiply(x, y):  # Runs 4 combinations
    assert x * y > 0
python
@pytest.mark.parametrize("x", [1, 2])
@pytest.mark.parametrize("y", [10, 20])
def test_multiply(x, y):  # Runs 4 combinations
    assert x * y > 0

With Expected Failures

预期失败场景

python
@pytest.mark.parametrize("input,expected", [
    (1, 1),
    pytest.param(0, 1, marks=pytest.mark.xfail),
])
def test_factorial(input, expected):
    assert factorial(input) == expected
python
@pytest.mark.parametrize("input,expected", [
    (1, 1),
    pytest.param(0, 1, marks=pytest.mark.xfail),
])
def test_factorial(input, expected):
    assert factorial(input) == expected

Markers

标记

Built-in Markers

内置标记

python
@pytest.mark.skip(reason="Not implemented")
def test_feature(): ...

@pytest.mark.skipif(sys.platform == "win32", reason="Unix only")
def test_unix(): ...

@pytest.mark.xfail(reason="Known bug")
def test_buggy(): ...
python
@pytest.mark.skip(reason="Not implemented")
def test_feature(): ...

@pytest.mark.skipif(sys.platform == "win32", reason="Unix only")
def test_unix(): ...

@pytest.mark.xfail(reason="Known bug")
def test_buggy(): ...

Custom Markers

自定义标记

Register in
pytest.ini
or
pyproject.toml
:
ini
[pytest]
markers =
    slow: marks tests as slow
    integration: integration tests
python
@pytest.mark.slow
def test_slow_operation(): ...
Run:
pytest -m slow
or
pytest -m "not slow"
pytest.ini
pyproject.toml
中注册:
ini
[pytest]
markers =
    slow: marks tests as slow
    integration: integration tests
python
@pytest.mark.slow
def test_slow_operation(): ...
运行命令:
pytest -m slow
pytest -m "not slow"

Assertions

断言

Basic Assertions

基础断言

python
assert value == expected
assert value != other
assert value is None
assert value is not None
assert value in collection
assert isinstance(obj, MyClass)
python
assert value == expected
assert value != other
assert value is None
assert value is not None
assert value in collection
assert isinstance(obj, MyClass)

Floating Point

浮点数断言

python
assert 0.1 + 0.2 == pytest.approx(0.3)
assert result == pytest.approx(expected, rel=1e-3)
python
assert 0.1 + 0.2 == pytest.approx(0.3)
assert result == pytest.approx(expected, rel=1e-3)

Exception Testing

异常测试

python
def test_raises():
    with pytest.raises(ValueError):
        int("invalid")

def test_raises_with_match():
    with pytest.raises(ValueError, match=r"invalid.*"):
        raise ValueError("invalid input")

def test_raises_inspect():
    with pytest.raises(ValueError) as exc_info:
        raise ValueError("test error")
    assert "test" in str(exc_info.value)
python
def test_raises():
    with pytest.raises(ValueError):
        int("invalid")

def test_raises_with_match():
    with pytest.raises(ValueError, match=r"invalid.*"):
        raise ValueError("invalid input")

def test_raises_inspect():
    with pytest.raises(ValueError) as exc_info:
        raise ValueError("test error")
    assert "test" in str(exc_info.value)

Monkeypatch (Mocking)

Monkeypatch(Mocking)

Patching Functions

打补丁替换函数

python
def test_api_call(monkeypatch):
    def mock_get(*args, **kwargs):
        return {"status": "ok"}

    monkeypatch.setattr("mymodule.api.get", mock_get)
    result = mymodule.fetch_data()
    assert result["status"] == "ok"
python
def test_api_call(monkeypatch):
    def mock_get(*args, **kwargs):
        return {"status": "ok"}

    monkeypatch.setattr("mymodule.api.get", mock_get)
    result = mymodule.fetch_data()
    assert result["status"] == "ok"

Environment Variables

环境变量操作

python
def test_with_env(monkeypatch):
    monkeypatch.setenv("API_KEY", "test-key")
    assert os.environ["API_KEY"] == "test-key"

def test_without_env(monkeypatch):
    monkeypatch.delenv("API_KEY", raising=False)
python
def test_with_env(monkeypatch):
    monkeypatch.setenv("API_KEY", "test-key")
    assert os.environ["API_KEY"] == "test-key"

def test_without_env(monkeypatch):
    monkeypatch.delenv("API_KEY", raising=False)

Dictionary Values

字典值修改

python
def test_config(monkeypatch):
    monkeypatch.setitem(app.config, "DEBUG", True)
python
def test_config(monkeypatch):
    monkeypatch.setitem(app.config, "DEBUG", True)

Built-in Fixtures

内置Fixtures

FixturePurpose
tmp_path
Temporary directory (pathlib.Path)
tmp_path_factory
Session-scoped temp directories
capsys
Capture stdout/stderr
caplog
Capture log messages
monkeypatch
Dynamic patching
request
Fixture/test metadata
Fixture用途
tmp_path
临时目录(pathlib.Path对象)
tmp_path_factory
会话级别的临时目录
capsys
捕获stdout/stderr输出
caplog
捕获日志消息
monkeypatch
动态打补丁
request
Fixture/测试元数据

Examples

示例

python
def test_output(capsys):
    print("hello")
    captured = capsys.readouterr()
    assert captured.out == "hello\n"

def test_logging(caplog):
    import logging
    logging.warning("test warning")
    assert "test warning" in caplog.text

def test_temp_file(tmp_path):
    file = tmp_path / "test.txt"
    file.write_text("content")
    assert file.read_text() == "content"
python
def test_output(capsys):
    print("hello")
    captured = capsys.readouterr()
    assert captured.out == "hello\n"

def test_logging(caplog):
    import logging
    logging.warning("test warning")
    assert "test warning" in caplog.text

def test_temp_file(tmp_path):
    file = tmp_path / "test.txt"
    file.write_text("content")
    assert file.read_text() == "content"

Project Structure

项目结构

Recommended layout:
project/
├── pyproject.toml
├── src/
│   └── mypackage/
│       ├── __init__.py
│       └── module.py
└── tests/
    ├── conftest.py      # Shared fixtures
    ├── test_module.py
    └── unit/
        └── test_specific.py
推荐目录结构:
project/
├── pyproject.toml
├── src/
│   └── mypackage/
│       ├── __init__.py
│       └── module.py
└── tests/
    ├── conftest.py      # 共享fixtures
    ├── test_module.py
    └── unit/
        └── test_specific.py

conftest.py

conftest.py

Shared fixtures available to all tests in directory:
python
undefined
目录下所有测试都可以使用的共享fixture:
python
undefined

tests/conftest.py

tests/conftest.py

import pytest
@pytest.fixture def app(): return create_app(testing=True)
@pytest.fixture def client(app): return app.test_client()
undefined
import pytest
@pytest.fixture def app(): return create_app(testing=True)
@pytest.fixture def client(app): return app.test_client()
undefined

Configuration

配置

pyproject.toml

pyproject.toml配置示例

toml
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = "-v --strict-markers"
markers = [
    "slow: marks tests as slow",
    "integration: integration tests",
]
filterwarnings = [
    "ignore::DeprecationWarning",
]
toml
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = "-v --strict-markers"
markers = [
    "slow: marks tests as slow",
    "integration: integration tests",
]
filterwarnings = [
    "ignore::DeprecationWarning",
]

Best Practices

最佳实践

  1. One assertion focus per test - Test one behavior per function
  2. Descriptive names -
    test_user_creation_with_invalid_email_raises_error
  3. Use fixtures - Avoid setup duplication
  4. Isolate tests - No shared state between tests
  5. Fast unit tests - Mark slow tests with
    @pytest.mark.slow
  6. Parametrize - Use parametrize over copy-paste tests
  7. Test edge cases - Empty inputs, boundaries, errors
  1. 单测试单断言聚焦 - 每个测试函数仅测试一种行为
  2. 描述性命名 - 例如
    test_user_creation_with_invalid_email_raises_error
  3. 使用fixtures - 避免重复的配置代码
  4. 测试隔离 - 测试之间不要共享状态
  5. 快速单元测试 - 给慢速测试添加
    @pytest.mark.slow
    标记
  6. 参数化 - 使用参数化代替复制粘贴测试用例
  7. 测试边界场景 - 空输入、边界值、错误场景

References

参考资料

  • Fixtures Guide - Advanced fixture patterns
  • Patterns Guide - Common testing patterns
  • Fixtures指南 - 进阶fixture模式
  • 模式指南 - 常见测试模式