483 lines
10 KiB
Markdown
483 lines
10 KiB
Markdown
# Guia de Testing - CBCFacil
|
|
|
|
Esta guia describe como ejecutar y escribir tests para CBCFacil.
|
|
|
|
## Estructura de Tests
|
|
|
|
```
|
|
tests/
|
|
├── conftest.py # Fixtures compartidos
|
|
├── test_config.py # Tests de configuracion
|
|
├── test_storage.py # Tests de almacenamiento
|
|
├── test_webdav.py # Tests de WebDAV
|
|
├── test_processors.py # Tests de procesadores
|
|
├── test_ai_providers.py # Tests de AI providers
|
|
├── test_vram_manager.py # Tests de VRAM manager
|
|
└── test_main_integration.py # Tests de integracion
|
|
```
|
|
|
|
## Instalacion de Dependencias de Test
|
|
|
|
```bash
|
|
# Activar entorno virtual
|
|
source .venv/bin/activate
|
|
|
|
# Instalar dependencias de desarrollo
|
|
pip install -r requirements-dev.txt
|
|
|
|
# Verificar instalacion
|
|
pytest --version
|
|
```
|
|
|
|
## Ejecutar Tests
|
|
|
|
### Todos los Tests
|
|
|
|
```bash
|
|
# Ejecutar todos los tests
|
|
pytest tests/
|
|
|
|
# Con output detallado
|
|
pytest tests/ -v
|
|
```
|
|
|
|
### Tests Especificos
|
|
|
|
```bash
|
|
# Tests de configuracion
|
|
pytest tests/test_config.py -v
|
|
|
|
# Tests de almacenamiento
|
|
pytest tests/test_storage.py -v
|
|
|
|
# Tests de WebDAV
|
|
pytest tests/test_webdav.py -v
|
|
|
|
# Tests de procesadores
|
|
pytest tests/test_processors.py -v
|
|
|
|
# Tests de AI providers
|
|
pytest tests/test_ai_providers.py -v
|
|
|
|
# Tests de VRAM manager
|
|
pytest tests/test_vram_manager.py -v
|
|
|
|
# Tests de integracion
|
|
pytest tests/test_main_integration.py -v
|
|
```
|
|
|
|
### Tests con Coverage
|
|
|
|
```bash
|
|
# Coverage basico
|
|
pytest tests/ --cov=cbcfacil
|
|
|
|
# Coverage con reporte HTML
|
|
pytest tests/ --cov=cbcfacil --cov-report=html
|
|
|
|
# Coverage con reporte term-missing
|
|
pytest tests/ --cov=cbcfacil --cov-report=term-missing
|
|
|
|
# Coverage por modulo
|
|
pytest tests/ --cov=cbcfacil --cov-report=term-missing --cov-report=annotate
|
|
```
|
|
|
|
### Tests en Modo Watch
|
|
|
|
```bash
|
|
# Recargar automaticamente al detectar cambios
|
|
pytest-watch tests/
|
|
```
|
|
|
|
### Tests Parallelos
|
|
|
|
```bash
|
|
# Ejecutar tests en paralelo
|
|
pytest tests/ -n auto
|
|
|
|
# Con numero fijo de workers
|
|
pytest tests/ -n 4
|
|
```
|
|
|
|
## Escribir Nuevos Tests
|
|
|
|
### Estructura Basica
|
|
|
|
```python
|
|
# tests/test_ejemplo.py
|
|
import pytest
|
|
from pathlib import Path
|
|
|
|
class TestEjemplo:
|
|
"""Clase de tests para un modulo"""
|
|
|
|
def setup_method(self):
|
|
"""Setup antes de cada test"""
|
|
pass
|
|
|
|
def teardown_method(self):
|
|
"""Cleanup despues de cada test"""
|
|
pass
|
|
|
|
def test_funcion_basica(self):
|
|
"""Test de una funcion basica"""
|
|
# Arrange
|
|
input_value = "test"
|
|
|
|
# Act
|
|
result = mi_funcion(input_value)
|
|
|
|
# Assert
|
|
assert result is not None
|
|
assert result == "expected"
|
|
```
|
|
|
|
### Usar Fixtures
|
|
|
|
```python
|
|
# tests/conftest.py
|
|
import pytest
|
|
from pathlib import Path
|
|
|
|
@pytest.fixture
|
|
def temp_directory(tmp_path):
|
|
"""Fixture para directorio temporal"""
|
|
dir_path = tmp_path / "test_files"
|
|
dir_path.mkdir()
|
|
return dir_path
|
|
|
|
@pytest.fixture
|
|
def mock_settings():
|
|
"""Fixture con settings de prueba"""
|
|
class MockSettings:
|
|
NEXTCLOUD_URL = "https://test.example.com"
|
|
NEXTCLOUD_USER = "test_user"
|
|
NEXTCLOUD_PASSWORD = "test_pass"
|
|
return MockSettings()
|
|
|
|
# En tu test
|
|
def test_con_fixture(temp_directory, mock_settings):
|
|
"""Test usando fixtures"""
|
|
assert temp_directory.exists()
|
|
assert mock_settings.NEXTCLOUD_URL == "https://test.example.com"
|
|
```
|
|
|
|
### Tests de Configuracion
|
|
|
|
```python
|
|
# tests/test_config.py
|
|
import pytest
|
|
from config import settings
|
|
|
|
class TestSettings:
|
|
"""Tests para configuracion"""
|
|
|
|
def test_has_webdav_config_true(self):
|
|
"""Test con WebDAV configurado"""
|
|
# Verificar que las properties funcionan
|
|
assert hasattr(settings, 'has_webdav_config')
|
|
assert hasattr(settings, 'has_ai_config')
|
|
|
|
def test_processed_files_path(self):
|
|
"""Test del path de archivos procesados"""
|
|
path = settings.processed_files_path
|
|
assert isinstance(path, Path)
|
|
assert path.suffix == ".txt"
|
|
```
|
|
|
|
### Tests de WebDAV
|
|
|
|
```python
|
|
# tests/test_webdav.py
|
|
import pytest
|
|
from unittest.mock import Mock, patch
|
|
|
|
class TestWebDAVService:
|
|
"""Tests para WebDAV Service"""
|
|
|
|
@pytest.fixture
|
|
def webdav_service(self):
|
|
"""Crear instancia del servicio"""
|
|
from services.webdav_service import webdav_service
|
|
return webdav_service
|
|
|
|
def test_list_remote_path(self, webdav_service):
|
|
"""Test de listado de archivos remotos"""
|
|
# Mock del cliente WebDAV
|
|
with patch('services.webdav_service.WebDAVClient') as mock_client:
|
|
mock_instance = Mock()
|
|
mock_instance.list.return_value = ['file1.pdf', 'file2.mp3']
|
|
mock_client.return_value = mock_instance
|
|
|
|
# Inicializar servicio
|
|
webdav_service.initialize()
|
|
|
|
# Test
|
|
files = webdav_service.list("TestFolder")
|
|
assert len(files) == 2
|
|
assert "file1.pdf" in files
|
|
```
|
|
|
|
### Tests de Procesadores
|
|
|
|
```python
|
|
# tests/test_processors.py
|
|
import pytest
|
|
from unittest.mock import Mock, patch
|
|
|
|
class TestAudioProcessor:
|
|
"""Tests para Audio Processor"""
|
|
|
|
@pytest.fixture
|
|
def processor(self):
|
|
"""Crear procesador"""
|
|
from processors.audio_processor import AudioProcessor
|
|
return AudioProcessor()
|
|
|
|
def test_process_audio_file(self, processor, tmp_path):
|
|
"""Test de procesamiento de audio"""
|
|
# Crear archivo de prueba
|
|
audio_file = tmp_path / "test.mp3"
|
|
audio_file.write_bytes(b"fake audio content")
|
|
|
|
# Mock de Whisper
|
|
with patch('processors.audio_processor.whisper') as mock_whisper:
|
|
mock_whisper.load_model.return_value.transcribe.return_value = {
|
|
"text": "Texto transcrito de prueba"
|
|
}
|
|
|
|
# Ejecutar
|
|
result = processor.process(str(audio_file))
|
|
|
|
# Verificar
|
|
assert result is not None
|
|
```
|
|
|
|
### Tests de AI Providers
|
|
|
|
```python
|
|
# tests/test_ai_providers.py
|
|
import pytest
|
|
from unittest.mock import Mock, patch
|
|
|
|
class TestClaudeProvider:
|
|
"""Tests para Claude Provider"""
|
|
|
|
def test_summarize_text(self):
|
|
"""Test de resumen con Claude"""
|
|
from services.ai.claude_provider import ClaudeProvider
|
|
|
|
provider = ClaudeProvider()
|
|
test_text = "Texto largo para resumir..."
|
|
|
|
# Mock de la llamada API
|
|
with patch.object(provider, '_call_api') as mock_call:
|
|
mock_call.return_value = "Texto resumido"
|
|
|
|
result = provider.summarize(test_text)
|
|
|
|
assert result == "Texto resumido"
|
|
mock_call.assert_called_once()
|
|
```
|
|
|
|
### Tests de Integracion
|
|
|
|
```python
|
|
# tests/test_main_integration.py
|
|
import pytest
|
|
from unittest.mock import patch
|
|
|
|
class TestMainIntegration:
|
|
"""Tests de integracion del main"""
|
|
|
|
def test_main_loop_no_files(self):
|
|
"""Test del loop principal sin archivos nuevos"""
|
|
with patch('main.webdav_service') as mock_webdav:
|
|
with patch('main.processed_registry') as mock_registry:
|
|
mock_webdav.list.return_value = []
|
|
mock_registry.is_processed.return_value = True
|
|
|
|
# El loop no debe procesar nada
|
|
# Verificar que no se llama a procesadores
|
|
```
|
|
|
|
## Configuracion de pytest
|
|
|
|
```ini
|
|
# pytest.ini o pyproject.toml
|
|
[tool.pytest.ini_options]
|
|
testpaths = ["tests"]
|
|
python_files = ["test_*.py"]
|
|
python_classes = ["Test*"]
|
|
python_functions = ["test_*"]
|
|
addopts = [
|
|
"-v",
|
|
"--tb=short",
|
|
"--strict-markers",
|
|
]
|
|
filterwarnings = [
|
|
"ignore::DeprecationWarning",
|
|
]
|
|
```
|
|
|
|
## Mejores Practicas
|
|
|
|
### 1. Nombrado de Tests
|
|
|
|
```python
|
|
# BIEN
|
|
def test_webdav_service_list_returns_files():
|
|
...
|
|
|
|
def test_processed_registry_is_processed_true_for_processed_file():
|
|
...
|
|
|
|
# MAL
|
|
def test_list():
|
|
...
|
|
|
|
def test_check():
|
|
...
|
|
```
|
|
|
|
### 2. Estructura AAA
|
|
|
|
```python
|
|
def test_ejemplo_aaa():
|
|
# Arrange
|
|
input_data = {"key": "value"}
|
|
expected = "result"
|
|
|
|
# Act
|
|
actual = function_under_test(input_data)
|
|
|
|
# Assert
|
|
assert actual == expected
|
|
```
|
|
|
|
### 3. Tests Aislados
|
|
|
|
```python
|
|
# Cada test debe ser independiente
|
|
def test_independent():
|
|
# No depender de estado de otros tests
|
|
# Usar fixtures para setup/cleanup
|
|
pass
|
|
```
|
|
|
|
### 4. Evitar TestLego
|
|
|
|
```python
|
|
# BIEN - Test del comportamiento, no la implementacion
|
|
def test_registry_returns_true_for_processed_file():
|
|
registry = ProcessedRegistry()
|
|
registry.save("file.txt")
|
|
assert registry.is_processed("file.txt") is True
|
|
|
|
# MAL - Test de implementacion
|
|
def test_registry_uses_set_internally():
|
|
# No testar detalles de implementacion
|
|
registry = ProcessedRegistry()
|
|
assert hasattr(registry, '_processed_files')
|
|
```
|
|
|
|
### 5. Mocks Appropriados
|
|
|
|
```python
|
|
# Usar mocks para dependencias externas
|
|
from unittest.mock import Mock, patch, MagicMock
|
|
|
|
def test_with_mocked_api():
|
|
with patch('requests.get') as mock_get:
|
|
mock_response = Mock()
|
|
mock_response.json.return_value = {"key": "value"}
|
|
mock_get.return_value = mock_response
|
|
|
|
result = my_api_function()
|
|
|
|
assert result == {"key": "value"}
|
|
```
|
|
|
|
## Coverage Objetivo
|
|
|
|
| Componente | Coverage Minimo |
|
|
|------------|-----------------|
|
|
| config/ | 90% |
|
|
| core/ | 90% |
|
|
| services/ | 70% |
|
|
| processors/ | 60% |
|
|
| storage/ | 90% |
|
|
| api/ | 80% |
|
|
|
|
## Integracion con CI/CD
|
|
|
|
```yaml
|
|
# .github/workflows/tests.yml
|
|
name: Tests
|
|
|
|
on: [push, pull_request]
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.11'
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
pip install -r requirements.txt
|
|
pip install -r requirements-dev.txt
|
|
|
|
- name: Run tests
|
|
run: |
|
|
pytest tests/ --cov=cbcfacil --cov-report=xml
|
|
|
|
- name: Upload coverage
|
|
uses: codecov/codecov-action@v3
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Tests Fallan por Imports
|
|
|
|
```bash
|
|
# Verificar que el venv esta activado
|
|
source .venv/bin/activate
|
|
|
|
# Reinstalar el paquete en modo desarrollo
|
|
pip install -e .
|
|
```
|
|
|
|
### Tests Muy Lentos
|
|
|
|
```bash
|
|
# Ejecutar en paralelo
|
|
pytest tests/ -n auto
|
|
|
|
# O ejecutar solo tests rapidos
|
|
pytest tests/ -m "not slow"
|
|
```
|
|
|
|
### Memory Errors
|
|
|
|
```bash
|
|
# Reducir workers
|
|
pytest tests/ -n 2
|
|
|
|
# O ejecutar secuencial
|
|
pytest tests/ -n 0
|
|
```
|
|
|
|
## Recursos Adicionales
|
|
|
|
- [Documentacion de pytest](https://docs.pytest.org/)
|
|
- [Documentacion de unittest.mock](https://docs.python.org/3/library/unittest.mock.html)
|
|
- [pytest-cov](https://pytest-cov.readthedocs.io/)
|