# Regla: Pruebas

Los tests no son opcionales. Son parte de la definición de "terminado" para
cualquier feature, bugfix o refactor. Código sin tests es código que no se puede
mantener con confianza.

---

## Tests obligatorios para toda feature nueva

- Ninguna feature se considera completa sin tests que la cubran.
- Definición de Done incluye:
  - Tests unitarios para toda función de lógica de negocio nueva
  - Tests de integración para todo endpoint nuevo
  - Tests de frontera para inputs extremos y casos de error
- El revisor rechaza PRs sin tests para código nuevo, sin excepción.
- Refactors: si el código refactorizado no tenía tests, agregarlos es parte del
  trabajo de refactor, no una tarea separada.
- Bugfixes: escribir el test que reproduce el bug ANTES de escribir el fix.
  Verificar que el test falla antes del fix y pasa después.

---

## Cobertura mínima >80%

- La cobertura es una métrica, no el objetivo. Un 100% de cobertura con tests
  triviales no garantiza calidad. Pero cobertura baja sí indica código sin probar.
- Umbral mínimo: 80% de cobertura de líneas en módulos de lógica de negocio.
- CI falla si la cobertura cae por debajo del umbral al agregar código nuevo.
- Reportar cobertura diferencial en el PR (cobertura del código nuevo añadido).
- Excepciones documentadas: código de configuración, __main__, scaffolding generado.
  No se excluyen módulos por conveniencia — documentar explícitamente la razón.
- Herramientas: `pytest-cov` con `--cov-fail-under=80` para Python,
  `jest --coverage --coverageThreshold` para TypeScript.

---

## Patrón Arrange-Act-Assert (AAA)

Todo test unitario sigue este patrón sin excepción:

```python
def test_calcular_impuesto_con_tasa_estandar():
    # Arrange — preparar el estado inicial
    producto = Producto(precio=100.0, categoria="electronico")
    calculadora = CalculadoraImpuesto(tasa=0.16)

    # Act — ejecutar la unidad bajo prueba
    resultado = calculadora.calcular(producto)

    # Assert — verificar el resultado
    assert resultado == 16.0
```

Reglas del AAA:
- Los tres bloques separados visualmente (línea en blanco o comentario).
- Un solo Assert conceptual por test. Múltiples `assert` sobre el mismo resultado
  están bien; assert sobre comportamientos distintos no.
- Si el Arrange es muy extenso: extraer a función helper o usar factories.
- Nombre del test describe el escenario completo:
  `test_[unidad]_[escenario]_[resultado_esperado]`
  Ejemplo: `test_crear_factura_sin_items_lanza_error_validacion`

---

## Tests deterministas — sin sleep ni delays

- Un test que falla intermitentemente es peor que ningún test: genera desconfianza
  en el suite completo.
- NUNCA usar `time.sleep()`, `asyncio.sleep()`, `setTimeout()` en tests.
- NUNCA depender de tiempo real. Mockear el reloj:
  - Python: `freezegun`, `unittest.mock.patch('datetime.datetime.now')`
  - JavaScript: `jest.useFakeTimers()`
- NUNCA depender del orden de ejecución de los tests. Cada test es independiente.
- NUNCA depender del estado de un test anterior. Limpiar estado en `teardown`.
- Tests de integración con BD: usar transacciones que se revierten al final,
  o fixtures aislados por test.
- Seeds de datos en BD de test: deterministas y reproducibles.

---

## Factories sobre fixtures hardcodeados

- Fixtures hardcodeados (dicts o JSON estáticos con todos los campos) son frágiles:
  cambian con el schema y no comunican qué datos son relevantes para el test.
- Usar factories que generan objetos con valores por defecto razonables y permiten
  sobreescribir solo lo que importa para el test:

  ```python
  # factory_boy (Python)
  class FacturaFactory(factory.Factory):
      class Meta:
          model = Factura
      estatus = "borrador"
      total = 0.0
      cliente = factory.SubFactory(ClienteFactory)

  # En el test — solo los datos relevantes
  factura = FacturaFactory(estatus="pagada", total=1000.0)
  ```

- Factories en un módulo dedicado `tests/factories.py` o `tests/factories/`.
- No duplicar factories entre módulos — importar desde el módulo central.
- Para TypeScript: usar `@faker-js/faker` con funciones builder, no objetos
  hardcodeados.

---

## Tests de frontera para edge cases

Los casos felices son necesarios pero no suficientes. Todo test suite debe incluir:

- **Valores nulos / vacíos**: `None`, `""`, `[]`, `{}`, `0`
- **Valores en el límite**: longitud máxima, longitud mínima, valores justo
  por encima y por debajo de umbrales numéricos
- **Tipos inesperados**: qué pasa si llega un string donde se espera un número
- **Colecciones extremas**: lista de 1 elemento, lista de 10,000 elementos
- **Concurrencia**: si la función modifica estado compartido, probar acceso
  concurrente
- **Errores de dependencias externas**: qué pasa si la BD está caída, si la API
  externa devuelve error 500, si el filesystem está lleno
- **Strings con caracteres especiales**: Unicode, emojis, SQL injection attempts,
  XSS payloads — para validar que la sanitización funciona

Cada edge case que causó un bug en producción: agregar test de regresión.

---

## Nomenclatura y organización

- Archivo de test espeja el módulo que prueba:
  `app/services/factura_service.py` → `tests/services/test_factura_service.py`
- Tests de integración en `tests/integration/`, unitarios en `tests/unit/`.
- Agrupar tests relacionados en clases cuando hay mucho setup compartido.
- Nombre de la clase: `TestNombreDelModulo`. Nombre del método: `test_escenario`.
- No usar números en nombres de test (`test_1`, `test_caso_2`): sin semántica.

---

## Mocks y stubs

- Mockear en el boundary del sistema: BD, APIs externas, filesystem, reloj.
- No mockear código propio — si se mockea, es señal de acoplamiento excesivo.
- Verificar que los mocks se configuran para el comportamiento esperado Y para
  el comportamiento de error.
- Limpiar mocks entre tests para evitar contaminación de estado.
- Documentar en el test por qué se mockea algo si no es obvio.

---

## Checklist de tests antes de merge

- [ ] Tests nuevos para todo código nuevo
- [ ] Test de regresión para todo bug corregido
- [ ] Cobertura del módulo modificado >= 80%
- [ ] Sin sleep/delay en tests
- [ ] Tests pasan de forma determinista 3 veces consecutivas en CI
- [ ] Factories usadas para datos de prueba complejos
- [ ] Edge cases cubiertos: nulos, vacíos, límites
