pytest
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesepytest
pytest
Complete Python testing toolkit with pytest.
基于pytest的完整Python测试工具包。
Running Tests
运行测试
bash
undefinedbash
undefinedRun 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
undefinedpytest -n auto
pytest -n 4
undefinedFixtures
Fixtures(测试夹具)
python
import pytestpython
import pytestBasic 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"
undefineddef test_user_name(user):
assert user["name"] == "Alice"
undefinedParametrize
参数化(Parametrize)
python
undefinedpython
undefinedBasic 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
undefinedMocking
模拟(Mocking)
python
from unittest.mock import Mock, patch, MagicMockpython
from unittest.mock import Mock, patch, MagicMockpatch 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)
undefinedmock_obj = Mock(spec=MyClass)
undefinedMarkers
标记(Markers)
python
undefinedpython
undefinedSkip
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"]
undefinedundefinedAssertions
断言(Assertions)
python
undefinedpython
undefinedBasic
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()
undefinedwith pytest.warns(DeprecationWarning):
deprecated_function()
undefinedCoverage
覆盖率(Coverage)
bash
undefinedbash
undefinedInstall
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
undefinedpytest --cov=myapp --cov-fail-under=80
undefinedconftest.py
conftest.py配置
python
undefinedpython
undefinedtests/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}"}
undefinedimport 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}"}
undefinedpyproject.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