intelligent-testing

jokken79's avatarfrom jokken79

Testing inteligente con generación automática de tests, detección de flaky tests y análisis de cobertura

⭐1stars🔀0forks📁View on GitHub🕐Updated Jan 11, 2026

When & Why to Use This Skill

The Intelligent Testing skill is a comprehensive automated quality assurance tool designed to streamline the software testing lifecycle. It enables developers to automatically generate robust unit, API, and E2E tests while identifying flaky tests and analyzing code coverage. By leveraging static analysis and code patterns, it provides actionable insights to improve test stability and ensure high-quality software delivery across various frameworks like Python, FastAPI, and Playwright.

Use Cases

  • Automated Test Generation: Rapidly generate unit tests for complex business logic, API endpoints for FastAPI, and E2E user flows using Playwright or Jest to reduce manual testing effort.
  • Flaky Test Detection and Mitigation: Identify non-deterministic test failures caused by time dependencies, random values, or external API calls, and apply recommended fixes to stabilize the CI/CD pipeline.
  • Code Coverage Optimization: Analyze line and branch coverage across different modules to identify untested critical paths and ensure compliance with quality thresholds.
  • CI/CD Pipeline Integration: Seamlessly integrate automated testing suites and coverage reporting into GitHub Actions to provide fast feedback loops and prevent regressions during the development process.
  • Mutation Testing and Stability Analysis: Use advanced techniques like mutation testing to evaluate the effectiveness of existing test suites and analyze historical execution data to score test reliability.
nameintelligent-testing
descriptionTesting inteligente con generación automática de tests, detección de flaky tests y análisis de cobertura

Intelligent Testing Skill

Skill avanzado para testing automatizado que genera tests, detecta tests frágiles, analiza cobertura y sugiere mejoras basadas en análisis estático y patrones de código.

Filosofía de Testing

Pirámide de Tests

          /\
         /  \     E2E Tests (10%)
        /----\    - Playwright/Cypress
       /      \   - Critical user flows
      /--------\  Integration Tests (20%)
     /          \ - API endpoints
    /------------\- Database operations
   /              \ Unit Tests (70%)
  /----------------\- Pure functions
 /                  \- Business logic
/____________________\

Principios FIRST

  • Fast: Tests rápidos (< 100ms unitarios)
  • Independent: Sin dependencias entre tests
  • Repeatable: Mismo resultado siempre
  • Self-validating: Pass/fail automático
  • Timely: Escritos junto al código

Generación Automática de Tests

1. Tests Unitarios (Python)

# Dado el código:
def calculate_granted_days(seniority_years: float) -> int:
    GRANT_TABLE = {0.5: 10, 1.5: 11, 2.5: 12, 3.5: 14, 4.5: 16, 5.5: 18, 6.5: 20}
    granted = 0
    for threshold, days in sorted(GRANT_TABLE.items()):
        if seniority_years >= threshold:
            granted = days
    return granted

# Tests generados automáticamente:
import pytest
from services.fiscal_year import calculate_granted_days

class TestCalculateGrantedDays:
    """Tests for calculate_granted_days function."""

    # Boundary tests
    @pytest.mark.parametrize("years,expected", [
        (0.0, 0),      # Bajo mínimo
        (0.5, 10),     # Exactamente 6 meses
        (0.49, 0),     # Justo bajo el límite
        (0.51, 10),    # Justo sobre el límite
        (6.5, 20),     # Máximo
        (10.0, 20),    # Sobre máximo
    ])
    def test_boundary_values(self, years, expected):
        assert calculate_granted_days(years) == expected

    # Edge cases
    def test_negative_years(self):
        assert calculate_granted_days(-1.0) == 0

    def test_float_precision(self):
        assert calculate_granted_days(1.4999999) == 10
        assert calculate_granted_days(1.5000001) == 11

    # Regression tests
    def test_known_values(self):
        assert calculate_granted_days(2.5) == 12
        assert calculate_granted_days(5.5) == 18

2. Tests de API (FastAPI)

# Dado el endpoint:
@app.get("/api/employees")
async def get_employees(year: int = None):
    return database.get_employees(year=year)

# Tests generados:
from fastapi.testclient import TestClient
from main import app
import pytest

client = TestClient(app)

class TestGetEmployees:
    """API tests for /api/employees endpoint."""

    def test_get_all_employees_success(self):
        response = client.get("/api/employees")
        assert response.status_code == 200
        assert isinstance(response.json(), list)

    def test_get_employees_by_year(self):
        response = client.get("/api/employees?year=2025")
        assert response.status_code == 200
        for emp in response.json():
            assert emp.get("year") == 2025

    def test_get_employees_invalid_year(self):
        response = client.get("/api/employees?year=invalid")
        assert response.status_code == 422  # Validation error

    def test_get_employees_empty_result(self):
        response = client.get("/api/employees?year=1900")
        assert response.status_code == 200
        assert response.json() == []

    @pytest.mark.parametrize("year", [2020, 2021, 2022, 2023, 2024, 2025])
    def test_get_employees_multiple_years(self, year):
        response = client.get(f"/api/employees?year={year}")
        assert response.status_code == 200

