Sprint 4 deliverables: - DATABASE-SCHEMA.md: ER diagrams for 12 schemas (~90 tables) - TESTING-STRATEGY.md: Testing pyramid (70/20/10), frameworks, CI/CD Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
14 KiB
TESTING-STRATEGY.md - Trading Platform
Version: 1.0.0 Fecha: 2026-01-30 Proyecto: trading-platform
Resumen Ejecutivo
Este documento define la estrategia de testing para Trading Platform, cubriendo todos los niveles de la pirámide de testing y consideraciones específicas para sistemas de trading con ML.
Niveles de Testing
Pirámide de Testing
┌─────────────┐
│ E2E │ 10%
│ Tests │
┌──┴─────────────┴──┐
│ Integration │ 20%
│ Tests │
┌──┴───────────────────┴──┐
│ Unit Tests │ 70%
└─────────────────────────┘
| Nivel | Porcentaje | Responsabilidad |
|---|---|---|
| Unit | 70% | Lógica de negocio aislada |
| Integration | 20% | Interacción entre módulos |
| E2E | 10% | Flujos críticos completos |
1. Unit Tests
1.1 Backend (TypeScript/Express)
Framework: Jest + ts-jest
Estructura:
apps/backend/src/
├── modules/
│ ├── auth/
│ │ ├── services/
│ │ │ ├── token.service.ts
│ │ │ └── __tests__/
│ │ │ └── token.service.spec.ts
│ │ └── ...
│ └── ...
Convenciones:
- Archivos:
*.spec.tsjunto al archivo fuente - Naming:
describe('ServiceName')>describe('methodName')>it('should...') - Mocks: Usar
jest.mock()para dependencias externas
Ejemplo:
// token.service.spec.ts
describe('TokenService', () => {
describe('generateAccessToken', () => {
it('should return a valid JWT token', () => {
const token = tokenService.generateAccessToken(mockUser);
expect(token).toMatch(/^eyJ/);
});
it('should include user id in payload', () => {
const token = tokenService.generateAccessToken(mockUser);
const decoded = jwt.decode(token);
expect(decoded.sub).toBe(mockUser.id);
});
});
});
Coverage mínimo: 80%
1.2 Frontend (React)
Framework: Vitest + React Testing Library
Estructura:
apps/frontend/src/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ └── Button.test.tsx
│ └── ...
├── hooks/
│ ├── useAuth.ts
│ └── __tests__/
│ └── useAuth.test.ts
Convenciones:
- Archivos:
*.test.tsxo*.test.ts - Testing Library: Preferir queries por rol y accesibilidad
- Avoid testing implementation details
Ejemplo:
// Button.test.tsx
describe('Button', () => {
it('should call onClick when clicked', async () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click me</Button>);
await userEvent.click(screen.getByRole('button', { name: /click me/i }));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
Coverage mínimo: 70%
1.3 ML Engine (Python)
Framework: pytest + pytest-cov
Estructura:
apps/ml-engine/
├── src/
│ ├── models/
│ │ └── range_predictor.py
│ └── ...
├── tests/
│ ├── unit/
│ │ ├── test_range_predictor.py
│ │ └── test_feature_engineering.py
│ └── ...
Convenciones:
- Archivos:
test_*.py - Fixtures: Usar
@pytest.fixturepara datos de prueba - Parametrize: Usar
@pytest.mark.parametrizepara múltiples casos
Ejemplo:
# test_range_predictor.py
class TestRangePredictor:
@pytest.fixture
def predictor(self):
return RangePredictor(symbol='XAUUSD')
def test_predict_returns_valid_range(self, predictor, sample_features):
result = predictor.predict(sample_features)
assert 'predicted_high' in result
assert 'predicted_low' in result
assert result['predicted_high'] > result['predicted_low']
@pytest.mark.parametrize('confidence_threshold', [0.5, 0.7, 0.9])
def test_filter_by_confidence(self, predictor, confidence_threshold):
# Test filtering logic
pass
Coverage mínimo: 75%
2. Integration Tests
2.1 API Integration Tests
Framework: Jest + Supertest
Objetivo: Verificar endpoints con base de datos real (test DB)
Setup:
// setup/test-db.ts
beforeAll(async () => {
await setupTestDatabase();
});
afterAll(async () => {
await teardownTestDatabase();
});
beforeEach(async () => {
await clearTables();
});
Ejemplo:
// auth.integration.spec.ts
describe('Auth API Integration', () => {
describe('POST /api/v1/auth/login', () => {
it('should return tokens for valid credentials', async () => {
// Arrange
await createTestUser({ email: 'test@example.com', password: 'Test123!' });
// Act
const response = await request(app)
.post('/api/v1/auth/login')
.send({ email: 'test@example.com', password: 'Test123!' });
// Assert
expect(response.status).toBe(200);
expect(response.body.data).toHaveProperty('accessToken');
expect(response.body.data).toHaveProperty('refreshToken');
});
});
});
2.2 Database Integration Tests
Objetivo: Verificar queries, triggers y funciones PL/pgSQL
// wallet.integration.spec.ts
describe('Wallet Transactions', () => {
it('should update balance after deposit', async () => {
const wallet = await createTestWallet({ balance: 100 });
await db.query(
'SELECT financial.process_transaction($1, $2, $3)',
[wallet.id, 'deposit', 50]
);
const updated = await getWallet(wallet.id);
expect(updated.balance).toBe(150);
});
});
2.3 ML Pipeline Integration Tests
Objetivo: Verificar pipeline completo de predicción
# test_prediction_pipeline.py
class TestPredictionPipeline:
def test_full_prediction_flow(self, db_session, redis_client):
# 1. Load features from feature store
features = load_features('XAUUSD', '2024-01-01')
# 2. Run prediction
prediction = run_prediction(features)
# 3. Verify stored in database
stored = db_session.query(Prediction).filter_by(
symbol='XAUUSD'
).first()
assert stored is not None
assert stored.predicted_high == prediction['high']
3. E2E Tests
3.1 Framework
Herramienta: Playwright
Estructura:
e2e/
├── tests/
│ ├── auth/
│ │ ├── login.spec.ts
│ │ └── register.spec.ts
│ ├── trading/
│ │ └── place-order.spec.ts
│ └── ...
├── fixtures/
│ └── test-data.ts
└── playwright.config.ts
3.2 Flujos Críticos a Testear
| Prioridad | Flujo | Descripción |
|---|---|---|
| P0 | Login/Logout | Autenticación completa |
| P0 | Registro + Verificación | Nuevo usuario |
| P0 | Depositar fondos | Wallet + Stripe |
| P1 | Abrir cuenta inversión | PAMM flow |
| P1 | Paper trading | Orden → Posición |
| P2 | Completar curso | Education flow |
| P2 | 2FA setup | Security flow |
3.3 Ejemplo E2E
// e2e/tests/auth/login.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Login Flow', () => {
test('user can login with valid credentials', async ({ page }) => {
// Navigate
await page.goto('/login');
// Fill form
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('Test123!');
// Submit
await page.getByRole('button', { name: /sign in/i }).click();
// Verify redirect to dashboard
await expect(page).toHaveURL('/dashboard');
await expect(page.getByText('Welcome back')).toBeVisible();
});
test('shows error for invalid credentials', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('wrong@example.com');
await page.getByLabel('Password').fill('wrongpassword');
await page.getByRole('button', { name: /sign in/i }).click();
await expect(page.getByText(/invalid credentials/i)).toBeVisible();
});
});
4. Testing Específico para Trading
4.1 Backtesting de Modelos ML
Objetivo: Validar performance de modelos con datos históricos
# tests/backtesting/test_model_performance.py
class TestModelBacktest:
def test_model_accuracy_oos(self):
"""Test Out-of-Sample accuracy"""
# Load 12 months of excluded data
oos_data = load_oos_data('2025-01', '2025-12')
# Run predictions
predictions = model.predict(oos_data.features)
# Calculate metrics
accuracy = calculate_directional_accuracy(
predictions,
oos_data.actual
)
# Minimum threshold
assert accuracy >= 0.55, f"OOS accuracy {accuracy} below threshold"
4.2 Paper Trading Tests
Objetivo: Simular operaciones sin riesgo
// tests/paper-trading/order-execution.spec.ts
describe('Paper Trading Order Execution', () => {
it('should execute market order at current price', async () => {
const account = await createPaperAccount({ balance: 10000 });
const order = await paperTradingService.placeOrder({
accountId: account.id,
symbol: 'XAUUSD',
side: 'BUY',
type: 'MARKET',
quantity: 0.1
});
expect(order.status).toBe('FILLED');
expect(order.filledPrice).toBeCloseTo(currentPrice, 2);
});
});
4.3 Risk Management Tests
// tests/risk/position-limits.spec.ts
describe('Position Limits', () => {
it('should reject order exceeding max position size', async () => {
const result = await riskService.validateOrder({
userId: testUser.id,
symbol: 'XAUUSD',
quantity: 100 // Exceeds limit
});
expect(result.allowed).toBe(false);
expect(result.reason).toContain('position limit');
});
});
5. Mocking Strategies
5.1 External Services
| Servicio | Estrategia |
|---|---|
| Stripe | Mock SDK responses |
| Binance API | Recorded fixtures |
| Redis | Redis-mock o real |
| LLM (Claude) | Canned responses |
5.2 Mock de Stripe
// __mocks__/stripe.ts
export const mockStripe = {
customers: {
create: jest.fn().mockResolvedValue({ id: 'cus_test123' }),
retrieve: jest.fn().mockResolvedValue({ id: 'cus_test123', email: 'test@example.com' })
},
subscriptions: {
create: jest.fn().mockResolvedValue({ id: 'sub_test123', status: 'active' })
}
};
jest.mock('stripe', () => ({
__esModule: true,
default: jest.fn(() => mockStripe)
}));
5.3 Mock de Market Data
# tests/fixtures/market_data.py
@pytest.fixture
def mock_binance_client(mocker):
mock = mocker.patch('binance.Client')
mock.return_value.get_klines.return_value = [
[1704067200000, "2050.00", "2055.00", "2048.00", "2052.00", "1000"],
[1704067260000, "2052.00", "2058.00", "2051.00", "2056.00", "1200"],
]
return mock
6. CI/CD Integration
6.1 GitHub Actions Workflow
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test:unit
- name: Upload coverage
uses: codecov/codecov-action@v3
integration-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: trading_test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports:
- 5432:5432
redis:
image: redis:7
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Run integration tests
run: npm run test:integration
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run E2E tests
run: npm run test:e2e
6.2 Pre-commit Hooks
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: test-affected
name: Run affected tests
entry: npm run test:affected
language: system
pass_filenames: false
7. Test Data Management
7.1 Factories
// tests/factories/user.factory.ts
export const userFactory = {
build: (overrides = {}) => ({
id: faker.string.uuid(),
email: faker.internet.email(),
passwordHash: bcrypt.hashSync('Test123!', 10),
status: 'active',
role: 'user',
...overrides
}),
create: async (overrides = {}) => {
const user = userFactory.build(overrides);
await db.query('INSERT INTO auth.users ...', [user]);
return user;
}
};
7.2 Fixtures para ML
# tests/conftest.py
@pytest.fixture(scope='session')
def sample_ohlcv_data():
return pd.read_parquet('tests/fixtures/xauusd_sample.parquet')
@pytest.fixture(scope='session')
def trained_model():
return joblib.load('tests/fixtures/model_v1.joblib')
8. Comandos de Ejecución
Backend
# Unit tests
npm run test:unit
# Integration tests
npm run test:integration
# Coverage
npm run test:coverage
Frontend
# Unit tests
npm run test
# Watch mode
npm run test:watch
# Coverage
npm run test:coverage
ML Engine
# All tests
pytest
# Unit only
pytest tests/unit/
# With coverage
pytest --cov=src --cov-report=html
E2E
# Headless
npm run test:e2e
# UI mode
npm run test:e2e:ui
# Specific browser
npx playwright test --project=chromium
9. Métricas y Umbrales
| Métrica | Umbral | Medición |
|---|---|---|
| Unit Test Coverage | ≥ 70% | Codecov |
| Integration Test Coverage | ≥ 50% | Codecov |
| E2E Critical Paths | 100% pass | Playwright |
| ML Model Accuracy (OOS) | ≥ 55% | Custom |
| Test Execution Time | < 10 min | CI |
10. Próximos Pasos
- Implementar tests faltantes por módulo
- Configurar CI/CD con GitHub Actions
- Integrar Codecov para tracking de coverage
- Crear fixtures de datos de mercado
- Implementar backtesting automatizado
Última actualización: 2026-01-30 Generado por: Claude Code (Opus 4.5) - Sprint 4