qagent-pytest 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qagent_pytest-0.1.0/LICENSE +22 -0
- qagent_pytest-0.1.0/PKG-INFO +107 -0
- qagent_pytest-0.1.0/README.md +72 -0
- qagent_pytest-0.1.0/pyproject.toml +80 -0
- qagent_pytest-0.1.0/setup.cfg +4 -0
- qagent_pytest-0.1.0/src/qagent/__init__.py +14 -0
- qagent_pytest-0.1.0/src/qagent/__main__.py +6 -0
- qagent_pytest-0.1.0/src/qagent/agents/__init__.py +13 -0
- qagent_pytest-0.1.0/src/qagent/agents/generator.py +190 -0
- qagent_pytest-0.1.0/src/qagent/agents/planner.py +647 -0
- qagent_pytest-0.1.0/src/qagent/agents/reviewer.py +692 -0
- qagent_pytest-0.1.0/src/qagent/almacenamiento/__init__.py +25 -0
- qagent_pytest-0.1.0/src/qagent/almacenamiento/disco.py +76 -0
- qagent_pytest-0.1.0/src/qagent/almacenamiento/historial.py +80 -0
- qagent_pytest-0.1.0/src/qagent/almacenamiento/proyecto.py +699 -0
- qagent_pytest-0.1.0/src/qagent/almacenamiento/recientes.py +247 -0
- qagent_pytest-0.1.0/src/qagent/analysis/__init__.py +20 -0
- qagent_pytest-0.1.0/src/qagent/analysis/__main__.py +7 -0
- qagent_pytest-0.1.0/src/qagent/analysis/analyzer.py +313 -0
- qagent_pytest-0.1.0/src/qagent/analysis/laundering.py +596 -0
- qagent_pytest-0.1.0/src/qagent/analysis/rutas.py +698 -0
- qagent_pytest-0.1.0/src/qagent/app/__init__.py +0 -0
- qagent_pytest-0.1.0/src/qagent/app/bitacora.py +145 -0
- qagent_pytest-0.1.0/src/qagent/app/desktop.py +824 -0
- qagent_pytest-0.1.0/src/qagent/app/monitor_eventos.py +123 -0
- qagent_pytest-0.1.0/src/qagent/config/__init__.py +45 -0
- qagent_pytest-0.1.0/src/qagent/config/auditoria.py +127 -0
- qagent_pytest-0.1.0/src/qagent/config/carga.py +620 -0
- qagent_pytest-0.1.0/src/qagent/config/config.local.ejemplo.toml +35 -0
- qagent_pytest-0.1.0/src/qagent/config/config.toml +51 -0
- qagent_pytest-0.1.0/src/qagent/config/entorno.py +174 -0
- qagent_pytest-0.1.0/src/qagent/config/roles.py +561 -0
- qagent_pytest-0.1.0/src/qagent/contracts/__init__.py +91 -0
- qagent_pytest-0.1.0/src/qagent/contracts/ejemplos/generated_test.json +7 -0
- qagent_pytest-0.1.0/src/qagent/contracts/ejemplos/planner_contract.endpoint.json +72 -0
- qagent_pytest-0.1.0/src/qagent/contracts/ejemplos/planner_contract.v2.json +26 -0
- qagent_pytest-0.1.0/src/qagent/contracts/ejemplos/review_result.json +12 -0
- qagent_pytest-0.1.0/src/qagent/contracts/ejemplos/run_log.log.json +110 -0
- qagent_pytest-0.1.0/src/qagent/contracts/ejemplos/run_log.results.json +105 -0
- qagent_pytest-0.1.0/src/qagent/contracts/generated_test.schema.json +31 -0
- qagent_pytest-0.1.0/src/qagent/contracts/planner_contract.v2.schema.json +166 -0
- qagent_pytest-0.1.0/src/qagent/contracts/review_result.schema.json +89 -0
- qagent_pytest-0.1.0/src/qagent/contracts/run_log.schema.json +250 -0
- qagent_pytest-0.1.0/src/qagent/frontend/README.md +134 -0
- qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/LEEME.md +13 -0
- qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/LICENSE-Inter-OFL.txt +93 -0
- qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/LICENSE-JetBrainsMono-OFL.txt +93 -0
- qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/inter-latin-400-normal.woff2 +0 -0
- qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/inter-latin-500-normal.woff2 +0 -0
- qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/inter-latin-600-normal.woff2 +0 -0
- qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/inter-latin-700-normal.woff2 +0 -0
- qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/jetbrains-mono-latin-400-normal.woff2 +0 -0
- qagent_pytest-0.1.0/src/qagent/frontend/assets/fuentes/jetbrains-mono-latin-600-normal.woff2 +0 -0
- qagent_pytest-0.1.0/src/qagent/frontend/assets/logo/qagent-icono.svg +46 -0
- qagent_pytest-0.1.0/src/qagent/frontend/assets/logo/qagent-logo.svg +43 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/barra-corrida/barra-corrida.css +23 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/barra-corrida/barra-corrida.js +26 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/config-pruebas/config-pruebas.css +40 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/config-pruebas/config-pruebas.js +137 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/configuracion/configuracion.css +20 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/configuracion/configuracion.js +127 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/configuracion/modal-api.js +126 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/ejecucion/ejecucion.css +93 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/ejecucion/ejecucion.js +232 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/historial/historial.js +125 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/informe/informe.css +73 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/informe/informe.js +201 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/informe/modal-exportar.css +6 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/informe/modal-exportar.js +1072 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/inicio/inicio.css +21 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/inicio/inicio.js +90 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/inicio/modal-abrir.js +101 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/inicio/modal-clonar.js +89 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/monitor/monitor.css +33 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/monitor/monitor.js +223 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/sidebar/sidebar.css +25 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/sidebar/sidebar.js +130 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/vista-previa/vista-previa.css +26 -0
- qagent_pytest-0.1.0/src/qagent/frontend/componentes/vista-previa/vista-previa.js +373 -0
- qagent_pytest-0.1.0/src/qagent/frontend/css/componentes.css +57 -0
- qagent_pytest-0.1.0/src/qagent/frontend/css/fuentes.css +8 -0
- qagent_pytest-0.1.0/src/qagent/frontend/css/layout.css +33 -0
- qagent_pytest-0.1.0/src/qagent/frontend/css/tokens.css +93 -0
- qagent_pytest-0.1.0/src/qagent/frontend/index.html +90 -0
- qagent_pytest-0.1.0/src/qagent/frontend/js/cargador.js +12 -0
- qagent_pytest-0.1.0/src/qagent/frontend/js/datos-corrida.js +257 -0
- qagent_pytest-0.1.0/src/qagent/frontend/js/estado.js +81 -0
- qagent_pytest-0.1.0/src/qagent/frontend/js/iconos.js +21 -0
- qagent_pytest-0.1.0/src/qagent/frontend/js/main.js +12 -0
- qagent_pytest-0.1.0/src/qagent/frontend/js/navegacion.js +22 -0
- qagent_pytest-0.1.0/src/qagent/frontend/js/puente-python.js +16 -0
- qagent_pytest-0.1.0/src/qagent/frontend/js/utils.js +7 -0
- qagent_pytest-0.1.0/src/qagent/llm/__init__.py +36 -0
- qagent_pytest-0.1.0/src/qagent/llm/cliente.py +407 -0
- qagent_pytest-0.1.0/src/qagent/llm/fabrica.py +91 -0
- qagent_pytest-0.1.0/src/qagent/llm/fake.py +311 -0
- qagent_pytest-0.1.0/src/qagent/llm/ia_simulada.json +59 -0
- qagent_pytest-0.1.0/src/qagent/llm/modelos.py +35 -0
- qagent_pytest-0.1.0/src/qagent/llm/precios.py +92 -0
- qagent_pytest-0.1.0/src/qagent/llm/proveedores.py +380 -0
- qagent_pytest-0.1.0/src/qagent/llm/proveedores.toml +140 -0
- qagent_pytest-0.1.0/src/qagent/llm/tracker.py +164 -0
- qagent_pytest-0.1.0/src/qagent/llm/validar.py +126 -0
- qagent_pytest-0.1.0/src/qagent/orchestrator/__init__.py +42 -0
- qagent_pytest-0.1.0/src/qagent/orchestrator/corrida.py +460 -0
- qagent_pytest-0.1.0/src/qagent/orchestrator/estados.py +49 -0
- qagent_pytest-0.1.0/src/qagent/orchestrator/eventos.py +69 -0
- qagent_pytest-0.1.0/src/qagent/orchestrator/orquestador.py +480 -0
- qagent_pytest-0.1.0/src/qagent/orchestrator/puertos.py +39 -0
- qagent_pytest-0.1.0/src/qagent/orchestrator/regresion.py +311 -0
- qagent_pytest-0.1.0/src/qagent/proyectos/__init__.py +7 -0
- qagent_pytest-0.1.0/src/qagent/proyectos/clonador.py +283 -0
- qagent_pytest-0.1.0/src/qagent/proyectos/proyecto_local.py +111 -0
- qagent_pytest-0.1.0/src/qagent/proyectos/vista_previa.py +153 -0
- qagent_pytest-0.1.0/src/qagent/sandbox/Dockerfile +24 -0
- qagent_pytest-0.1.0/src/qagent/sandbox/__init__.py +46 -0
- qagent_pytest-0.1.0/src/qagent/sandbox/cliente.py +26 -0
- qagent_pytest-0.1.0/src/qagent/sandbox/ejecucion.py +306 -0
- qagent_pytest-0.1.0/src/qagent/sandbox/estado.py +71 -0
- qagent_pytest-0.1.0/src/qagent/sandbox/imagen.py +151 -0
- qagent_pytest-0.1.0/src/qagent/sandbox/modelos.py +93 -0
- qagent_pytest-0.1.0/src/qagent/sandbox/normalizacion.py +65 -0
- qagent_pytest-0.1.0/src/qagent/storage/__init__.py +23 -0
- qagent_pytest-0.1.0/src/qagent/storage/corrida_demo.py +672 -0
- qagent_pytest-0.1.0/src/qagent/storage/exportar_informe.py +1959 -0
- qagent_pytest-0.1.0/src/qagent/storage/registro.py +380 -0
- qagent_pytest-0.1.0/src/qagent/storage/vista_ejecucion.py +297 -0
- qagent_pytest-0.1.0/src/qagent/storage/vista_reporte.py +239 -0
- qagent_pytest-0.1.0/src/qagent/usuario.py +20 -0
- qagent_pytest-0.1.0/src/qagent_pytest.egg-info/PKG-INFO +107 -0
- qagent_pytest-0.1.0/src/qagent_pytest.egg-info/SOURCES.txt +137 -0
- qagent_pytest-0.1.0/src/qagent_pytest.egg-info/dependency_links.txt +1 -0
- qagent_pytest-0.1.0/src/qagent_pytest.egg-info/entry_points.txt +2 -0
- qagent_pytest-0.1.0/src/qagent_pytest.egg-info/requires.txt +15 -0
- qagent_pytest-0.1.0/src/qagent_pytest.egg-info/top_level.txt +1 -0
- qagent_pytest-0.1.0/tests/test_contracts.py +330 -0
- qagent_pytest-0.1.0/tests/test_distribucion.py +71 -0
- qagent_pytest-0.1.0/tests/test_laundering.py +590 -0
- qagent_pytest-0.1.0/tests/test_recursos_paquete.py +63 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Angel Mauricio Oncoy Patricio, Mateo Castillo Pezo,
|
|
4
|
+
Rodrigo Rodríguez Malca y Adrián Sanchez Vargas (equipo QAgent)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qagent-pytest
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Sistema multiagente que genera y valida pruebas pytest para código Python 3.10+
|
|
5
|
+
Author: Equipo QAgent - Taller Integrador I (UPAO)
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repositorio, https://github.com/AngelOncoy/QAgent
|
|
8
|
+
Project-URL: Problemas, https://github.com/AngelOncoy/QAgent/issues
|
|
9
|
+
Keywords: pytest,testing,unit tests,llm,multi-agent,test generation
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Natural Language :: Spanish
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Testing
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: pywebview>=5.0.0
|
|
24
|
+
Requires-Dist: docker>=7.1.0
|
|
25
|
+
Requires-Dist: requests>=2.31
|
|
26
|
+
Requires-Dist: jsonschema>=4.18
|
|
27
|
+
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
31
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
32
|
+
Provides-Extra: empaquetar
|
|
33
|
+
Requires-Dist: pyinstaller>=6.10; extra == "empaquetar"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# QAgent
|
|
37
|
+
|
|
38
|
+
**Genera y valida automáticamente pruebas unitarias (pytest) para tu código Python, con un equipo de tres agentes de IA.**
|
|
39
|
+
|
|
40
|
+
Abres tu proyecto, eliges qué probar y QAgent escribe las pruebas, las ejecuta en un entorno aislado y te entrega un informe con lo que pasó, lo que falló y por qué. No acepta pruebas «arregladas a la fuerza»: si una prueba solo pasa porque se le quitó o relajó una aserción, la rechaza.
|
|
41
|
+
|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
## Cómo funciona
|
|
45
|
+
|
|
46
|
+
| Agente | Qué hace |
|
|
47
|
+
|---|---|
|
|
48
|
+
| **Planner** | Lee tu código (funciones, firmas, docstrings) y decide qué casos hay que probar. |
|
|
49
|
+
| **Generator** | Escribe la prueba pytest de cada caso. |
|
|
50
|
+
| **Reviewer** | La ejecuta en un contenedor Docker sin internet ni acceso a tus archivos, revisa que las aserciones sean legítimas y pide hasta 3 intentos de corrección. |
|
|
51
|
+
|
|
52
|
+
Por cada prueba registra si pasó, la cobertura de líneas y ramas, cuántos intentos necesitó, los tokens que gastó cada agente y, en las funciones críticas, el Mutation Score.
|
|
53
|
+
|
|
54
|
+