3. Tests E2E (Playwright)

// Dado el flujo de usuario:
// 1. Login → 2. Dashboard → 3. Submit Leave Request → 4. Verify

import { test, expect } from '@playwright/test';

test.describe('Leave Request Workflow', () => {
    test.beforeEach(async ({ page }) => {
        await page.goto('/');
        // Login
        await page.locator('[data-testid="login-btn"]').click();
        await page.locator('[data-testid="username"]').fill('admin');
        await page.locator('[data-testid="password"]').fill('password');
        await page.locator('[data-testid="submit-login"]').click();
        await expect(page).toHaveURL('/dashboard');
    });

    test('should submit leave request successfully', async ({ page }) => {
        // Navigate to requests
        await page.locator('[data-testid="nav-requests"]').click();

        // Fill form
        await page.locator('[data-testid="start-date"]').fill('2025-12-25');
        await page.locator('[data-testid="end-date"]').fill('2025-12-26');
        await page.locator('[data-testid="leave-type"]').selectOption('full');
        await page.locator('[data-testid="reason"]').fill('Christmas vacation');

        // Submit
        await page.locator('[data-testid="submit-request"]').click();

        // Verify
        await expect(page.locator('[data-testid="success-toast"]'))
            .toContainText('Request submitted');
        await expect(page.locator('[data-testid="pending-requests"]'))
            .toContainText('2025-12-25');
    });

    test('should reject invalid date range', async ({ page }) => {
        await page.locator('[data-testid="nav-requests"]').click();
        await page.locator('[data-testid="start-date"]').fill('2025-12-26');
        await page.locator('[data-testid="end-date"]').fill('2025-12-25');  // End before start
        await page.locator('[data-testid="submit-request"]').click();

        await expect(page.locator('[data-testid="error-message"]'))
            .toContainText('End date must be after start date');
    });
});

Detección de Tests Frágiles

Patrones Problemáticos

FRAGILE_PATTERNS = {
    'time_dependent': {
        'pattern': r'datetime\.now\(\)|time\.time\(\)|Date\.now\(\)',
        'fix': 'Usar freezegun/timemachine para mockear tiempo',
        'example': '''
            # ANTES (frágil)
            def test_expiry():
                assert is_expired(datetime.now() - timedelta(days=1))

            # DESPUÉS (estable)
            @freeze_time("2025-01-09")
            def test_expiry():
                assert is_expired(datetime(2025, 1, 8))
        '''
    },
    'order_dependent': {
        'pattern': r'test_\d+_|test_a_|test_first_',
        'fix': 'Usar fixtures para setup/teardown independiente',
    },
    'sleep_wait': {
        'pattern': r'time\.sleep\(|await asyncio\.sleep\(|setTimeout\(',
        'fix': 'Usar polling/waitFor con timeout en lugar de sleep fijo',
        'example': '''
            # ANTES (frágil)
            await asyncio.sleep(2)
            assert result.ready

            # DESPUÉS (estable)
            await wait_for(lambda: result.ready, timeout=5)
        '''
    },
    'random_values': {
        'pattern': r'random\.\w+\(|uuid\.uuid4\(\)|faker\.',
        'fix': 'Fijar seed o usar valores determinísticos',
    },
    'external_dependency': {
        'pattern': r'requests\.get\(|fetch\(|\.connect\(',
        'fix': 'Mockear llamadas externas con responses/httpx-mock',
    }
}

Análisis de Flakiness

def analyze_test_stability(test_history: list) -> dict:
    """
    Analiza historial de ejecuciones para detectar tests flaky.

    Returns:
        {
            "test_name": {
                "pass_rate": 0.95,
                "flakiness_score": 0.05,
                "common_failure_patterns": ["timeout", "assertion"],
                "recommendation": "Add explicit wait for async operation"
            }
        }
    """
    results = {}
    for test in test_history:
        runs = test['runs']
        passes = sum(1 for r in runs if r['status'] == 'pass')
        pass_rate = passes / len(runs)

        if pass_rate < 1.0 and pass_rate > 0.0:
            results[test['name']] = {
                'pass_rate': pass_rate,
                'flakiness_score': 1 - pass_rate,
                'common_failure_patterns': extract_failure_patterns(runs),
                'recommendation': generate_fix_recommendation(runs)
            }

    return results

Análisis de Cobertura

Cobertura por Módulo

COVERAGE_THRESHOLDS = {
    'critical': {
        'files': ['fiscal_year.py', 'auth.py', 'database.py'],
        'line_coverage': 90,
        'branch_coverage': 85,
    },
    'important': {
        'files': ['main.py', 'excel_service.py'],
        'line_coverage': 80,
        'branch_coverage': 70,
    },
    'standard': {
        'files': ['*'],
        'line_coverage': 70,
        'branch_coverage': 60,
    }
}

