pytest

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

pytest

pytest

Complete Python testing toolkit with pytest.
基于pytest的完整Python测试工具包。

Running Tests

运行测试

bash
undefined
bash
undefined

Run all tests

Run all tests

pytest
pytest

Run specific file/directory

Run specific file/directory

pytest tests/test_api.py pytest tests/
pytest tests/test_api.py pytest tests/

Run specific test

Run specific test

pytest tests/test_api.py::test_login pytest tests/test_api.py::TestUserClass::test_create
pytest tests/test_api.py::test_login pytest tests/test_api.py::TestUserClass::test_create

Verbose output

Verbose output

pytest -v pytest -vv # Extra verbose
pytest -v pytest -vv # Extra verbose

Stop on first failure

Stop on first failure

pytest -x
pytest -x

Run last failed

Run last failed

pytest --lf
pytest --lf

Run failed first, then rest

Run failed first, then rest

pytest --ff
pytest --ff

Show print output

Show print output

pytest -s
pytest -s

Parallel (requires pytest-xdist)

Parallel (requires pytest-xdist)

pytest -n auto pytest -n 4
undefined
pytest -n auto pytest -n 4
undefined

Fixtures

Fixtures(测试夹具)

python
import pytest
python
import pytest

Basic fixture

Basic fixture

@pytest.fixture def user(): return {"name": "Alice", "email": "alice@test.com"}
@pytest.fixture def user(): return {"name": "Alice", "email": "alice@test.com"}

Fixture with teardown

Fixture with teardown

@pytest.fixture def db_connection(): conn = create_connection() yield conn conn.close()
@pytest.fixture def db_connection(): conn = create_connection() yield conn conn.close()

Autouse fixture (runs for every test)

Autouse fixture (runs for every test)

@pytest.fixture(autouse=True) def reset_state(): State.reset() yield State.cleanup()
@pytest.fixture(autouse=True) def reset_state(): State.reset() yield State.cleanup()

Scoped fixtures

Scoped fixtures

@pytest.fixture(scope="module") def expensive_resource(): return load_heavy_thing()
@pytest.fixture(scope="module") def expensive_resource(): return load_heavy_thing()

scope options: "function" (default), "class", "module", "package", "session"

scope options: "function" (default), "class", "module", "package", "session"

Fixture with params

Fixture with params

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

Using fixtures

Using fixtures

def test_user_name(user): assert user["name"] == "Alice"
undefined
def test_user_name(user): assert user["name"] == "Alice"
undefined

Parametrize

参数化(Parametrize)

python
undefined
python
undefined

Basic parametrize

Basic parametrize

@pytest.mark.parametrize("input,expected", [ ("hello", 5), ("", 0), ("world", 5), ]) def test_string_length(input, expected): assert len(input) == expected
@pytest.mark.parametrize("input,expected", [ ("hello", 5), ("", 0), ("world", 5), ]) def test_string_length(input, expected): assert len(input) == expected

Multiple parameters

Multiple parameters

@pytest.mark.parametrize("x", [0, 1]) @pytest.mark.parametrize("y", [2, 3]) def test_combinations(x, y): pass # Runs 4 times: (0,2), (0,3), (1,2), (1,3)
@pytest.mark.parametrize("x", [0, 1]) @pytest.mark.parametrize("y", [2, 3]) def test_combinations(x, y): pass # Runs 4 times: (0,2), (0,3), (1,2), (1,3)

With IDs

With IDs

@pytest.mark.parametrize("input,expected", [ pytest.param("admin", True, id="admin-user"), pytest.param("guest", False, id="guest-user"), ]) def test_is_admin(input, expected): assert is_admin(input) == expected
undefined
@pytest.mark.parametrize("input,expected", [ pytest.param("admin", True, id="admin-user"), pytest.param("guest", False, id="guest-user"), ]) def test_is_admin(input, expected): assert is_admin(input) == expected
undefined

Mocking

模拟(Mocking)

python
from unittest.mock import Mock, patch, MagicMock
python
from unittest.mock import Mock, patch, MagicMock

patch decorator

patch decorator

@patch("myapp.services.send_email") def test_registration(mock_send): register_user("alice@test.com") mock_send.assert_called_once_with("alice@test.com", subject="Welcome")
@patch("myapp.services.send_email") def test_registration(mock_send): register_user("alice@test.com") mock_send.assert_called_once_with("alice@test.com", subject="Welcome")

patch as context manager

patch as context manager

def test_api_call(): with patch("myapp.client.requests.get") as mock_get: mock_get.return_value.json.return_value = {"status": "ok"} result = fetch_status() assert result == "ok"
def test_api_call(): with patch("myapp.client.requests.get") as mock_get: mock_get.return_value.json.return_value = {"status": "ok"} result = fetch_status() assert result == "ok"

