Initial commit
This commit is contained in:
550
skills/temporal-python-testing/resources/local-setup.md
Normal file
550
skills/temporal-python-testing/resources/local-setup.md
Normal file
@@ -0,0 +1,550 @@
|
||||
# Local Development Setup for Temporal Python Testing
|
||||
|
||||
Comprehensive guide for setting up local Temporal development environment with pytest integration and coverage tracking.
|
||||
|
||||
## Temporal Server Setup with Docker Compose
|
||||
|
||||
### Basic Docker Compose Configuration
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
temporal:
|
||||
image: temporalio/auto-setup:latest
|
||||
container_name: temporal-dev
|
||||
ports:
|
||||
- "7233:7233" # Temporal server
|
||||
- "8233:8233" # Web UI
|
||||
environment:
|
||||
- DB=postgresql
|
||||
- POSTGRES_USER=temporal
|
||||
- POSTGRES_PWD=temporal
|
||||
- POSTGRES_SEEDS=postgresql
|
||||
- DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yaml
|
||||
depends_on:
|
||||
- postgresql
|
||||
|
||||
postgresql:
|
||||
image: postgres:14-alpine
|
||||
container_name: temporal-postgres
|
||||
environment:
|
||||
- POSTGRES_USER=temporal
|
||||
- POSTGRES_PASSWORD=temporal
|
||||
- POSTGRES_DB=temporal
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
temporal-ui:
|
||||
image: temporalio/ui:latest
|
||||
container_name: temporal-ui
|
||||
depends_on:
|
||||
- temporal
|
||||
environment:
|
||||
- TEMPORAL_ADDRESS=temporal:7233
|
||||
- TEMPORAL_CORS_ORIGINS=http://localhost:3000
|
||||
ports:
|
||||
- "8080:8080"
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
### Starting Local Server
|
||||
|
||||
```bash
|
||||
# Start Temporal server
|
||||
docker-compose up -d
|
||||
|
||||
# Verify server is running
|
||||
docker-compose ps
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f temporal
|
||||
|
||||
# Access Temporal Web UI
|
||||
open http://localhost:8080
|
||||
|
||||
# Stop server
|
||||
docker-compose down
|
||||
|
||||
# Reset data (clean slate)
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
### Health Check Script
|
||||
|
||||
```python
|
||||
# scripts/health_check.py
|
||||
import asyncio
|
||||
from temporalio.client import Client
|
||||
|
||||
async def check_temporal_health():
|
||||
"""Verify Temporal server is accessible"""
|
||||
try:
|
||||
client = await Client.connect("localhost:7233")
|
||||
print("✓ Connected to Temporal server")
|
||||
|
||||
# Test workflow execution
|
||||
from temporalio.worker import Worker
|
||||
|
||||
@workflow.defn
|
||||
class HealthCheckWorkflow:
|
||||
@workflow.run
|
||||
async def run(self) -> str:
|
||||
return "healthy"
|
||||
|
||||
async with Worker(
|
||||
client,
|
||||
task_queue="health-check",
|
||||
workflows=[HealthCheckWorkflow],
|
||||
):
|
||||
result = await client.execute_workflow(
|
||||
HealthCheckWorkflow.run,
|
||||
id="health-check",
|
||||
task_queue="health-check",
|
||||
)
|
||||
print(f"✓ Workflow execution successful: {result}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Health check failed: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(check_temporal_health())
|
||||
```
|
||||
|
||||
## pytest Configuration
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
temporal-project/
|
||||
├── docker-compose.yml
|
||||
├── pyproject.toml
|
||||
├── pytest.ini
|
||||
├── requirements.txt
|
||||
├── src/
|
||||
│ ├── workflows/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── order_workflow.py
|
||||
│ │ └── payment_workflow.py
|
||||
│ └── activities/
|
||||
│ ├── __init__.py
|
||||
│ ├── payment_activities.py
|
||||
│ └── inventory_activities.py
|
||||
├── tests/
|
||||
│ ├── conftest.py
|
||||
│ ├── unit/
|
||||
│ │ ├── test_workflows.py
|
||||
│ │ └── test_activities.py
|
||||
│ ├── integration/
|
||||
│ │ └── test_order_flow.py
|
||||
│ └── replay/
|
||||
│ └── test_workflow_replay.py
|
||||
└── scripts/
|
||||
├── health_check.py
|
||||
└── export_histories.py
|
||||
```
|
||||
|
||||
### pytest Configuration
|
||||
|
||||
```ini
|
||||
# pytest.ini
|
||||
[pytest]
|
||||
asyncio_mode = auto
|
||||
testpaths = tests
|
||||
python_files = test_*.py
|
||||
python_classes = Test*
|
||||
python_functions = test_*
|
||||
|
||||
# Markers for test categorization
|
||||
markers =
|
||||
unit: Unit tests (fast, isolated)
|
||||
integration: Integration tests (require Temporal server)
|
||||
replay: Replay tests (require production histories)
|
||||
slow: Slow running tests
|
||||
|
||||
# Coverage settings
|
||||
addopts =
|
||||
--verbose
|
||||
--strict-markers
|
||||
--cov=src
|
||||
--cov-report=term-missing
|
||||
--cov-report=html
|
||||
--cov-fail-under=80
|
||||
|
||||
# Async test timeout
|
||||
asyncio_default_fixture_loop_scope = function
|
||||
```
|
||||
|
||||
### Shared Test Fixtures
|
||||
|
||||
```python
|
||||
# tests/conftest.py
|
||||
import pytest
|
||||
from temporalio.testing import WorkflowEnvironment
|
||||
from temporalio.client import Client
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop():
|
||||
"""Provide event loop for async fixtures"""
|
||||
import asyncio
|
||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
async def temporal_client():
|
||||
"""Provide Temporal client connected to local server"""
|
||||
client = await Client.connect("localhost:7233")
|
||||
yield client
|
||||
await client.close()
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
async def workflow_env():
|
||||
"""Module-scoped time-skipping environment"""
|
||||
env = await WorkflowEnvironment.start_time_skipping()
|
||||
yield env
|
||||
await env.shutdown()
|
||||
|
||||
@pytest.fixture
|
||||
def activity_env():
|
||||
"""Function-scoped activity environment"""
|
||||
from temporalio.testing import ActivityEnvironment
|
||||
return ActivityEnvironment()
|
||||
|
||||
@pytest.fixture
|
||||
async def test_worker(temporal_client, workflow_env):
|
||||
"""Pre-configured test worker"""
|
||||
from temporalio.worker import Worker
|
||||
from src.workflows import OrderWorkflow, PaymentWorkflow
|
||||
from src.activities import process_payment, update_inventory
|
||||
|
||||
return Worker(
|
||||
workflow_env.client,
|
||||
task_queue="test-queue",
|
||||
workflows=[OrderWorkflow, PaymentWorkflow],
|
||||
activities=[process_payment, update_inventory],
|
||||
)
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
```txt
|
||||
# requirements.txt
|
||||
temporalio>=1.5.0
|
||||
pytest>=7.4.0
|
||||
pytest-asyncio>=0.21.0
|
||||
pytest-cov>=4.1.0
|
||||
pytest-xdist>=3.3.0 # Parallel test execution
|
||||
```
|
||||
|
||||
```toml
|
||||
# pyproject.toml
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0"]
|
||||
build-backend = "setuptools.build_backend"
|
||||
|
||||
[project]
|
||||
name = "temporal-project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"temporalio>=1.5.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=7.4.0",
|
||||
"pytest-asyncio>=0.21.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
"pytest-xdist>=3.3.0",
|
||||
]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
testpaths = ["tests"]
|
||||
```
|
||||
|
||||
## Coverage Configuration
|
||||
|
||||
### Coverage Settings
|
||||
|
||||
```ini
|
||||
# .coveragerc
|
||||
[run]
|
||||
source = src
|
||||
omit =
|
||||
*/tests/*
|
||||
*/venv/*
|
||||
*/__pycache__/*
|
||||
|
||||
[report]
|
||||
exclude_lines =
|
||||
# Exclude type checking blocks
|
||||
if TYPE_CHECKING:
|
||||
# Exclude debug code
|
||||
def __repr__
|
||||
# Exclude abstract methods
|
||||
@abstractmethod
|
||||
# Exclude pass statements
|
||||
pass
|
||||
|
||||
[html]
|
||||
directory = htmlcov
|
||||
```
|
||||
|
||||
### Running Tests with Coverage
|
||||
|
||||
```bash
|
||||
# Run all tests with coverage
|
||||
pytest --cov=src --cov-report=term-missing
|
||||
|
||||
# Generate HTML coverage report
|
||||
pytest --cov=src --cov-report=html
|
||||
open htmlcov/index.html
|
||||
|
||||
# Run specific test categories
|
||||
pytest -m unit # Unit tests only
|
||||
pytest -m integration # Integration tests only
|
||||
pytest -m "not slow" # Skip slow tests
|
||||
|
||||
# Parallel execution (faster)
|
||||
pytest -n auto # Use all CPU cores
|
||||
|
||||
# Fail if coverage below threshold
|
||||
pytest --cov=src --cov-fail-under=80
|
||||
```
|
||||
|
||||
### Coverage Report Example
|
||||
|
||||
```
|
||||
---------- coverage: platform darwin, python 3.11.5 -----------
|
||||
Name Stmts Miss Cover Missing
|
||||
-----------------------------------------------------------------
|
||||
src/__init__.py 0 0 100%
|
||||
src/activities/__init__.py 2 0 100%
|
||||
src/activities/inventory.py 45 3 93% 78-80
|
||||
src/activities/payment.py 38 0 100%
|
||||
src/workflows/__init__.py 2 0 100%
|
||||
src/workflows/order_workflow.py 67 5 93% 45-49
|
||||
src/workflows/payment_workflow.py 52 0 100%
|
||||
-----------------------------------------------------------------
|
||||
TOTAL 206 8 96%
|
||||
|
||||
10 files skipped due to complete coverage.
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Daily Development Flow
|
||||
|
||||
```bash
|
||||
# 1. Start Temporal server
|
||||
docker-compose up -d
|
||||
|
||||
# 2. Verify server health
|
||||
python scripts/health_check.py
|
||||
|
||||
# 3. Run tests during development
|
||||
pytest tests/unit/ --verbose
|
||||
|
||||
# 4. Run full test suite before commit
|
||||
pytest --cov=src --cov-report=term-missing
|
||||
|
||||
# 5. Check coverage
|
||||
open htmlcov/index.html
|
||||
|
||||
# 6. Stop server
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
### Pre-Commit Hook
|
||||
|
||||
```bash
|
||||
# .git/hooks/pre-commit
|
||||
#!/bin/bash
|
||||
|
||||
echo "Running tests..."
|
||||
pytest --cov=src --cov-fail-under=80
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Tests failed. Commit aborted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All tests passed!"
|
||||
```
|
||||
|
||||
### Makefile for Common Tasks
|
||||
|
||||
```makefile
|
||||
# Makefile
|
||||
.PHONY: setup test test-unit test-integration coverage clean
|
||||
|
||||
setup:
|
||||
docker-compose up -d
|
||||
pip install -r requirements.txt
|
||||
python scripts/health_check.py
|
||||
|
||||
test:
|
||||
pytest --cov=src --cov-report=term-missing
|
||||
|
||||
test-unit:
|
||||
pytest -m unit --verbose
|
||||
|
||||
test-integration:
|
||||
pytest -m integration --verbose
|
||||
|
||||
test-replay:
|
||||
pytest -m replay --verbose
|
||||
|
||||
test-parallel:
|
||||
pytest -n auto --cov=src
|
||||
|
||||
coverage:
|
||||
pytest --cov=src --cov-report=html
|
||||
open htmlcov/index.html
|
||||
|
||||
clean:
|
||||
docker-compose down -v
|
||||
rm -rf .pytest_cache htmlcov .coverage
|
||||
|
||||
ci:
|
||||
docker-compose up -d
|
||||
sleep 10 # Wait for Temporal to start
|
||||
pytest --cov=src --cov-fail-under=80
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
### CI/CD Example
|
||||
|
||||
```yaml
|
||||
# .github/workflows/test.yml
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Start Temporal server
|
||||
run: docker-compose up -d
|
||||
|
||||
- name: Wait for Temporal
|
||||
run: sleep 10
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Run tests with coverage
|
||||
run: |
|
||||
pytest --cov=src --cov-report=xml --cov-fail-under=80
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
file: ./coverage.xml
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: docker-compose down
|
||||
```
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
### Enable Temporal SDK Logging
|
||||
|
||||
```python
|
||||
import logging
|
||||
|
||||
# Enable debug logging for Temporal SDK
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
temporal_logger = logging.getLogger("temporalio")
|
||||
temporal_logger.setLevel(logging.DEBUG)
|
||||
```
|
||||
|
||||
### Interactive Debugging
|
||||
|
||||
```python
|
||||
# Add breakpoint in test
|
||||
@pytest.mark.asyncio
|
||||
async def test_workflow_with_breakpoint(workflow_env):
|
||||
import pdb; pdb.set_trace() # Debug here
|
||||
|
||||
async with Worker(...):
|
||||
result = await workflow_env.client.execute_workflow(...)
|
||||
```
|
||||
|
||||
### Temporal Web UI
|
||||
|
||||
```bash
|
||||
# Access Web UI at http://localhost:8080
|
||||
# - View workflow executions
|
||||
# - Inspect event history
|
||||
# - Replay workflows
|
||||
# - Monitor workers
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Isolated Environment**: Use Docker Compose for reproducible local setup
|
||||
2. **Health Checks**: Always verify Temporal server before running tests
|
||||
3. **Fast Feedback**: Use pytest markers to run unit tests quickly
|
||||
4. **Coverage Targets**: Maintain ≥80% code coverage
|
||||
5. **Parallel Testing**: Use pytest-xdist for faster test runs
|
||||
6. **CI/CD Integration**: Automated testing on every commit
|
||||
7. **Cleanup**: Clear Docker volumes between test runs if needed
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Issue: Temporal server not starting**
|
||||
```bash
|
||||
# Check logs
|
||||
docker-compose logs temporal
|
||||
|
||||
# Reset database
|
||||
docker-compose down -v
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
**Issue: Tests timing out**
|
||||
```python
|
||||
# Increase timeout in pytest.ini
|
||||
asyncio_default_timeout = 30
|
||||
```
|
||||
|
||||
**Issue: Port already in use**
|
||||
```bash
|
||||
# Find process using port 7233
|
||||
lsof -i :7233
|
||||
|
||||
# Kill process or change port in docker-compose.yml
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- Temporal Local Development: docs.temporal.io/develop/python/local-dev
|
||||
- pytest Documentation: docs.pytest.org
|
||||
- Docker Compose: docs.docker.com/compose
|
||||
- pytest-asyncio: github.com/pytest-dev/pytest-asyncio
|
||||
Reference in New Issue
Block a user