352 lines
11 KiB
Markdown
352 lines
11 KiB
Markdown
# Tests Folder Restructure Plan
|
|
|
|
## Current vs Recommended Structure
|
|
|
|
### Before (Single Folder)
|
|
```
|
|
tests/
|
|
├── test_auth.py
|
|
├── test_products.py
|
|
├── test_stock.py
|
|
├── test_shops.py
|
|
├── test_marketplace.py
|
|
├── test_admin.py
|
|
├── test_stats.py
|
|
├── test_database.py
|
|
├── test_utils.py
|
|
├── conftest.py
|
|
└── ...more files
|
|
```
|
|
|
|
### After (Organized Structure)
|
|
```
|
|
tests/
|
|
├── conftest.py # Global test configuration and fixtures
|
|
├── pytest.ini # Pytest configuration
|
|
├── __init__.py
|
|
├── fixtures/ # Shared test fixtures
|
|
│ ├── __init__.py
|
|
│ ├── auth_fixtures.py # Auth-related fixtures
|
|
│ ├── product_fixtures.py # Product fixtures
|
|
│ ├── shop_fixtures.py # Shop fixtures
|
|
│ └── database_fixtures.py # Database setup fixtures
|
|
├── unit/ # Unit tests (isolated, fast)
|
|
│ ├── __init__.py
|
|
│ ├── conftest.py # Unit test specific fixtures
|
|
│ ├── models/ # Test database and API models
|
|
│ │ ├── __init__.py
|
|
│ │ ├── test_user_model.py
|
|
│ │ ├── test_product_model.py
|
|
│ │ ├── test_shop_model.py
|
|
│ │ └── test_stock_model.py
|
|
│ ├── utils/ # Test utility functions
|
|
│ │ ├── __init__.py
|
|
│ │ ├── test_data_processing.py
|
|
│ │ ├── test_csv_processor.py
|
|
│ │ └── test_database_utils.py
|
|
│ ├── services/ # Test business logic
|
|
│ │ ├── __init__.py
|
|
│ │ ├── test_auth_service.py
|
|
│ │ ├── test_product_service.py
|
|
│ │ └── test_marketplace_service.py
|
|
│ └── middleware/ # Test middleware components
|
|
│ ├── __init__.py
|
|
│ ├── test_auth_middleware.py
|
|
│ ├── test_rate_limiter.py
|
|
│ └── test_error_handler.py
|
|
├── integration/ # Integration tests (multiple components)
|
|
│ ├── __init__.py
|
|
│ ├── conftest.py # Integration test fixtures
|
|
│ ├── api/ # API endpoint tests
|
|
│ │ ├── __init__.py
|
|
│ │ ├── conftest.py # API test fixtures (test client, etc.)
|
|
│ │ ├── v1/
|
|
│ │ │ ├── __init__.py
|
|
│ │ │ ├── test_auth_endpoints.py
|
|
│ │ │ ├── test_product_endpoints.py
|
|
│ │ │ ├── test_shop_endpoints.py
|
|
│ │ │ ├── test_stock_endpoints.py
|
|
│ │ │ ├── test_marketplace_endpoints.py
|
|
│ │ │ ├── test_admin_endpoints.py
|
|
│ │ │ └── test_stats_endpoints.py
|
|
│ │ └── test_api_main.py # Test API router setup
|
|
│ ├── database/ # Database integration tests
|
|
│ │ ├── __init__.py
|
|
│ │ ├── test_crud_operations.py
|
|
│ │ ├── test_relationships.py
|
|
│ │ └── test_migrations.py
|
|
│ └── workflows/ # End-to-end workflow tests
|
|
│ ├── __init__.py
|
|
│ ├── test_product_import_workflow.py
|
|
│ ├── test_shop_setup_workflow.py
|
|
│ └── test_stock_management_workflow.py
|
|
├── e2e/ # End-to-end tests (full application)
|
|
│ ├── __init__.py
|
|
│ ├── conftest.py
|
|
│ ├── test_user_registration_flow.py
|
|
│ ├── test_marketplace_import_flow.py
|
|
│ └── test_complete_shop_setup.py
|
|
├── performance/ # Performance and load tests
|
|
│ ├── __init__.py
|
|
│ ├── test_api_performance.py
|
|
│ ├── test_database_performance.py
|
|
│ └── test_import_performance.py
|
|
└── test_data/ # Test data files
|
|
├── csv/
|
|
│ ├── valid_products.csv
|
|
│ ├── invalid_products.csv
|
|
│ └── large_dataset.csv
|
|
├── json/
|
|
│ ├── sample_product.json
|
|
│ └── marketplace_response.json
|
|
└── fixtures/
|
|
├── test_users.json
|
|
└── test_products.json
|
|
```
|
|
|
|
## File Organization Principles
|
|
|
|
### 1. Test Types Separation
|
|
- **Unit Tests**: Fast, isolated tests for individual functions/classes
|
|
- **Integration Tests**: Tests that involve multiple components working together
|
|
- **E2E Tests**: Full application flow tests
|
|
- **Performance Tests**: Load and performance testing
|
|
|
|
### 2. Fixture Organization
|
|
```python
|
|
# tests/fixtures/auth_fixtures.py
|
|
import pytest
|
|
from models.database.user import User
|
|
from utils.auth import create_access_token
|
|
|
|
@pytest.fixture
|
|
def test_user_data():
|
|
return {
|
|
"email": "test@example.com",
|
|
"username": "testuser",
|
|
"password": "testpass123"
|
|
}
|
|
|
|
@pytest.fixture
|
|
def authenticated_user(db_session, test_user_data):
|
|
user = User(**test_user_data)
|
|
db_session.add(user)
|
|
db_session.commit()
|
|
return user
|
|
|
|
@pytest.fixture
|
|
def auth_headers(authenticated_user):
|
|
token = create_access_token(data={"sub": authenticated_user.username})
|
|
return {"Authorization": f"Bearer {token}"}
|
|
```
|
|
|
|
### 3. Conftest.py Structure
|
|
```python
|
|
# tests/conftest.py (Global fixtures)
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from main import app
|
|
from app.core.database import get_db, Base
|
|
|
|
# Database fixtures
|
|
@pytest.fixture(scope="session")
|
|
def test_engine():
|
|
engine = create_engine("sqlite:///test.db")
|
|
Base.metadata.create_all(bind=engine)
|
|
yield engine
|
|
Base.metadata.drop_all(bind=engine)
|
|
|
|
@pytest.fixture
|
|
def db_session(test_engine):
|
|
TestingSessionLocal = sessionmaker(bind=test_engine)
|
|
session = TestingSessionLocal()
|
|
yield session
|
|
session.close()
|
|
|
|
@pytest.fixture
|
|
def client(db_session):
|
|
def override_get_db():
|
|
yield db_session
|
|
|
|
app.dependency_overrides[get_db] = override_get_db
|
|
yield TestClient(app)
|
|
app.dependency_overrides.clear()
|
|
```
|
|
|
|
```python
|
|
# tests/integration/api/conftest.py (API-specific fixtures)
|
|
import pytest
|
|
|
|
@pytest.fixture
|
|
def api_client(client):
|
|
"""Pre-configured API client for integration tests"""
|
|
return client
|
|
|
|
@pytest.fixture
|
|
def admin_client(client, admin_auth_headers):
|
|
"""API client with admin authentication"""
|
|
client.headers.update(admin_auth_headers)
|
|
return client
|
|
```
|
|
|
|
## Test Naming Conventions
|
|
|
|
### File Naming
|
|
- `test_*.py` for all test files
|
|
- Mirror your app structure: `test_product_endpoints.py` for `api/v1/products.py`
|
|
- Use descriptive names: `test_marketplace_import_workflow.py`
|
|
|
|
### Test Function Naming
|
|
```python
|
|
# Good test naming patterns
|
|
def test_create_product_with_valid_data_returns_201():
|
|
pass
|
|
|
|
def test_create_product_without_title_returns_422():
|
|
pass
|
|
|
|
def test_get_product_by_id_returns_product_data():
|
|
pass
|
|
|
|
def test_get_nonexistent_product_returns_404():
|
|
pass
|
|
|
|
def test_update_product_stock_updates_quantity():
|
|
pass
|
|
|
|
def test_marketplace_import_with_invalid_csv_fails_gracefully():
|
|
pass
|
|
```
|
|
|
|
## Running Tests by Category
|
|
|
|
### Pytest Configuration (pytest.ini)
|
|
```ini
|
|
[tool:pytest]
|
|
testpaths = tests
|
|
python_files = test_*.py
|
|
python_classes = Test*
|
|
python_functions = test_*
|
|
addopts =
|
|
-v
|
|
--tb=short
|
|
--strict-markers
|
|
markers =
|
|
unit: Unit tests
|
|
integration: Integration tests
|
|
e2e: End-to-end tests
|
|
performance: Performance tests
|
|
slow: Slow running tests
|
|
database: Tests that require database
|
|
external: Tests that require external services
|
|
```
|
|
|
|
### Running Specific Test Categories
|
|
```bash
|
|
# Run only unit tests (fast)
|
|
pytest tests/unit -m unit
|
|
|
|
# Run only integration tests
|
|
pytest tests/integration -m integration
|
|
|
|
# Run all tests except slow ones
|
|
pytest -m "not slow"
|
|
|
|
# Run only database tests
|
|
pytest -m database
|
|
|
|
# Run tests for specific domain
|
|
pytest tests/unit/models/ tests/integration/api/v1/test_product_endpoints.py
|
|
|
|
# Run with coverage
|
|
pytest --cov=app --cov-report=html
|
|
```
|
|
|
|
## Benefits of This Structure
|
|
|
|
1. **Faster Development**: Developers can run relevant test subsets
|
|
2. **Clear Separation**: Easy to understand what each test covers
|
|
3. **Parallel Execution**: Can run different test types in parallel
|
|
4. **Maintenance**: Easier to maintain and update tests
|
|
5. **CI/CD Pipeline**: Can have different pipeline stages for different test types
|
|
|
|
## Migration Strategy
|
|
|
|
### Phase 1: Create Structure
|
|
1. Create new directory structure
|
|
2. Move global fixtures to `fixtures/` directory
|
|
3. Update `conftest.py` files
|
|
|
|
### Phase 2: Move Unit Tests
|
|
1. Start with utility and model tests
|
|
2. Move to `tests/unit/` with appropriate subdirectories
|
|
3. Update imports and fixtures
|
|
|
|
### Phase 3: Move Integration Tests
|
|
1. Move API endpoint tests to `tests/integration/api/`
|
|
2. Create database integration tests
|
|
3. Add workflow tests
|
|
|
|
### Phase 4: Add Missing Coverage
|
|
1. Add performance tests if needed
|
|
2. Create E2E tests for critical flows
|
|
3. Add proper test markers
|
|
|
|
## Example Test File Structure
|
|
|
|
### Unit Test Example
|
|
```python
|
|
# tests/unit/models/test_product_model.py
|
|
import pytest
|
|
from models.database import Product
|
|
|
|
class TestProductModel:
|
|
def test_product_creation_with_valid_data(self, db_session):
|
|
product = Product(
|
|
product_id="TEST123",
|
|
title="Test Product",
|
|
price="99.99"
|
|
)
|
|
db_session.add(product)
|
|
db_session.commit()
|
|
|
|
assert product.id is not None
|
|
assert product.product_id == "TEST123"
|
|
assert product.title == "Test Product"
|
|
|
|
def test_product_gtin_relationship_with_stock(self, db_session, test_product, test_stock):
|
|
# Test the GTIN-based relationship
|
|
assert test_product.gtin in [stock.gtin for stock in test_product.stock_entries]
|
|
```
|
|
|
|
### Integration Test Example
|
|
```python
|
|
# tests/integration/api/v1/test_product_endpoints.py
|
|
import pytest
|
|
|
|
class TestProductEndpoints:
|
|
def test_create_product_endpoint(self, client, auth_headers, valid_product_data):
|
|
response = client.post(
|
|
"/api/v1/products/",
|
|
json=valid_product_data,
|
|
headers=auth_headers
|
|
)
|
|
assert response.status_code == 201
|
|
assert response.json()["product_id"] == valid_product_data["product_id"]
|
|
|
|
def test_get_products_with_pagination(self, client, auth_headers, multiple_products):
|
|
response = client.get(
|
|
"/api/v1/products/?skip=0&limit=10",
|
|
headers=auth_headers
|
|
)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "products" in data
|
|
assert "total" in data
|
|
assert len(data["products"]) <= 10
|
|
```
|
|
|
|
This structure will scale beautifully as your application grows and makes it much easier for your team to maintain and extend the test suite! |