Mock fixture (pytest-mock)

Mock fixture (pytest-mock)

def test_with_mocker(mocker): mock_db = mocker.patch("myapp.db.query") mock_db.return_value = [{"id": 1}] result = get_users() assert len(result) == 1
def test_with_mocker(mocker): mock_db = mocker.patch("myapp.db.query") mock_db.return_value = [{"id": 1}] result = get_users() assert len(result) == 1

Side effects

Side effects

mock_func = Mock(side_effect=ValueError("boom")) mock_func = Mock(side_effect=[1, 2, 3]) # Returns sequentially mock_func = Mock(side_effect=lambda x: x * 2)
mock_func = Mock(side_effect=ValueError("boom")) mock_func = Mock(side_effect=[1, 2, 3]) # Returns sequentially mock_func = Mock(side_effect=lambda x: x * 2)

Spec mocking (catches attribute errors)

Spec mocking (catches attribute errors)

mock_obj = Mock(spec=MyClass)
undefined
mock_obj = Mock(spec=MyClass)
undefined

Markers

标记(Markers)

python
undefined
python
undefined

Skip

Skip

@pytest.mark.skip(reason="Not implemented yet") def test_future_feature(): pass
@pytest.mark.skip(reason="Not implemented yet") def test_future_feature(): pass

Skip conditionally

Skip conditionally

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

Expected failure

Expected failure

@pytest.mark.xfail(reason="Known bug #123") def test_known_bug(): pass
@pytest.mark.xfail(reason="Known bug #123") def test_known_bug(): pass

Custom markers

Custom markers

@pytest.mark.slow def test_heavy_computation(): pass
@pytest.mark.slow def test_heavy_computation(): pass

Run by marker: pytest -m slow

Run by marker: pytest -m slow

Run excluding: pytest -m "not slow"

Run excluding: pytest -m "not slow"

Register markers in pytest.ini or pyproject.toml

Register markers in pytest.ini or pyproject.toml

[tool.pytest.ini_options]

[tool.pytest.ini_options]

markers = ["slow: marks tests as slow"]

markers = ["slow: marks tests as slow"]

undefined
undefined

Assertions

断言(Assertions)

python
undefined
python
undefined

Basic

Basic

assert result == expected assert item in collection assert value is None
assert result == expected assert item in collection assert value is None

Exception testing

Exception testing

with pytest.raises(ValueError): int("not_a_number")
with pytest.raises(ValueError, match="invalid literal"): int("not_a_number")
with pytest.raises(ValueError): int("not_a_number")
with pytest.raises(ValueError, match="invalid literal"): int("not_a_number")

Approximate

Approximate

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

Warnings

Warnings

with pytest.warns(DeprecationWarning): deprecated_function()
undefined
with pytest.warns(DeprecationWarning): deprecated_function()
undefined

Coverage

覆盖率(Coverage)

bash
undefined
bash
undefined

Install

Install

pip install pytest-cov
pip install pytest-cov

Run with coverage

Run with coverage

pytest --cov=myapp pytest --cov=myapp --cov-report=html pytest --cov=myapp --cov-report=term-missing pytest --cov=myapp --cov-branch # Branch coverage
pytest --cov=myapp pytest --cov=myapp --cov-report=html pytest --cov=myapp --cov-report=term-missing pytest --cov=myapp --cov-branch # Branch coverage

Minimum threshold

Minimum threshold

pytest --cov=myapp --cov-fail-under=80
undefined
pytest --cov=myapp --cov-fail-under=80
undefined

conftest.py

conftest.py配置

python
undefined
python
undefined

tests/conftest.py - Shared fixtures available to all tests

tests/conftest.py - Shared fixtures available to all tests

import pytest
@pytest.fixture def app(): """Create test application.""" app = create_app(testing=True) yield app
@pytest.fixture def client(app): """Create test client.""" return app.test_client()
@pytest.fixture def auth_headers(): """Create auth headers.""" token = create_test_token() return {"Authorization": f"Bearer {token}"}
undefined
import pytest
@pytest.fixture def app(): """Create test application.""" app = create_app(testing=True) yield app
@pytest.fixture def client(app): """Create test client.""" return app.test_client()
@pytest.fixture def auth_headers(): """Create auth headers.""" token = create_test_token() return {"Authorization": f"Bearer {token}"}
undefined

pyproject.toml Config

pyproject.toml配置

toml
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = "-v --tb=short --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 --tb=short --strict-markers"
markers = [
    "slow: marks tests as slow",
    "integration: integration tests",
]
filterwarnings = [
    "ignore::DeprecationWarning",
]

Reference

参考资料

For advanced patterns, plugins, and async testing:
references/patterns.md
如需了解高级模式、插件和异步测试,请查看:
references/patterns.md