ADR-014: Test Structure OrganizationΒΆ

Estado: βœ… Accepted Date: 2026-01-29 Deciders: Rodrigo RoldΓ‘n

ContextΒΆ

Testing puede organizarse de varias formas:

  1. Flat structure: Todos los tests en un directorio

  2. Mirror structure: Tests reflejan estructura de src/

  3. Organized by type: unit/, integration/, e2e/

Reqivo necesita:

  • Unit tests para cada mΓ³dulo

  • Integration tests para flujos completos

  • Mapeo claro cΓ³digo ↔ tests

DecisionΒΆ

Estructura organizada por tipo con mirror de src/:

tests/
β”œβ”€β”€ unit/                          ← Tests unitarios
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ test_version.py           ← src/reqivo/version.py
β”‚   β”œβ”€β”€ test_exceptions.py        ← src/reqivo/exceptions.py
β”‚   β”œβ”€β”€ test_utils.py             ← src/reqivo/utils/*
β”‚   β”œβ”€β”€ test_response.py          ← src/reqivo/client/response.py
β”‚   β”œβ”€β”€ test_request.py           ← src/reqivo/client/request.py
β”‚   β”œβ”€β”€ test_session.py           ← src/reqivo/client/session.py (pendiente)
β”‚   β”œβ”€β”€ test_websocket.py         ← src/reqivo/client/websocket.py (pendiente)
β”‚   β”œβ”€β”€ test_http_parser.py       ← src/reqivo/http/http11.py (pendiente)
β”‚   β”œβ”€β”€ test_headers.py           ← src/reqivo/http/headers.py (pendiente)
β”‚   β”œβ”€β”€ test_body.py              ← src/reqivo/http/body.py (pendiente)
β”‚   β”œβ”€β”€ test_connection.py        ← src/reqivo/transport/connection.py (pendiente)
β”‚   └── test_connection_pool.py   ← src/reqivo/transport/connection_pool.py (pendiente)
β”‚
β”œβ”€β”€ integration/                   ← Tests de integraciΓ³n
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ test_http_requests.py     ← GET/POST flows completos
β”‚   β”œβ”€β”€ test_session_cookies.py   ← Session + cookies + redirects
β”‚   β”œβ”€β”€ test_websocket_flow.py    ← WebSocket handshake + messages
β”‚   β”œβ”€β”€ test_connection_pooling.py ← Pool reuse + concurrency
β”‚   β”œβ”€β”€ test_tls_connections.py   ← HTTPS + TLS
β”‚   └── test_timeouts.py          ← Timeout scenarios
β”‚
β”œβ”€β”€ e2e/                           ← Tests end-to-end (futuro)
β”‚   └── test_real_servers.py      ← Tests contra httpbin, etc.
β”‚
└── utils/                         ← Test utilities
    β”œβ”€β”€ __init__.py
    β”œβ”€β”€ fixtures.py               ← Shared fixtures
    β”œβ”€β”€ mock_server.py            ← Mock HTTP server
    └── assertions.py             ← Custom assertions

Principios:

  1. Mirror source structure:

    • src/reqivo/client/response.py β†’ tests/unit/test_response.py

    • Un test file por cada source file

  2. Naming convention:

    • Unit tests: test_<module>.py

    • Integration tests: test_<feature>_flow.py o test_<component>_integration.py

  3. Test organization dentro del archivo:

    # tests/unit/test_response.py
    
    class TestResponseInit:
        """Tests for Response.__init__()"""
    
    class TestResponseText:
        """Tests for Response.text()"""
    
    class TestResponseJson:
        """Tests for Response.json()"""
    
    class TestResponseStreaming:
        """Tests for Response.iter_*()"""
    
  4. Fixture organization:

    • Fixtures comunes en tests/utils/fixtures.py

    • Fixtures especΓ­ficas en conftest.py local

ConsequencesΒΆ

Positive βœ…ΒΆ

  1. Findability: FΓ‘cil encontrar tests para un mΓ³dulo

  2. Completeness: Detectar mΓ³dulos sin tests

  3. Separation: Unit vs integration claro

  4. Scalability: Estructura crece con proyecto

  5. Clear mapping: 1:1 entre source y test files

Negative ❌¢

  1. Duplicate structure: Dos Γ‘rboles de directorios (src + tests)

  2. Renaming overhead: Renombrar mΓ³dulo requiere renombrar test

  3. Large test files: MΓ³dulos grandes β†’ test files grandes

MitigationsΒΆ

  • Split large tests: Dividir por funcionalidad si es muy grande

  • Shared utilities: Reutilizar fixtures y helpers

  • Clear docstrings: Documentar quΓ© testea cada clase/funciΓ³n

Test NamingΒΆ

Unit test:

def test_response_text_decodes_utf8():
    """Response.text() should decode UTF-8 body correctly."""

def test_response_json_raises_on_invalid_json():
    """Response.json() should raise ValueError on invalid JSON."""

Integration test:

def test_session_preserves_cookies_across_requests():
    """Session should send cookies from previous response."""

def test_connection_pool_reuses_connections():
    """Connection pool should reuse connections for same host."""

Current StatusΒΆ

Existentes:

  • βœ… tests/unit/test_version.py

  • βœ… tests/unit/test_exceptions.py

  • βœ… tests/unit/test_utils.py

  • βœ… tests/unit/test_response.py

  • βœ… tests/unit/test_request.py

Pendientes (segΓΊn ADR-014):

  • ❌ tests/unit/test_session.py

  • ❌ tests/unit/test_websocket.py

  • ❌ tests/unit/test_http_parser.py

  • ❌ tests/unit/test_headers.py

  • ❌ tests/unit/test_body.py

  • ❌ tests/unit/test_connection.py

  • ❌ tests/unit/test_connection_pool.py

  • ❌ tests/unit/test_auth.py

  • ❌ tests/integration/* (todos)

Alternatives ConsideredΒΆ

  1. Flat structure: Rejected. DifΓ­cil escalar.

  2. By feature: Rejected. Ambiguo quΓ© es una β€œfeature”.

  3. Colocated tests: Rejected. Mezcla concerns.

ReferencesΒΆ

  • pytest documentation: Test layout

  • Python Packaging Guide