Files
Nguyen Duc Thao 3861b027b2
Some checks failed
K8S Fission Deployment / Deployment fission functions (push) Failing after 12s
add new
2026-01-26 23:07:28 +07:00

297 lines
6.6 KiB
Markdown

# Fixtures
## Table of Contents
- [Basic Fixtures](#basic-fixtures)
- [Fixture Scopes](#fixture-scopes)
- [Fixture Parameters](#fixture-parameters)
- [Conftest.py](#conftestpy)
- [Built-in Fixtures](#built-in-fixtures)
## Basic Fixtures
### Simple Fixture
```python
import pytest
@pytest.fixture
def user_data():
"""Return sample user data."""
return {
"name": "John Doe",
"email": "john@example.com",
"age": 30,
}
def test_user_name(user_data):
assert user_data["name"] == "John Doe"
def test_user_email(user_data):
assert "@" in user_data["email"]
```
### Fixture with Setup and Teardown
```python
@pytest.fixture
def database():
"""Setup database, yield, then cleanup."""
# Setup
db = create_database()
db.connect()
yield db # Test runs here
# Teardown
db.clear()
db.disconnect()
def test_insert(database):
database.insert({"id": 1, "name": "Test"})
assert database.count() == 1
```
### Fixture Returning Factory
```python
@pytest.fixture
def make_user():
"""Return a factory function for creating users."""
created_users = []
def _make_user(name: str, email: str = None):
user = User(name=name, email=email or f"{name}@example.com")
created_users.append(user)
return user
yield _make_user
# Cleanup all created users
for user in created_users:
user.delete()
def test_multiple_users(make_user):
user1 = make_user("Alice")
user2 = make_user("Bob")
assert user1.name != user2.name
```
## Fixture Scopes
```python
# Function scope (default) - runs for each test
@pytest.fixture(scope="function")
def fresh_data():
return {"count": 0}
# Class scope - runs once per test class
@pytest.fixture(scope="class")
def class_resource():
return ExpensiveResource()
# Module scope - runs once per test module
@pytest.fixture(scope="module")
def module_connection():
conn = create_connection()
yield conn
conn.close()
# Session scope - runs once per test session
@pytest.fixture(scope="session")
def session_config():
return load_config()
```
### Scope Hierarchy
```
session (once per test run)
└── package (once per test package)
└── module (once per test file)
└── class (once per test class)
└── function (once per test function)
```
## Fixture Parameters
### Parametrized Fixtures
```python
@pytest.fixture(params=["sqlite", "postgresql", "mysql"])
def database_type(request):
"""Run tests with each database type."""
return request.param
def test_connection(database_type):
# This test runs 3 times, once for each database
db = create_database(database_type)
assert db.connect()
```
### Fixture with IDs
```python
@pytest.fixture(params=[
pytest.param({"admin": True}, id="admin"),
pytest.param({"admin": False}, id="regular"),
])
def user_config(request):
return request.param
# Test output shows: test_permissions[admin], test_permissions[regular]
```
### Indirect Parametrization
```python
@pytest.fixture
def user(request):
"""Create user based on parameter."""
role = request.param
return User(role=role)
@pytest.mark.parametrize("user", ["admin", "editor", "viewer"], indirect=True)
def test_user_access(user):
# user fixture receives each role as request.param
assert user.role in ["admin", "editor", "viewer"]
```
## Conftest.py
### tests/conftest.py
```python
"""Shared fixtures for all tests."""
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# Session-scoped database engine
@pytest.fixture(scope="session")
def engine():
return create_engine("sqlite:///:memory:")
# Function-scoped session with transaction rollback
@pytest.fixture(scope="function")
def db_session(engine):
connection = engine.connect()
transaction = connection.begin()
Session = sessionmaker(bind=connection)
session = Session()
yield session
session.close()
transaction.rollback()
connection.close()
# Shared test data
@pytest.fixture
def sample_products():
return [
{"name": "Widget", "price": 9.99},
{"name": "Gadget", "price": 19.99},
{"name": "Gizmo", "price": 29.99},
]
```
### Nested Conftest Files
```
tests/
├── conftest.py # Available to all tests
├── unit/
│ ├── conftest.py # Available to unit tests only
│ └── test_models.py
└── integration/
├── conftest.py # Available to integration tests only
└── test_api.py
```
## Built-in Fixtures
### tmp_path / tmp_path_factory
```python
def test_create_file(tmp_path):
"""tmp_path provides unique temporary directory."""
file = tmp_path / "test.txt"
file.write_text("Hello, World!")
assert file.read_text() == "Hello, World!"
@pytest.fixture(scope="session")
def session_temp_dir(tmp_path_factory):
"""Create temp dir for entire session."""
return tmp_path_factory.mktemp("session_data")
```
### capsys / capfd
```python
def test_print_output(capsys):
"""Capture stdout/stderr."""
print("Hello")
captured = capsys.readouterr()
assert captured.out == "Hello\n"
def test_file_descriptor_output(capfd):
"""Capture at file descriptor level."""
import os
os.system("echo 'Hello'")
captured = capfd.readouterr()
assert "Hello" in captured.out
```
### monkeypatch
```python
def test_env_variable(monkeypatch):
"""Modify environment temporarily."""
monkeypatch.setenv("API_KEY", "test-key")
assert os.environ["API_KEY"] == "test-key"
def test_module_attribute(monkeypatch):
"""Modify module attribute temporarily."""
monkeypatch.setattr("module.CONFIG", {"debug": True})
assert module.CONFIG["debug"] is True
def test_dict_item(monkeypatch):
"""Modify dictionary temporarily."""
monkeypatch.setitem(app.settings, "DEBUG", True)
```
### request
```python
@pytest.fixture
def resource(request):
"""Access test context."""
print(f"Running: {request.node.name}")
print(f"Module: {request.module.__name__}")
print(f"Function: {request.function.__name__}")
# Access fixture parameters
if hasattr(request, "param"):
return create_resource(request.param)
return create_default_resource()
```
### Autouse Fixtures
```python
@pytest.fixture(autouse=True)
def setup_logging():
"""Automatically runs for every test."""
logging.basicConfig(level=logging.DEBUG)
yield
logging.shutdown()
@pytest.fixture(autouse=True, scope="session")
def global_setup():
"""Run once at session start."""
initialize_system()
yield
cleanup_system()
```