|
|
55
|
+
|
|
56
|
+
## Instalación
|
|
57
|
+
|
|
58
|
+
### Windows: instalador (recomendado)
|
|
59
|
+
|
|
60
|
+
1. Descarga `QAgent-<versión>-instalador.exe` desde la [última versión publicada](https://github.com/AngelOncoy/QAgent/releases/latest).
|
|
61
|
+
2. Ejecútalo. No necesita permisos de administrador ni tener Python instalado.
|
|
62
|
+
3. Si Windows muestra «Windows protegió tu PC», elige **Más información → Ejecutar de todas formas** (el instalador aún no está firmado digitalmente).
|
|
63
|
+
|
|
64
|
+
### Con pip (si ya usas Python 3.10 o superior)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install qagent-pytest
|
|
68
|
+
qagent
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
En PyPI se llama `qagent-pytest` porque el nombre `qagent` ya lo usaba otro proyecto; el programa, el comando y la marca siguen siendo **QAgent**.
|
|
72
|
+
|
|
73
|
+
## Antes de la primera corrida
|
|
74
|
+
|
|
75
|
+
- **Docker Desktop**, abierto: las pruebas se ejecutan solo dentro de un contenedor aislado. La primera corrida prepara ese entorno sola (necesita internet y tarda unos minutos, una sola vez). [Descargar Docker Desktop](https://www.docker.com/products/docker-desktop/).
|
|
76
|
+
- **Claves de API** de los modelos de IA: se agregan en la pantalla **Configuración** del programa, una por agente.
|
|
77
|
+
- **Git**, si vas a abrir repositorios por su URL.
|
|
78
|
+
|
|
79
|
+
¿Quieres probarlo sin claves ni costo? Agrega la línea `QAGENT_FAKE_LLM=1` al archivo `.env` de la carpeta `.qagent` de tu usuario (`C:\Users\<tú>\.qagent\.env`): QAgent usará una IA simulada con respuestas grabadas.
|
|
80
|
+
|
|
81
|
+
Tus claves, tu configuración y la lista de proyectos recientes se guardan en esa carpeta `.qagent` de tu usuario, nunca junto al programa. Desinstalar QAgent no las borra.
|
|
82
|
+
|
|
83
|
+
## Primeros pasos
|
|
84
|
+
|
|
85
|
+
1. Abre **QAgent** desde el menú Inicio (o con el comando `qagent`).
|
|
86
|
+
2. En **Configuración**, agrega la API de cada agente.
|
|
87
|
+
3. En **Proyectos**, elige **Abrir proyecto** (una carpeta con código Python) o **Abrir repositorio** (una URL de Git).
|
|
88
|
+
4. Revisa la **Vista previa**, elige el perfil en **Configuración de pruebas** e inicia la corrida.
|
|
89
|
+
5. Sigue el avance en el **Monitor** y revisa el **Reporte final**; puedes exportarlo.
|
|
90
|
+
|
|
91
|
+
## Alcance
|
|
92
|
+
|
|
93
|
+
QAgent genera pruebas **unitarias** para código **Python 3.10+** y pruebas de endpoints FastAPI. Integración contra servicios reales, extremo a extremo, carga y estrés quedan fuera de esta versión.
|
|
94
|
+
|
|
95
|
+
## Para el equipo de desarrollo
|
|
96
|
+
|
|
97
|
+
Cómo preparar el entorno, ejecutar las pruebas, construir el instalador y publicar una versión: [`docs/desarrollo.md`](https://github.com/AngelOncoy/QAgent/blob/develop/docs/desarrollo.md). Las reglas del proyecto están en [`AGENTS.md`](https://github.com/AngelOncoy/QAgent/blob/develop/AGENTS.md) y las decisiones técnicas en [`docs/decisiones/`](https://github.com/AngelOncoy/QAgent/tree/develop/docs/decisiones).
|
|
98
|
+
|
|
99
|
+
## Equipo
|
|
100
|
+
|
|
101
|
+
Proyecto del curso Taller Integrador I, Ingeniería de Sistemas e Inteligencia Artificial, Universidad Privada Antenor Orrego (UPAO).
|
|
102
|
+
|
|
103
|
+
Castillo Pezo, Mateo · Oncoy Patricio, Angel · Rodríguez Malca, Rodrigo · Sanchez Vargas, Adrián
|
|
104
|
+
|
|
105
|
+
## Licencia
|
|
106
|
+
|
|
107
|
+
[MIT](https://github.com/AngelOncoy/QAgent/blob/develop/LICENSE).
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# QAgent
|
|
2
|
+
|
|
3
|
+
**Genera y valida automáticamente pruebas unitarias (pytest) para tu código Python, con un equipo de tres agentes de IA.**
|
|
4
|
+
|
|
5
|
+
Abres tu proyecto, eliges qué probar y QAgent escribe las pruebas, las ejecuta en un entorno aislado y te entrega un informe con lo que pasó, lo que falló y por qué. No acepta pruebas «arregladas a la fuerza»: si una prueba solo pasa porque se le quitó o relajó una aserción, la rechaza.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Cómo funciona
|
|
10
|
+
|
|
11
|
+
| Agente | Qué hace |
|
|
12
|
+
|---|---|
|
|
13
|
+
| **Planner** | Lee tu código (funciones, firmas, docstrings) y decide qué casos hay que probar. |
|
|
14
|
+
| **Generator** | Escribe la prueba pytest de cada caso. |
|
|
15
|
+
| **Reviewer** | La ejecuta en un contenedor Docker sin internet ni acceso a tus archivos, revisa que las aserciones sean legítimas y pide hasta 3 intentos de corrección. |
|
|
16
|
+
|
|
17
|
+
Por cada prueba registra si pasó, la cobertura de líneas y ramas, cuántos intentos necesitó, los tokens que gastó cada agente y, en las funciones críticas, el Mutation Score.
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
## Instalación
|
|
22
|
+
|
|
23
|
+
### Windows: instalador (recomendado)
|
|
24
|
+
|
|
25
|
+
1. Descarga `QAgent-<versión>-instalador.exe` desde la [última versión publicada](https://github.com/AngelOncoy/QAgent/releases/latest).
|
|
26
|
+
2. Ejecútalo. No necesita permisos de administrador ni tener Python instalado.
|
|
27
|
+
3. Si Windows muestra «Windows protegió tu PC», elige **Más información → Ejecutar de todas formas** (el instalador aún no está firmado digitalmente).
|
|
28
|
+
|
|
29
|
+
### Con pip (si ya usas Python 3.10 o superior)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install qagent-pytest
|
|
33
|
+
qagent
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
En PyPI se llama `qagent-pytest` porque el nombre `qagent` ya lo usaba otro proyecto; el programa, el comando y la marca siguen siendo **QAgent**.
|
|
37
|
+
|
|
38
|
+
## Antes de la primera corrida
|
|
39
|
+
|
|
40
|
+
- **Docker Desktop**, abierto: las pruebas se ejecutan solo dentro de un contenedor aislado. La primera corrida prepara ese entorno sola (necesita internet y tarda unos minutos, una sola vez). [Descargar Docker Desktop](https://www.docker.com/products/docker-desktop/).
|
|
41
|
+
- **Claves de API** de los modelos de IA: se agregan en la pantalla **Configuración** del programa, una por agente.
|
|
42
|
+
- **Git**, si vas a abrir repositorios por su URL.
|
|
43
|
+
|
|
44
|
+
¿Quieres probarlo sin claves ni costo? Agrega la línea `QAGENT_FAKE_LLM=1` al archivo `.env` de la carpeta `.qagent` de tu usuario (`C:\Users\<tú>\.qagent\.env`): QAgent usará una IA simulada con respuestas grabadas.
|
|
45
|
+
|
|
46
|
+
Tus claves, tu configuración y la lista de proyectos recientes se guardan en esa carpeta `.qagent` de tu usuario, nunca junto al programa. Desinstalar QAgent no las borra.
|
|
47
|
+
|
|
48
|
+
## Primeros pasos
|
|
49
|
+
|
|
50
|
+
1. Abre **QAgent** desde el menú Inicio (o con el comando `qagent`).
|
|
51
|
+
2. En **Configuración**, agrega la API de cada agente.
|
|
52
|
+
3. En **Proyectos**, elige **Abrir proyecto** (una carpeta con código Python) o **Abrir repositorio** (una URL de Git).
|
|
53
|
+
4. Revisa la **Vista previa**, elige el perfil en **Configuración de pruebas** e inicia la corrida.
|
|
54
|
+
5. Sigue el avance en el **Monitor** y revisa el **Reporte final**; puedes exportarlo.
|
|
55
|
+
|
|
56
|
+
## Alcance
|
|
57
|
+
|
|
58
|
+
QAgent genera pruebas **unitarias** para código **Python 3.10+** y pruebas de endpoints FastAPI. Integración contra servicios reales, extremo a extremo, carga y estrés quedan fuera de esta versión.
|
|
59
|
+
|
|
60
|
+
## Para el equipo de desarrollo
|
|
61
|
+
|
|
62
|
+
Cómo preparar el entorno, ejecutar las pruebas, construir el instalador y publicar una versión: [`docs/desarrollo.md`](https://github.com/AngelOncoy/QAgent/blob/develop/docs/desarrollo.md). Las reglas del proyecto están en [`AGENTS.md`](https://github.com/AngelOncoy/QAgent/blob/develop/AGENTS.md) y las decisiones técnicas en [`docs/decisiones/`](https://github.com/AngelOncoy/QAgent/tree/develop/docs/decisiones).
|
|
63
|
+
|
|
64
|
+
## Equipo
|
|
65
|
+
|
|
66
|
+
Proyecto del curso Taller Integrador I, Ingeniería de Sistemas e Inteligencia Artificial, Universidad Privada Antenor Orrego (UPAO).
|
|
67
|
+
|
|
68
|
+
Castillo Pezo, Mateo · Oncoy Patricio, Angel · Rodríguez Malca, Rodrigo · Sanchez Vargas, Adrián
|
|
69
|
+
|
|
70
|
+
## Licencia
|
|
71
|
+
|
|
72
|
+
[MIT](https://github.com/AngelOncoy/QAgent/blob/develop/LICENSE).
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"] # 77+: licencia como expresión SPDX (license = "MIT")
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
# Nombre en PyPI (`pip install qagent-pytest`): «qagent» ya era de otro proyecto. El
|
|
7
|
+
# producto, el comando `qagent` y el paquete `import qagent` no cambian (ADR-005).
|
|
8
|
+
name = "qagent-pytest"
|
|
9
|
+
version = "0.1.0"
|
|
10
|
+
description = "Sistema multiagente que genera y valida pruebas pytest para código Python 3.10+"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
authors = [{ name = "Equipo QAgent - Taller Integrador I (UPAO)" }]
|
|
16
|
+
keywords = ["pytest", "testing", "unit tests", "llm", "multi-agent", "test generation"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Environment :: Win32 (MS Windows)",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Natural Language :: Spanish",
|
|
22
|
+
"Operating System :: Microsoft :: Windows",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Topic :: Software Development :: Testing",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"pywebview>=5.0.0",
|
|
31
|
+
"docker>=7.1.0",
|
|
32
|
+
"requests>=2.31", # se usa directamente en sandbox/ejecucion.py
|
|
33
|
+
"jsonschema>=4.18",
|
|
34
|
+
"tomli>=2.0; python_version < '3.11'",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = [
|
|
39
|
+
"pytest>=8.0.0",
|
|
40
|
+
"ruff>=0.4.0",
|
|
41
|
+
"build>=1.2", # scripts/verificar_paquete.py construye el wheel y el sdist
|
|
42
|
+
]
|
|
43
|
+
# Solo para construir el ejecutable de Windows (scripts/construir_windows.py).
|
|
44
|
+
empaquetar = [
|
|
45
|
+
"pyinstaller>=6.10",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Repositorio = "https://github.com/AngelOncoy/QAgent"
|
|
50
|
+
Problemas = "https://github.com/AngelOncoy/QAgent/issues"
|
|
51
|
+
|
|
52
|
+
[project.scripts]
|
|
53
|
+
qagent = "qagent.app.desktop:ejecutar"
|
|
54
|
+
|
|
55
|
+
# Producto, comando y paquete se llaman igual: QAgent / `qagent` (antes `pyagent`).
|
|
56
|
+
[tool.setuptools.packages.find]
|
|
57
|
+
where = ["src"]
|
|
58
|
+
# Se descubren también las carpetas sin __init__.py (frontend/, contracts/ejemplos/):
|
|
59
|
+
# así setuptools no avisa de paquetes «ambiguos» y sus archivos se instalan.
|
|
60
|
+
|
|
61
|
+
# Recursos que la app necesita instalada (TA-06): sin ellos `pip install qagent` abre sin interfaz.
|
|
62
|
+
[tool.setuptools.package-data]
|
|
63
|
+
"qagent.frontend" = ["**/*"]
|
|
64
|
+
"qagent.config" = ["config.toml", "config.local.ejemplo.toml"]
|
|
65
|
+
"qagent.contracts" = ["*.schema.json", "ejemplos/*.json"]
|
|
66
|
+
"qagent.sandbox" = ["Dockerfile"]
|
|
67
|
+
"qagent.llm" = ["proveedores.toml", "ia_simulada.json"]
|
|
68
|
+
|
|
69
|
+
[tool.pytest.ini_options]
|
|
70
|
+
# src/ para importar `qagent`; la raíz para importar helpers de las pruebas (`tests.orchestrator.fakes`).
|
|
71
|
+
pythonpath = ["src", "."]
|
|
72
|
+
testpaths = ["tests"]
|
|
73
|
+
markers = [
|
|
74
|
+
'docker: prueba de integración que necesita Docker Desktop iniciado (omitir con -m "not docker")',
|
|
75
|
+
'red: prueba de integración que necesita internet (omitir con -m "not red")',
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[tool.ruff]
|
|
79
|
+
# bench/ y bench_api/ son bancos con bugs sembrados a propósito: no se reformatea ni se "arregla".
|
|
80
|
+
extend-exclude = ["bench", "bench_api"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""QAgent: sistema multiagente que genera y valida pruebas pytest para código Python."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError
|
|
4
|
+
from importlib.metadata import version as _version
|
|
5
|
+
|
|
6
|
+
#: Nombre del paquete en PyPI (`pip install qagent-pytest`). El producto y el `import`
|
|
7
|
+
#: siguen siendo QAgent / `qagent`; «qagent» en PyPI es de otro proyecto (TA-06).
|
|
8
|
+
DISTRIBUCION = "qagent-pytest"
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
#: Versión instalada; la única fuente es `version` en pyproject.toml.
|
|
12
|
+
__version__ = _version(DISTRIBUCION)
|
|
13
|
+
except PackageNotFoundError: # ejecutado desde src/ sin instalar el paquete
|
|
14
|
+
__version__ = "0.0.0+desarrollo"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Agentes reales coordinados por el orquestador de QAgent."""
|
|
2
|
+
|
|
3
|
+
from qagent.agents.generator import ErrorRespuestaGenerator, GeneratorAgent
|
|
4
|
+
from qagent.agents.planner import ErrorRespuestaPlanner, PlannerAgent
|
|
5
|
+
from qagent.agents.reviewer import ReviewerAgent
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"ErrorRespuestaGenerator",
|
|
9
|
+
"ErrorRespuestaPlanner",
|
|
10
|
+
"GeneratorAgent",
|
|
11
|
+
"PlannerAgent",
|
|
12
|
+
"ReviewerAgent",
|
|
13
|
+
]
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Agente Generator: convierte un contrato del Planner en código pytest."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import PurePosixPath
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from qagent import contracts
|
|
11
|
+
from qagent.llm import ClienteLLM, TokenTracker
|
|
12
|
+
from qagent.llm.fake import MARCA_CONTRATO
|
|
13
|
+
|
|
14
|
+
#: Caracteres de feedback (salida de pytest) que se reenvían al modelo.
|
|
15
|
+
LARGO_MAXIMO_FEEDBACK = 2000
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ErrorRespuestaGenerator(ValueError):
|
|
19
|
+
"""El Generator devolvió una respuesta que no puede utilizarse."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, mensaje: str, *, codigo: str = "") -> None:
|
|
22
|
+
super().__init__(mensaje)
|
|
23
|
+
self.codigo = codigo
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GeneratorAgent:
|
|
27
|
+
"""Genera el test de un único objetivo a partir de su contrato."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
cliente: ClienteLLM,
|
|
32
|
+
tracker: TokenTracker,
|
|
33
|
+
*,
|
|
34
|
+
es_simulado: bool = False,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Inicializa el Generator con su cliente y registro de tokens."""
|
|
37
|
+
self.cliente = cliente
|
|
38
|
+
self.tracker = tracker
|
|
39
|
+
self.es_simulado = es_simulado
|
|
40
|
+
|
|
41
|
+
def generar(
|
|
42
|
+
self,
|
|
43
|
+
contrato: dict[str, Any],
|
|
44
|
+
intento: int,
|
|
45
|
+
feedback: str | None,
|
|
46
|
+
) -> dict[str, Any]:
|
|
47
|
+
"""Genera o corrige un test pytest."""
|
|
48
|
+
prompt = self._crear_prompt(contrato, intento, feedback)
|
|
49
|
+
respuesta = self.cliente.generar(prompt, rol="generator")
|
|
50
|
+
|
|
51
|
+
self.tracker.registrar(
|
|
52
|
+
agente="generator",
|
|
53
|
+
modelo=respuesta.modelo,
|
|
54
|
+
tokens_entrada=respuesta.tokens_entrada,
|
|
55
|
+
tokens_salida=respuesta.tokens_salida,
|
|
56
|
+
costo_usd=respuesta.costo_usd,
|
|
57
|
+
es_simulado=self.es_simulado,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
codigo = _quitar_cerca_python(respuesta.contenido)
|
|
61
|
+
if not codigo.strip():
|
|
62
|
+
raise ErrorRespuestaGenerator("El Generator devolvió código vacío.")
|
|
63
|
+
try:
|
|
64
|
+
ast.parse(codigo)
|
|
65
|
+
except (SyntaxError, ValueError) as error:
|
|
66
|
+
raise ErrorRespuestaGenerator(
|
|
67
|
+
f"El Generator devolvió código Python inválido: {type(error).__name__}: {error}",
|
|
68
|
+
codigo=codigo,
|
|
69
|
+
) from error
|
|
70
|
+
|
|
71
|
+
generado = {
|
|
72
|
+
"version": "1",
|
|
73
|
+
"objetivo": contrato["objetivo"],
|
|
74
|
+
"intento": intento,
|
|
75
|
+
"codigo": codigo,
|
|
76
|
+
"casos_cubiertos": [caso["id"] for caso in contrato["casos"]],
|
|
77
|
+
}
|
|
78
|
+
contracts.validar(contracts.GENERATED_TEST, generado)
|
|
79
|
+
return generado
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def _crear_prompt(
|
|
83
|
+
contrato: dict[str, Any],
|
|
84
|
+
intento: int,
|
|
85
|
+
feedback: str | None,
|
|
86
|
+
) -> str:
|
|
87
|
+
# Una sola línea: más corto (menos tokens) y la IA simulada lo puede leer.
|
|
88
|
+
contrato_json = json.dumps(contrato, ensure_ascii=False)
|
|
89
|
+
correccion = (
|
|
90
|
+
_recortar(feedback)
|
|
91
|
+
if feedback
|
|
92
|
+
else "No existe feedback porque este es el primer intento."
|
|
93
|
+
)
|
|
94
|
+
if contrato.get("tipo") == "endpoint":
|
|
95
|
+
reglas_objetivo = _reglas_endpoint(contrato)
|
|
96
|
+
else:
|
|
97
|
+
importacion = (
|
|
98
|
+
f"from {modulo_importable(contrato['modulo'])} import "
|
|
99
|
+
f"{contrato['objetivo']}"
|
|
100
|
+
)
|
|
101
|
+
reglas_objetivo = f"""
|
|
102
|
+
- Importa el objetivo exactamente así: {importacion}
|
|
103
|
+
- Llama directamente a la función con la entrada de cada caso.
|
|
104
|
+
- Para un caso con "excepcion" usa: with pytest.raises(<Excepcion>).
|
|
105
|
+
""".strip()
|
|
106
|
+
return f"""
|
|
107
|
+
Escribe un archivo pytest completo para el contrato indicado.
|
|
108
|
+
|
|
109
|
+
REGLAS OBLIGATORIAS:
|
|
110
|
+
{reglas_objetivo}
|
|
111
|
+
- Escribe una función test_<id> por cada caso del contrato.
|
|
112
|
+
- Usa exactamente los valores esperados del contrato (pytest.approx para float).
|
|
113
|
+
- No ejecutes el código para descubrir resultados.
|
|
114
|
+
- No elimines, cambies ni debilites las aserciones; sin try/except, skip ni xfail.
|
|
115
|
+
- Simula con mocks las funciones incluidas en llama_a.
|
|
116
|
+
- Devuelve únicamente código Python, sin explicaciones.
|
|
117
|
+
- Este es el intento {intento}.
|
|
118
|
+
|
|
119
|
+
FEEDBACK DEL INTENTO ANTERIOR:
|
|
120
|
+
{correccion}
|
|
121
|
+
|
|
122
|
+
{MARCA_CONTRATO} {contrato_json}
|
|
123
|
+
""".strip()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _reglas_endpoint(contrato: dict[str, Any]) -> str:
|
|
127
|
+
"""Reglas de generación para un endpoint FastAPI descrito por el contrato."""
|
|
128
|
+
modulo = modulo_importable(contrato["modulo"])
|
|
129
|
+
aplicacion = contrato["aplicacion"]
|
|
130
|
+
dependencias = contrato.get("dependencias", [])
|
|
131
|
+
simbolos = [aplicacion]
|
|
132
|
+
for dependencia in dependencias:
|
|
133
|
+
simbolos.extend([dependencia["funcion"], dependencia["reemplazo"]])
|
|
134
|
+
importacion = f"from {modulo} import {', '.join(dict.fromkeys(simbolos))}"
|
|
135
|
+
detalle_overrides = (
|
|
136
|
+
json.dumps(dependencias, ensure_ascii=False)
|
|
137
|
+
if dependencias
|
|
138
|
+
else "[] (este endpoint no declara Depends)"
|
|
139
|
+
)
|
|
140
|
+
return f"""
|
|
141
|
+
- Usa fastapi.testclient.TestClient para realizar una petición HTTP real en memoria.
|
|
142
|
+
- Importa la aplicación y los símbolos de override exactamente así: {importacion}
|
|
143
|
+
- Construye TestClient({aplicacion}, raise_server_exceptions=False).
|
|
144
|
+
- Usa client.request con método, ruta, query, json y headers del contrato.
|
|
145
|
+
- Comprueba siempre response.status_code.
|
|
146
|
+
- Si valor_esperado trae "json", compara response.json() completo.
|
|
147
|
+
- Si trae "json_contiene", comprueba cada clave y valor indicados.
|
|
148
|
+
- Dependencias a reemplazar: {detalle_overrides}
|
|
149
|
+
- Para cada dependencia asigna {aplicacion}.dependency_overrides[funcion] = reemplazo.
|
|
150
|
+
- Si una entrada trae "preparacion", instancia el reemplazo una vez en la fixture,
|
|
151
|
+
ejecuta allí cada método/modelo/argumentos declarado y usa un lambda que devuelva
|
|
152
|
+
esa misma instancia durante el test.
|
|
153
|
+
- Usa una fixture con try/finally: guarda una copia de los overrides anteriores y,
|
|
154
|
+
al terminar cada test, limpia el diccionario y restaura exactamente esa copia.
|
|
155
|
+
- Nunca reemplaces el manejador de la ruta ni modelos Pydantic.
|
|
156
|
+
""".strip()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def modulo_importable(modulo: str) -> str:
|
|
160
|
+
"""Ruta del módulo relativa al proyecto -> nombre importable.
|
|
161
|
+
|
|
162
|
+
En el sandbox `PYTHONPATH` es la raíz del proyecto, así que
|
|
163
|
+
`bench/banco_mvp.py` se importa como `bench.banco_mvp`.
|
|
164
|
+
"""
|
|
165
|
+
return (
|
|
166
|
+
PurePosixPath(modulo.replace("\\", "/"))
|
|
167
|
+
.with_suffix("")
|
|
168
|
+
.as_posix()
|
|
169
|
+
.replace("/", ".")
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _recortar(texto: str, maximo: int = LARGO_MAXIMO_FEEDBACK) -> str:
|
|
174
|
+
"""Deja solo el final del feedback (donde pytest pone el error) para ahorrar tokens."""
|
|
175
|
+
if len(texto) <= maximo:
|
|
176
|
+
return texto
|
|
177
|
+
return "[…recortado…]\n" + texto[-maximo:]
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _quitar_cerca_python(contenido: str) -> str:
|
|
181
|
+
"""Elimina una cerca Markdown ```python si el modelo la agregó."""
|
|
182
|
+
texto = contenido.strip()
|
|
183
|
+
if texto.startswith("```"):
|
|
184
|
+
lineas = texto.splitlines()
|
|
185
|
+
if lineas and lineas[0].strip().startswith("```"):
|
|
186
|
+
lineas.pop(0)
|
|
187
|
+
if lineas and lineas[-1].strip() == "```":
|
|
188
|
+
lineas.pop()
|
|
189
|
+
texto = "\n".join(lineas).strip()
|
|
190
|
+
return texto + "\n" if texto else ""
|