Descripción General
Descripción General del Testing
Sección titulada «Descripción General del Testing»Orionis incluye un subsistema de testing completamente integrado que elimina la fricción de configurar un entorno de pruebas. En lugar de arrancar la aplicación manualmente o hacer mocks de los internos del framework, cada método de prueba se ejecuta dentro del contexto real de la aplicación — el contenedor de servicios está activo, los valores de configuración están cargados y todos los proveedores registrados han sido iniciados. Esto permite verificar el comportamiento exactamente como se ejecutará en producción.
El subsistema se compone de cuatro piezas principales:
| Componente | Responsabilidad |
|---|---|
TestCase | Clase base que debe extender cada prueba. Proporciona soporte async e inyección automática del contexto de la aplicación |
TestingEngine | Coordina el descubrimiento, filtrado, ejecución, salida en consola y almacenamiento en caché de resultados |
TestResult | Dataclass inmutable que contiene el resultado completo de un método de prueba individual |
TestStatus | Enumeración de cadena que representa los cuatro posibles resultados de una prueba |
Arquitectura
Sección titulada «Arquitectura»A nivel general, el flujo es:
- El
TestingEnginelee sus valores predeterminados de la configuración de la aplicación (config/testing.py). - Escanea el directorio configurado buscando archivos de prueba que coincidan con un patrón glob.
- Dentro de esos archivos, filtra los métodos individuales mediante un segundo patrón glob.
- El motor entrega la suite de pruebas resultante al
TestRunner, que ejecuta cada prueba dentro del contexto de la aplicación. - A medida que cada prueba finaliza, el
TestResultProcessorregistra unTestResultinmutable y lo renderiza en la consola según la verbosidad configurada. - Después de que todas las pruebas se completan, el runner muestra una tabla resumen con los conteos por estado y el tiempo total de ejecución.
- Si el almacenamiento en caché está habilitado, la lista completa de resultados se persiste como un archivo JSON con marca de tiempo.
Toda la ejecución es asíncrona — el motor descarga la ejecución de pruebas a un pool de hilos para que el bucle de eventos permanezca desbloqueado.
Configuración
Sección titulada «Configuración»Todos los valores predeterminados del testing se declaran en el dataclass BootstrapTesting en config/testing.py. El framework lee estos valores durante el arranque y los pasa al constructor del TestingEngine automáticamente.
from orionis.foundation.config.testing.entities.testing import Testingfrom orionis.foundation.config.testing.enums import VerbosityMode
class BootstrapTesting(Testing): verbosity: int | VerbosityMode = VerbosityMode.DETAILED fail_fast: bool = False start_dir: str = "tests" file_pattern: str = "test_*.py" method_pattern: str = "test*" cache_results: bool = FalseReferencia de Opciones
Sección titulada «Referencia de Opciones»| Opción | Tipo | Predeterminado | Descripción |
|---|---|---|---|
verbosity | int | VerbosityMode | DETAILED (2) | Controla la cantidad de detalle impreso por prueba. Consulte la sección Niveles de Verbosidad más adelante |
fail_fast | bool | False | Cuando es True, la ejecución se detiene inmediatamente después del primer fallo o error. Las pruebas restantes no se recopilan |
start_dir | str | "tests" | Ruta relativa al directorio raíz donde el motor comienza a buscar archivos de prueba |
file_pattern | str | "test_*.py" | Patrón glob aplicado contra los nombres de archivo en el directorio de inicio (y subdirectorios). Solo se cargan los archivos que coincidan |
method_pattern | str | "test*" | Patrón glob aplicado contra los nombres de método dentro de cada clase de prueba descubierta. Solo se ejecutan los métodos que coincidan |
cache_results | bool | False | Cuando es True, el motor serializa todos los objetos TestResult a un archivo JSON después de que la ejecución se complete |
Enumeración VerbosityMode
Sección titulada «Enumeración VerbosityMode»La enumeración VerbosityMode proporciona constantes nombradas para los tres niveles de verbosidad:
| Miembro | Valor | Comportamiento |
|---|---|---|
VerbosityMode.SILENT | 0 | Sin salida por prueba. Solo se muestra la tabla resumen después de que todas las pruebas finalicen |
VerbosityMode.MINIMAL | 1 | Una línea compacta por prueba: badge de estado, nombre completamente cualificado, relleno de puntos y tiempo de ejecución |
VerbosityMode.DETAILED | 2 | Un panel Rich por prueba con metadatos completos. Los fallos y errores incluyen el mensaje de excepción y el código fuente que rodea la línea causante |
Niveles de Verbosidad en Detalle
Sección titulada «Niveles de Verbosidad en Detalle»Silent — VerbosityMode.SILENT (0)
Sección titulada «Silent — VerbosityMode.SILENT (0)»El motor ejecuta todas las pruebas sin imprimir resultados individuales. Después de que la suite finaliza, solo aparece la tabla resumen. Este modo es útil en pipelines de CI donde solo importa la señal final de éxito/fallo.
Minimal — VerbosityMode.MINIMAL (1)
Sección titulada «Minimal — VerbosityMode.MINIMAL (1)»Cada prueba produce una sola línea que se ajusta al ancho del terminal:
PASSED • tests.unit.test_user.TestUserService.testCreatesUser ............. • ~ 0.003s FAILED • tests.unit.test_user.TestUserService.testBadLogic ................ • ~ 0.012s SKIPPED • tests.unit.test_user.TestUserService.testPending ................. • ~ 0.000sEl badge de estado tiene código de color: verde para pasado, magenta para fallido, rojo para error y amarillo para omitido. Si el nombre de la prueba es demasiado largo para el terminal, se trunca con puntos suspensivos para evitar el ajuste de línea.
Detailed — VerbosityMode.DETAILED (2)
Sección titulada «Detailed — VerbosityMode.DETAILED (2)»Cada prueba renderiza un panel Rich con borde que contiene:
- ID — el ID de objeto Python de la instancia de prueba
- Name — identificador de prueba completamente cualificado (
módulo.clase.método) - Class — nombre de la clase de prueba
- Method — nombre del método de prueba
- Module — ruta del módulo
- File path — ruta absoluta al archivo fuente
Para pruebas que pasan o son omitidas, el panel muestra los metadatos anteriores con el tiempo de ejecución en el subtítulo.
Para pruebas que fallan o tienen error, el panel adicionalmente incluye:
- El nombre de la clase de excepción y el mensaje de error
- Un fragmento del código fuente alrededor de la línea causante (típicamente 3–4 líneas), con la línea exacta resaltada usando un marcador
*|
Las pruebas con error muestran el icono 💥 en lugar de ❌ y usan un borde rojo.
Salida en Consola
Sección titulada «Salida en Consola»Independientemente del nivel de verbosidad, el motor siempre renderiza dos paneles que enmarcan la ejecución de pruebas:
Panel de Inicio
Sección titulada «Panel de Inicio»Se muestra antes de que se ejecute la primera prueba. Contiene:
| Campo | Contenido |
|---|---|
| Título | 🚀 Orionis TestSuite |
| Iniciado a las | Marca de tiempo actual en formato YYYY-MM-DD HH:MM:SS, usando la zona horaria configurada de la aplicación |
| PID | ID del proceso Python en ejecución |
| Política del bucle Reactor | Nombre de la política del bucle de eventos asyncio activa |
| Instrucción de parada | Recordatorio de Ctrl+C |
Tabla Resumen
Sección titulada «Tabla Resumen»Se muestra después de que la última prueba finaliza. Una vista tabular con cinco columnas:
| Total | Passed | Failed | Errored | Skipped |
|---|---|---|---|---|
| 42 | 40 | 1 | 0 | 1 |
El subtítulo de la tabla muestra el tiempo total de ejecución en segundos con precisión de milisegundos.
Almacenamiento de Resultados
Sección titulada «Almacenamiento de Resultados»Cuando cache_results se establece en True en config/testing.py, el motor escribe un archivo JSON después de cada ejecución. El archivo se almacena en:
storage/framework/cache/testing/<unix_timestamp>.jsonFormato del Archivo
Sección titulada «Formato del Archivo»El archivo JSON contiene un arreglo de objetos, uno por prueba ejecutada. Cada objeto incluye todos los campos del dataclass TestResult, con los valores de TestStatus serializados como su representación de cadena:
[ { "id": 140234821907, "name": "tests.test_user.TestUserService.testCreatesUser", "status": "PASSED", "execution_time": 0.003, "error_message": null, "traceback": null, "class_name": "TestUserService", "method": "testCreatesUser", "module": "tests.test_user", "file_path": "/app/tests/test_user.py", "doc_string": "Crear un usuario y persistirlo.", "exception": null, "line_no": null, "source_code": [] }]Casos de Uso
Sección titulada «Casos de Uso»- Integración CI/CD — parsear el archivo JSON en su pipeline para extraer conteos de éxito/fallo, identificar pruebas inestables o generar reportes
- Seguimiento histórico — acumular archivos con marca de tiempo a lo largo del tiempo para detectar regresiones de rendimiento o tasas crecientes de fallos
- Dashboards personalizados — alimentar los resultados a una herramienta de monitoreo o base de datos para visualización
La carpeta de caché se crea automáticamente si no existe. Cada ejecución produce un nuevo archivo; los archivos anteriores no se sobrescriben ni se rotan.
Estados de Prueba
Sección titulada «Estados de Prueba»Cada prueba ejecutada recibe exactamente uno de cuatro estados posibles, definidos en la enumeración TestStatus:
| Estado | Valor de Cadena | Cuándo se Asigna |
|---|---|---|
TestStatus.PASSED | "PASSED" | El método de prueba se completó sin lanzar ninguna excepción y todas las aserciones tuvieron éxito |
TestStatus.FAILED | "FAILED" | Se lanzó un AssertionError — típicamente de una llamada self.assert* que no se cumplió |
TestStatus.ERRORED | "ERRORED" | Se lanzó una excepción inesperada (cualquier cosa diferente de AssertionError) durante la ejecución |
TestStatus.SKIPPED | "SKIPPED" | La prueba fue marcada para omitirse, ya sea mediante @unittest.skip, @unittest.skipIf o @unittest.skipUnless |
TestStatus extiende StrEnum, lo que significa que cada miembro es simultáneamente una cadena. Se pueden comparar miembros contra cadenas planas, usarlos como claves de diccionario, serializarlos directamente a JSON o incluirlos en cadenas formateadas sin ninguna conversión:
from orionis.test.enums.status import TestStatus
status = TestStatus.PASSED
# Comparación con cadenastatus == "PASSED" # True
# Uso como clave de diccionariocounts = {TestStatus.PASSED: 0, TestStatus.FAILED: 0}
# Interpolación directa de cadenaprint(f"Resultado de la prueba: {status}") # "Resultado de la prueba: PASSED"La enumeración contiene exactamente cuatro miembros — no existen otros ni se agregarán.