Cobertura de Casos de Uso

def analyze_use_case_coverage(code_path: str, tests_path: str) -> dict:
    """
    Analiza qué casos de uso están cubiertos por tests.

    Returns:
        {
            "5_day_compliance": {
                "covered": True,
                "tests": ["test_check_5day_compliance", "test_compliance_alert"],
                "missing_scenarios": []
            },
            "lifo_deduction": {
                "covered": False,
                "tests": [],
                "missing_scenarios": [
                    "Deduction spanning multiple years",
                    "Deduction with expired days"
                ]
            }
        }
    """

Templates de Tests

Fixture Factory

import pytest
from datetime import date, timedelta

@pytest.fixture
def employee_factory():
    """Factory para crear empleados de test."""
    def _create(
        employee_num: str = "TEST001",
        name: str = "Test Employee",
        granted: float = 10.0,
        used: float = 0.0,
        year: int = 2025
    ):
        return {
            "id": f"{employee_num}_{year}",
            "employee_num": employee_num,
            "name": name,
            "granted": granted,
            "used": used,
            "balance": granted - used,
            "year": year
        }
    return _create

@pytest.fixture
def leave_request_factory():
    """Factory para crear solicitudes de test."""
    def _create(
        employee_num: str = "TEST001",
        days: float = 1.0,
        status: str = "PENDING",
        start_date: date = None
    ):
        start = start_date or date.today()
        return {
            "employee_num": employee_num,
            "days_requested": days,
            "status": status,
            "start_date": start.isoformat(),
            "end_date": (start + timedelta(days=int(days))).isoformat()
        }
    return _create

Mocking Best Practices

from unittest.mock import Mock, patch, MagicMock

# Mock de base de datos
@pytest.fixture
def mock_db():
    with patch('database.get_db') as mock:
        mock_conn = MagicMock()
        mock_cursor = MagicMock()
        mock_conn.cursor.return_value = mock_cursor
        mock.__enter__ = Mock(return_value=mock_conn)
        mock.__exit__ = Mock(return_value=False)
        yield mock_cursor

# Mock de servicios externos
@pytest.fixture
def mock_excel_service():
    with patch('excel_service.parse_excel_file') as mock:
        mock.return_value = [
            {"employee_num": "001", "name": "Test", "granted": 10, "used": 5}
        ]
        yield mock

Comandos de Testing

Ejecución

# Backend
pytest tests/ -v                          # Todos los tests
pytest tests/ -v --cov=. --cov-report=html  # Con cobertura
pytest tests/ -v -x                       # Parar en primer fallo
pytest tests/ -v -k "compliance"          # Solo tests con "compliance"
pytest tests/ --tb=short                  # Traceback corto

# Frontend
npx jest                                  # Todos los tests
npx jest --coverage                       # Con cobertura
npx jest --watch                          # Watch mode
npx jest --testNamePattern="escapeHtml"   # Test específico

# E2E
npx playwright test                       # Todos
npx playwright test --headed              # Ver browser
npx playwright test --debug               # Debug mode
npx playwright show-report                # Ver reporte

Generación de Reportes

# Cobertura combinada
pytest --cov=. --cov-report=xml
npx jest --coverage --coverageReporters=cobertura

# Mutation testing
pip install mutmut
mutmut run --paths-to-mutate=fiscal_year.py
mutmut results

Integración con YuKyuDATA

Tests Críticos Requeridos

# 1. Compliance Tests
class TestFiveDayCompliance:
    def test_employee_with_10_days_using_4_is_non_compliant(self):
        pass
    def test_employee_with_10_days_using_5_is_compliant(self):
        pass
    def test_employee_with_9_days_exempt_from_rule(self):
        pass

# 2. LIFO Deduction Tests
class TestLIFODeduction:
    def test_deduct_from_current_year_first(self):
        pass
    def test_deduct_from_previous_year_when_current_exhausted(self):
        pass
    def test_expired_days_not_deductible(self):
        pass

# 3. Year-End Carryover Tests
class TestYearEndCarryover:
    def test_carryover_up_to_max_days(self):
        pass
    def test_expire_days_older_than_2_years(self):
        pass
    def test_preserve_balance_order(self):
        pass

# 4. Leave Request Workflow Tests
class TestLeaveRequestWorkflow:
    def test_create_pending_request(self):
        pass
    def test_approve_deducts_balance(self):
        pass
    def test_reject_preserves_balance(self):
        pass
    def test_revert_restores_balance(self):
        pass

GitHub Actions CI

name: Test Suite
on: [push, pull_request]

jobs:
  backend-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install -r requirements.txt
      - run: pytest tests/ --cov=. --cov-report=xml
      - uses: codecov/codecov-action@v4

  frontend-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx jest --coverage

  e2e-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test

Principio Guía: "Test what matters. Make tests reliable. Fast feedback > complete coverage."