Skip to content
IRC-CodingIRC-Coding
Fundamentos de Software TestingUnit TestsIntegration TestsE2E TestsTDDBDD

Fundamentos de Software Testing: Unit, Integration y E2E Tests

Guía de Software Testing con Unit Tests, Integration Tests, E2E Tests, TDD y BDD. Aprende Test-Driven Development y Behavior-Driven Development.

S

schutzgeist

25 min read
Fundamentos de Software Testing: Unit, Integration y E2E Tests

Fundamentos de Testing en Software: Unit Tests, Integration Tests, E2E Tests, TDD y BDD

Este artículo es una introducción completa a los fundamentos de testing en software, incluyendo Unit Tests, Integration Tests, E2E Tests, TDD y BDD con ejemplos prácticos.

En Resumen

El testing en software es un proceso sistemático para verificar la calidad y funcionalidad de una aplicación, asegurando que cumple con los requisitos y funciona sin errores.

Descripción Técnica Concisa

Testing en software es la verificación de una aplicación respecto a su corrección, completitud y calidad mediante pruebas dirigidas y métodos de validación.

Componentes principales:

Unit Tests

  • Tests aislados: Prueba componentes individuales de forma independiente
  • Ejecución rápida: Milisegundos a pocos segundos
  • Mocking: Simula dependencias externas
  • Coverage: Mide la cobertura de código
  • Frameworks: JUnit, Jest, PyTest

Integration Tests

  • Integración de componentes: Prueba múltiples módulos en conjunto
  • Tests de base de datos: Valida la capa de persistencia
  • Tests de API: Verifica interfaces entre servicios
  • Integración del sistema: Comprueba el sistema completo
  • Entornos de prueba: Sistemas staging y de testing

End-to-End (E2E) Tests

  • Escenarios de usuario: Prueba flujos completos
  • Automatización de navegador: Simula interacciones en la UI
  • Cross-browser: Prueba en diferentes navegadores
  • Tests móviles: Aplicaciones iOS/Android
  • Herramientas: Selenium, Cypress, Playwright

Test-Driven Development (TDD)

  • Red-Green-Refactor: Ciclo iterativo de desarrollo
  • Tests primero: Escribe pruebas antes de la implementación
  • Pasos pequeños: Desarrollo incremental
  • Tests de regresión: Suite de pruebas automatizada
  • Mejora del diseño: Arquitectura de software más robusta

Behavior-Driven Development (BDD)

  • Sintaxis Gherkin: Descripciones de pruebas legibles
  • User Stories: Formula requisitos del negocio
  • Comunicación con stakeholders: Entendimiento compartido
  • Cucumber: Implementación del framework BDD
  • Documentación viva: Documentación actualizada continuamente

Puntos Clave para Examen

  • Unit Tests: Pruebas aisladas de funciones/clases individuales
  • Integration Tests: Pruebas de cómo trabajan juntos los componentes
  • E2E Tests: Flujos completos del usuario de principio a fin
  • TDD: Test-Driven Development con ciclo Red-Green-Refactor
  • BDD: Behavior-Driven Development con sintaxis Gherkin
  • Test Pyramid: Más Unit Tests que Integration/E2E Tests
  • Mocking: Simulación de dependencias para las pruebas
  • Code Coverage: Medición de la cobertura de pruebas
  • IHK-relevante: Aseguramiento de calidad de software y estrategias de testing

Componentes Fundamentales

  1. Test Strategy: Planificación y priorización de pruebas
  2. Test Automation: Ejecución automatizada de pruebas
  3. Test Frameworks: Herramientas y bibliotecas para testing
  4. Continuous Testing: Integración en pipelines CI/CD
  5. Test Data Management: Generación y gestión de datos de prueba
  6. Test Reporting: Análisis y documentación de resultados
  7. Performance Testing: Pruebas de carga y estrés
  8. Security Testing: Identificación de vulnerabilidades

Ejemplos Prácticos

1. Unit Tests con JavaScript y Jest

// mathUtils.js - Zu testende Funktionen
export class MathUtils {
  static add(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
      throw new Error('Both arguments must be numbers');
    }
    return a + b;
  }

  static subtract(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
      throw new Error('Both arguments must be numbers');
    }
    return a - b;
  }

  static multiply(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
      throw new Error('Both arguments must be numbers');
    }
    return a * b;
  }

  static divide(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
      throw new Error('Both arguments must be numbers');
    }
    if (b === 0) {
      throw new Error('Division by zero is not allowed');
    }
    return a / b;
  }

  static factorial(n) {
    if (typeof n !== 'number' || n < 0 || !Number.isInteger(n)) {
      throw new Error('Argument must be a non-negative integer');
    }
    if (n === 0 || n === 1) {
      return 1;
    }
    return n * this.factorial(n - 1);
  }

  static fibonacci(n) {
    if (typeof n !== 'number' || n < 0 || !Number.isInteger(n)) {
      throw new Error('Argument must be a non-negative integer');
    }
    if (n === 0) {
      return 0;
    }
    if (n === 1) {
      return 1;
    }
    return this.fibonacci(n - 1) + this.fibonacci(n - 2);
  }

  static isPrime(n) {
    if (typeof n !== 'number' || n < 2 || !Number.isInteger(n)) {
      return false;
    }
    if (n === 2) {
      return true;
    }
    if (n % 2 === 0) {
      return false;
    }
    for (let i = 3; i <= Math.sqrt(n); i += 2) {
      if (n % i === 0) {
        return false;
      }
    }
    return true;
  }

  static gcd(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
      throw new Error('Both arguments must be numbers');
    }
    a = Math.abs(a);
    b = Math.abs(b);
    while (b !== 0) {
      const temp = b;
      b = a % b;
      a = temp;
    }
    return a;
  }

  static lcm(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
      throw new Error('Both arguments must be numbers');
    }
    if (a === 0 || b === 0) {
      return 0;
    }
    return Math.abs(a * b) / this.gcd(a, b);
  }
}

// userService.js - Service mit Abhängigkeiten
export class UserService {
  constructor(apiClient, cache) {
    this.apiClient = apiClient;
    this.cache = cache;
  }

  async getUser(id) {
    if (!id) {
      throw new Error('User ID is required');
    }

    // Check cache first
    const cachedUser = this.cache.get(`user:${id}`);
    if (cachedUser) {
      return cachedUser;
    }

    // Fetch from API
    const user = await this.apiClient.get(`/users/${id}`);
    
    // Cache the result
    this.cache.set(`user:${id}`, user, 300); // 5 minutes
    
    return user;
  }

  async createUser(userData) {
    if (!userData || !userData.email || !userData.name) {
      throw new Error('Email and name are required');
    }

    const user = await this.apiClient.post('/users', userData);
    
    // Cache the new user
    this.cache.set(`user:${user.id}`, user, 300);
    
    return user;
  }

  async updateUser(id, userData) {
    if (!id || !userData) {
      throw new Error('User ID and data are required');
    }

    const user = await this.apiClient.put(`/users/${id}`, userData);
    
    // Update cache
    this.cache.set(`user:${id}`, user, 300);
    
    return user;
  }

  async deleteUser(id) {
    if (!id) {
      throw new Error('User ID is required');
    }

    await this.apiClient.delete(`/users/${id}`);
    
    // Remove from cache
    this.cache.delete(`user:${id}`);
    
    return true;
  }

  async searchUsers(query) {
    if (!query) {
      throw new Error('Search query is required');
    }

    const cacheKey = `search:${query}`;
    const cachedResults = this.cache.get(cacheKey);
    if (cachedResults) {
      return cachedResults;
    }

    const results = await this.apiClient.get('/users/search', { q: query });
    
    this.cache.set(cacheKey, results, 600); // 10 minutes
    
    return results;
  }
}

// mathUtils.test.js - Unit Tests
import { MathUtils } from './mathUtils';

describe('MathUtils', () => {
  describe('add', () => {
    test('should add two positive numbers', () => {
      expect(MathUtils.add(2, 3)).toBe(5);
      expect(MathUtils.add(10, 20)).toBe(30);
    });

    test('should add negative numbers', () => {
      expect(MathUtils.add(-2, -3)).toBe(-5);
      expect(MathUtils.add(-10, 5)).toBe(-5);
    });

    test('should add zero', () => {
      expect(MathUtils.add(0, 5)).toBe(5);
      expect(MathUtils.add(5, 0)).toBe(5);
      expect(MathUtils.add(0, 0)).toBe(0);
    });

    test('should handle decimal numbers', () => {
      expect(MathUtils.add(1.5, 2.5)).toBe(4.0);
      expect(MathUtils.add(-1.5, 2.5)).toBe(1.0);
    });

    test('should throw error for non-number arguments', () => {
      expect(() => MathUtils.add('2', 3)).toThrow('Both arguments must be numbers');
      expect(() => MathUtils.add(2, '3')).toThrow('Both arguments must be numbers');
      expect(() => MathUtils.add(null, 3)).toThrow('Both arguments must be numbers');
      expect(() => MathUtils.add(undefined, 3)).toThrow('Both arguments must be numbers');
    });
  });

  describe('subtract', () => {
    test('should subtract two positive numbers', () => {
      expect(MathUtils.subtract(5, 3)).toBe(2);
      expect(MathUtils.subtract(10, 20)).toBe(-10);
    });

    test('should subtract negative numbers', () => {
      expect(MathUtils.subtract(-5, -3)).toBe(-2);
      expect(MathUtils.subtract(-10, 5)).toBe(-15);
    });

    test('should throw error for non-number arguments', () => {
      expect(() => MathUtils.subtract('5', 3)).toThrow('Both arguments must be numbers');
    });
  });

  describe('divide', () => {
    test('should divide two positive numbers', () => {
      expect(MathUtils.divide(10, 2)).toBe(5);
      expect(MathUtils.divide(7, 2)).toBe(3.5);
    });

    test('should handle division by zero', () => {
      expect(() => MathUtils.divide(10, 0)).toThrow('Division by zero is not allowed');
    });

    test('should handle negative numbers', () => {
      expect(MathUtils.divide(-10, 2)).toBe(-5);
      expect(MathUtils.divide(10, -2)).toBe(-5);
      expect(MathUtils.divide(-10, -2)).toBe(5);
    });
  });

  describe('factorial', () => {
    test('should calculate factorial of positive numbers', () => {
      expect(MathUtils.factorial(0)).toBe(1);
      expect(MathUtils.factorial(1)).toBe(1);
      expect(MathUtils.factorial(5)).toBe(120);
      expect(MathUtils.factorial(10)).toBe(3628800);
    });

    test('should throw error for negative numbers', () => {
      expect(() => MathUtils.factorial(-1)).toThrow('Argument must be a non-negative integer');
    });

    test('should throw error for non-integers', () => {
      expect(() => MathUtils.factorial(2.5)).toThrow('Argument must be a non-negative integer');
    });
  });

  describe('fibonacci', () => {
    test('should calculate fibonacci numbers', () => {
      expect(MathUtils.fibonacci(0)).toBe(0);
      expect(MathUtils.fibonacci(1)).toBe(1);
      expect(MathUtils.fibonacci(5)).toBe(5);
      expect(MathUtils.fibonacci(10)).toBe(55);
    });

    test('should throw error for negative numbers', () => {
      expect(() => MathUtils.fibonacci(-1)).toThrow('Argument must be a non-negative integer');
    });
  });

  describe('isPrime', () => {
    test('should identify prime numbers correctly', () => {
      expect(MathUtils.isPrime(2)).toBe(true);
      expect(MathUtils.isPrime(3)).toBe(true);
      expect(MathUtils.isPrime(5)).toBe(true);
      expect(MathUtils.isPrime(7)).toBe(true);
      expect(MathUtils.isPrime(11)).toBe(true);
    });

    test('should identify non-prime numbers correctly', () => {
      expect(MathUtils.isPrime(1)).toBe(false);
      expect(MathUtils.isPrime(4)).toBe(false);
      expect(MathUtils.isPrime(6)).toBe(false);
      expect(MathUtils.isPrime(8)).toBe(false);
      expect(MathUtils.isPrime(9)).toBe(false);
      expect(MathUtils.isPrime(10)).toBe(false);
    });
  });

  describe('gcd', () => {
    test('should calculate greatest common divisor', () => {
      expect(MathUtils.gcd(48, 18)).toBe(6);
      expect(MathUtils.gcd(54, 24)).toBe(6);
      expect(MathUtils.gcd(17, 23)).toBe(1);
      expect(MathUtils.gcd(0, 5)).toBe(5);
      expect(MathUtils.gcd(5, 0)).toBe(5);
    });

    test('should handle negative numbers', () => {
      expect(MathUtils.gcd(-48, 18)).toBe(6);
      expect(MathUtils.gcd(48, -18)).toBe(6);
      expect(MathUtils.gcd(-48, -18)).toBe(6);
    });
  });

  describe('lcm', () => {
    test('should calculate least common multiple', () => {
      expect(MathUtils.lcm(4, 6)).toBe(12);
      expect(MathUtils.lcm(21, 6)).toBe(42);
      expect(MathUtils.lcm(3, 5)).toBe(15);
    });

    test('should handle zero', () => {
      expect(MathUtils.lcm(0, 5)).toBe(0);
      expect(MathUtils.lcm(5, 0)).toBe(0);
      expect(MathUtils.lcm(0, 0)).toBe(0);
    });
  });
});

// userService.test.js - Unit Tests mit Mocking
import { UserService } from './userService';

// Mock dependencies
const mockApiClient = {
  get: jest.fn(),
  post: jest.fn(),
  put: jest.fn(),
  delete: jest.fn()
};

const mockCache = {
  get: jest.fn(),
  set: jest.fn(),
  delete: jest.fn()
};

describe('UserService', () => {
  let userService;

  beforeEach(() => {
    // Reset mocks before each test
    jest.clearAllMocks();
    
    userService = new UserService(mockApiClient, mockCache);
  });

  describe('getUser', () => {
    test('should return user from cache if available', async () => {
      const cachedUser = { id: 1, name: 'John Doe', email: 'john@example.com' };
      mockCache.get.mockReturnValue(cachedUser);

      const result = await userService.getUser(1);

      expect(result).toBe(cachedUser);
      expect(mockCache.get).toHaveBeenCalledWith('user:1');
      expect(mockApiClient.get).not.toHaveBeenCalled();
    });

    test('should fetch user from API if not in cache', async () => {
      const user = { id: 1, name: 'John Doe', email: 'john@example.com' };
      mockCache.get.mockReturnValue(null);
      mockApiClient.get.mockResolvedValue(user);

      const result = await userService.getUser(1);

      expect(result).toBe(user);
      expect(mockCache.get).toHaveBeenCalledWith('user:1');
      expect(mockApiClient.get).toHaveBeenCalledWith('/users/1');
      expect(mockCache.set).toHaveBeenCalledWith('user:1', user, 300);
    });

    test('should throw error if no user ID provided', async () => {
      await expect(userService.getUser()).rejects.toThrow('User ID is required');
    });

    test('should handle API errors', async () => {
      mockCache.get.mockReturnValue(null);
      mockApiClient.get.mockRejectedValue(new Error('API Error'));

      await expect(userService.getUser(1)).rejects.toThrow('API Error');
    });
  });

  describe('createUser', () => {
    test('should create user successfully', async () => {
      const userData = { name: 'John Doe', email: 'john@example.com' };
      const createdUser = { id: 1, ...userData };
      mockApiClient.post.mockResolvedValue(createdUser);

      const result = await userService.createUser(userData);

      expect(result).toBe(createdUser);
      expect(mockApiClient.post).toHaveBeenCalledWith('/users', userData);
      expect(mockCache.set).toHaveBeenCalledWith('user:1', createdUser, 300);
    });

    test('should throw error if email is missing', async () => {
      const userData = { name: 'John Doe' };

      await expect(userService.createUser(userData)).rejects.toThrow('Email and name are required');
    });

    test('should throw error if name is missing', async () => {
      const userData = { email: 'john@example.com' };

      await expect(userService.createUser(userData)).rejects.toThrow('Email and name are required');
    });
  });

  describe('updateUser', () => {
    test('should update user successfully', async () => {
      const userData = { name: 'Jane Doe' };
      const updatedUser = { id: 1, name: 'Jane Doe', email: 'john@example.com' };
      mockApiClient.put.mockResolvedValue(updatedUser);

      const result = await userService.updateUser(1, userData);

      expect(result).toBe(updatedUser);
      expect(mockApiClient.put).toHaveBeenCalledWith('/users/1', userData);
      expect(mockCache.set).toHaveBeenCalledWith('user:1', updatedUser, 300);
    });

    test('should throw error if no user ID provided', async () => {
      await expect(userService.updateUser()).rejects.toThrow('User ID and data are required');
    });
  });

  describe('searchUsers', () => {
    test('should return cached search results if available', async () => {
      const cachedResults = [{ id: 1, name: 'John Doe' }];
      mockCache.get.mockReturnValue(cachedResults);

      const result = await userService.searchUsers('John');

      expect(result).toBe(cachedResults);
      expect(mockCache.get).toHaveBeenCalledWith('search:John');
      expect(mockApiClient.get).not.toHaveBeenCalled();
    });

    test('should search users via API if not in cache', async () => {
      const results = [{ id: 1, name: 'John Doe' }];
      mockCache.get.mockReturnValue(null);
      mockApiClient.get.mockResolvedValue(results);

      const result = await userService.searchUsers('John');

      expect(result).toBe(results);
      expect(mockCache.get).toHaveBeenCalledWith('search:John');
      expect(mockApiClient.get).toHaveBeenCalledWith('/users/search', { q: 'John' });
      expect(mockCache.set).toHaveBeenCalledWith('search:John', results, 600);
    });
  });
});

// Test Configuration
// jest.config.js
module.exports = {
  testEnvironment: 'node',
  collectCoverage: true,
  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'lcov', 'html'],
  collectCoverageFrom: [
    'src/**/*.js',
    '!src/**/*.test.js',
    '!src/**/index.js'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  },
  setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
  testMatch: [
    '**/__tests__/**/*.js',
    '**/?(*.)+(spec|test).js'
  ]
};

// tests/setup.js
// Global test setup
beforeAll(() => {
  console.log('Starting test suite');
});

afterAll(() => {
  console.log('Test suite completed');
});

beforeEach(() => {
  // Reset console mocks
  jest.spyOn(console, 'log').mockImplementation(() => {});
  jest.spyOn(console, 'error').mockImplementation(() => {});
});

afterEach(() => {
  // Restore console mocks
  console.log.mockRestore();
  console.error.mockRestore();
});

2. Pruebas de integración con Python y PyTest

# tests/integration/test_user_integration.py
import pytest
import asyncio
from httpx import AsyncClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from fastapi.testclient import TestClient
from app.main import app
from app.database import get_db, Base
from app.models import User, Post, Comment
from app.core.config import settings

# Test database setup
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# Override database dependency for testing
def override_get_db():
    try:
        db = TestingSessionLocal()
        yield db
    finally:
        db.close()

app.dependency_overrides[get_db] = override_get_db

@pytest.fixture(scope="session")
def event_loop():
    """Create an instance of the default event loop for the test session."""
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()

@pytest.fixture(scope="function")
def db_session():
    """Create a fresh database session for each test."""
    Base.metadata.create_all(bind=engine)
    session = TestingSessionLocal()
    try:
        yield session
    finally:
        session.close()
        Base.metadata.drop_all(bind=engine)

@pytest.fixture(scope="function")
def client(db_session):
    """Create a test client."""
    with TestClient(app) as test_client:
        yield test_client

@pytest.fixture(scope="function")
async def async_client(db_session):
    """Create an async test client."""
    async with AsyncClient(app=app, base_url="http://test") as ac:
        yield ac

@pytest.fixture
def sample_user(db_session):
    """Create a sample user for testing."""
    user = User(
        username="testuser",
        email="test@example.com",
        hashed_password="hashed_password",
        is_active=True
    )
    db_session.add(user)
    db_session.commit()
    db_session.refresh(user)
    return user

@pytest.fixture
def sample_post(db_session, sample_user):
    """Create a sample post for testing."""
    post = Post(
        title="Test Post",
        content="This is a test post content.",
        author_id=sample_user.id,
        is_published=True
    )
    db_session.add(post)
    db_session.commit()
    db_session.refresh(post)
    return post

@pytest.fixture
def sample_comment(db_session, sample_user, sample_post):
    """Create a sample comment for testing."""
    comment = Comment(
        content="This is a test comment.",
        author_id=sample_user.id,
        post_id=sample_post.id
    )
    db_session.add(comment)
    db_session.commit()
    db_session.refresh(comment)
    return comment

class TestUserIntegration:
    """Integration tests for user endpoints."""

    def test_create_user_success(self, client):
        """Test successful user creation."""
        user_data = {
            "username": "newuser",
            "email": "newuser@example.com",
            "password": "securepassword123"
        }

        response = client.post("/api/v1/users/", json=user_data)

        assert response.status_code == 201
        data = response.json()
        assert data["username"] == user_data["username"]
        assert data["email"] == user_data["email"]
        assert "id" in data
        assert "hashed_password" not in data  # Password should not be returned

    def test_create_user_duplicate_email(self, client, sample_user):
        """Test user creation with duplicate email."""
        user_data = {
            "username": "anotheruser",
            "email": sample_user.email,  # Duplicate email
            "password": "securepassword123"
        }

        response = client.post("/api/v1/users/", json=user_data)

        assert response.status_code == 400
        assert "already registered" in response.json()["detail"].lower()

    def test_get_user_success(self, client, sample_user):
        """Test successful user retrieval."""
        response = client.get(f"/api/v1/users/{sample_user.id}")

        assert response.status_code == 200
        data = response.json()
        assert data["id"] == sample_user.id
        assert data["username"] == sample_user.username
        assert data["email"] == sample_user.email

    def test_get_user_not_found(self, client):
        """Test user retrieval with non-existent ID."""
        response = client.get("/api/v1/users/99999")

        assert response.status_code == 404
        assert "not found" in response.json()["detail"].lower()

    def test_update_user_success(self, client, sample_user):
        """Test successful user update."""
        update_data = {
            "username": "updateduser",
            "email": "updated@example.com"
        }

        response = client.put(f"/api/v1/users/{sample_user.id}", json=update_data)

        assert response.status_code == 200
        data = response.json()
        assert data["username"] == update_data["username"]
        assert data["email"] == update_data["email"]

    def test_delete_user_success(self, client, sample_user):
        """Test successful user deletion."""
        response = client.delete(f"/api/v1/users/{sample_user.id}")

        assert response.status_code == 204

        # Verify user is deleted
        response = client.get(f"/api/v1/users/{sample_user.id}")
        assert response.status_code == 404

class TestPostIntegration:
    """Integration tests for post endpoints."""

    def test_create_post_success(self, client, sample_user):
        """Test successful post creation."""
        post_data = {
            "title": "New Test Post",
            "content": "This is a new test post content.",
            "is_published": True
        }

        # Authenticate as sample user
        client.headers.update({"Authorization": f"Bearer {sample_user.id}"})

        response = client.post("/api/v1/posts/", json=post_data)

        assert response.status_code == 201
        data = response.json()
        assert data["title"] == post_data["title"]
        assert data["content"] == post_data["content"]
        assert data["author_id"] == sample_user.id

    def test_get_post_success(self, client, sample_post):
        """Test successful post retrieval."""
        response = client.get(f"/api/v1/posts/{sample_post.id}")

        assert response.status_code == 200
        data = response.json()
        assert data["id"] == sample_post.id
        assert data["title"] == sample_post.title
        assert data["author"]["id"] == sample_post.author_id

    def test_get_posts_with_pagination(self, client, db_session):
        """Test post listing with pagination."""
        # Create multiple posts
        user = db_session.query(User).first()
        for i in range(25):
            post = Post(
                title=f"Post {i}",
                content=f"Content for post {i}",
                author_id=user.id,
                is_published=True
            )
            db_session.add(post)
        db_session.commit()

        response = client.get("/api/v1/posts/?skip=0&limit=10")

        assert response.status_code == 200
        data = response.json()
        assert len(data) == 10
        assert "total" in response.headers

    def test_update_post_success(self, client, sample_post, sample_user):
        """Test successful post update."""
        update_data = {
            "title": "Updated Post Title",
            "content": "Updated post content."
        }

        # Authenticate as post author
        client.headers.update({"Authorization": f"Bearer {sample_user.id}"})

        response = client.put(f"/api/v1/posts/{sample_post.id}", json=update_data)

        assert response.status_code == 200
        data = response.json()
        assert data["title"] == update_data["title"]
        assert data["content"] == update_data["content"]

    def test_delete_post_success(self, client, sample_post, sample_user):
        """Test successful post deletion."""
        # Authenticate as post author
        client.headers.update({"Authorization": f"Bearer {sample_user.id}"})

        response = client.delete(f"/api/v1/posts/{sample_post.id}")

        assert response.status_code == 204

        # Verify post is deleted
        response = client.get(f"/api/v1/posts/{sample_post.id}")
        assert response.status_code == 404

class TestCommentIntegration:
    """Integration tests for comment endpoints."""

    def test_create_comment_success(self, client, sample_post, sample_user):
        """Test successful comment creation."""
        comment_data = {
            "content": "This is a test comment.",
            "post_id": sample_post.id
        }

        # Authenticate as sample user
        client.headers.update({"Authorization": f"Bearer {sample_user.id}"})

        response = client.post("/api/v1/comments/", json=comment_data)

        assert response.status_code == 201
        data = response.json()
        assert data["content"] == comment_data["content"]
        assert data["post_id"] == sample_post.id
        assert data["author_id"] == sample_user.id

    def test_get_comments_for_post(self, client, sample_post, sample_comment):
        """Test retrieving comments for a post."""
        response = client.get(f"/api/v1/posts/{sample_post.id}/comments")

        assert response.status_code == 200
        data = response.json()
        assert len(data) >= 1
        assert any(comment["id"] == sample_comment.id for comment in data)

class TestAPIIntegration:
    """Integration tests for API endpoints."""

    def test_user_post_relationship(self, client, sample_user, sample_post):
        """Test user-post relationship."""
        response = client.get(f"/api/v1/users/{sample_user.id}/posts")

        assert response.status_code == 200
        data = response.json()
        assert len(data) >= 1
        assert any(post["id"] == sample_post.id for post in data)

    def test_post_comment_relationship(self, client, sample_post, sample_comment):
        """Test post-comment relationship."""
        response = client.get(f"/api/v1/posts/{sample_post.id}/comments")

        assert response.status_code == 200
        data = response.json()
        assert len(data) >= 1
        assert any(comment["id"] == sample_comment.id for comment in data)

    def test_search_functionality(self, client, sample_post):
        """Test search functionality."""
        response = client.get(f"/api/v1/search?q={sample_post.title}")

        assert response.status_code == 200
        data = response.json()
        assert "posts" in data
        assert "users" in data
        assert len(data["posts"]) >= 1

class TestDatabaseIntegration:
    """Integration tests for database operations."""

    def test_transaction_rollback_on_error(self, client, sample_user):
        """Test transaction rollback on error."""
        # This test would verify that database transactions are properly
        # rolled back when an error occurs during a complex operation
        
        post_data = {
            "title": "Valid Post",
            "content": "Valid content",
            "is_published": True
        }

        # Simulate a database error during post creation
        with pytest.raises(Exception):
            # This would be implemented with actual database error simulation
            pass

        # Verify that no partial data was saved
        response = client.get("/api/v1/posts/")
        data = response.json()
        assert not any(post["title"] == "Valid Post" for post in data)

    def test_concurrent_requests(self, async_client, sample_user):
        """Test handling of concurrent requests."""
        import asyncio

        async def create_post(title):
            post_data = {
                "title": title,
                "content": f"Content for {title}",
                "is_published": True
            }
            response = await async_client.post(
                "/api/v1/posts/",
                json=post_data,
                headers={"Authorization": f"Bearer {sample_user.id}"}
            )
            return response

        # Create multiple concurrent requests
        tasks = [
            create_post(f"Concurrent Post {i}")
            for i in range(10)
        ]

        responses = await asyncio.gather(*tasks)

        # Verify all requests succeeded
        for response in responses:
            assert response.status_code == 201

        # Verify all posts were created
        response = await async_client.get("/api/v1/posts/")
        data = response.json()
        concurrent_posts = [
            post for post in data
            if post["title"].startswith("Concurrent Post")
        ]
        assert len(concurrent_posts) == 10

class TestAuthenticationIntegration:
    """Integration tests for authentication."""

    def test_login_success(self, client, sample_user):
        """Test successful login."""
        login_data = {
            "username": sample_user.username,
            "password": "testpassword"  # This would be the actual password
        }

        response = client.post("/api/v1/auth/login", json=login_data)

        assert response.status_code == 200
        data = response.json()
        assert "access_token" in data
        assert "token_type" in data
        assert data["token_type"] == "bearer"

    def test_protected_endpoint_without_token(self, client):
        """Test accessing protected endpoint without token."""
        response = client.post("/api/v1/posts/", json={
            "title": "Test Post",
            "content": "Test content"
        })

        assert response.status_code == 401
        assert "not authenticated" in response.json()["detail"].lower()

    def test_protected_endpoint_with_invalid_token(self, client):
        """Test accessing protected endpoint with invalid token."""
        client.headers.update({"Authorization": "Bearer invalid_token"})

        response = client.post("/api/v1/posts/", json={
            "title": "Test Post",
            "content": "Test content"
        })

        assert response.status_code == 401

# Performance Tests
class TestPerformanceIntegration:
    """Integration tests for performance."""

    def test_bulk_operations_performance(self, client, sample_user):
        """Test performance of bulk operations."""
        import time

        client.headers.update({"Authorization": f"Bearer {sample_user.id}"})

        # Measure time for creating multiple posts
        start_time = time.time()
        
        for i in range(100):
            post_data = {
                "title": f"Bulk Post {i}",
                "content": f"Bulk content {i}",
                "is_published": True
            }
            response = client.post("/api/v1/posts/", json=post_data)
            assert response.status_code == 201

        end_time = time.time()
        duration = end_time - start_time

        # Should complete within reasonable time (adjust threshold as needed)
        assert duration < 10.0, f"Bulk operations took too long: {duration}s"

    def test_large_payload_handling(self, client, sample_user):
        """Test handling of large payloads."""
        client.headers.update({"Authorization": f"Bearer {sample_user.id}"})

        # Create post with very large content
        large_content = "A" * 1000000  # 1MB of content
        
        post_data = {
            "title": "Large Content Post",
            "content": large_content,
            "is_published": True
        }

        response = client.post("/api/v1/posts/", json=post_data)

        # Should handle large payload (or return appropriate error)
        assert response.status_code in [201, 413, 422]

# Error Handling Tests
class TestErrorHandlingIntegration:
    """Integration tests for error handling."""

    def test_malformed_json_request(self, client):
        """Test handling of malformed JSON."""
        response = client.post(
            "/api/v1/users/",
            data="invalid json",
            headers={"Content-Type": "application/json"}
        )

        assert response.status_code == 422

    def test_invalid_endpoint(self, client):
        """Test handling of invalid endpoints."""
        response = client.get("/api/v1/invalid/endpoint")

        assert response.status_code == 404

    def test_method_not_allowed(self, client):
        """Test handling of disallowed HTTP methods."""
        response = client.patch("/api/v1/users/")

        assert response.status_code == 405

# Configuration Tests
class TestConfigurationIntegration:
    """Integration tests for configuration."""

    def test_cors_headers(self, client):
        """Test CORS headers are properly set."""
        response = client.options("/api/v1/users/")

        assert "access-control-allow-origin" in response.headers

    def test_rate_limiting(self, client):
        """Test rate limiting is working."""
        # Make multiple rapid requests
        responses = []
        for i in range(100):
            response = client.get("/api/v1/users/")
            responses.append(response)
            if response.status_code == 429:
                break

        # Should eventually hit rate limit
        rate_limited = any(r.status_code == 429 for r in responses)
        assert rate_limited, "Rate limiting not working"

# Test utilities
@pytest.fixture
def auth_headers(sample_user):
    """Create authentication headers for testing."""
    return {"Authorization": f"Bearer {sample_user.id}"}

@pytest.fixture
def create_multiple_posts(db_session, sample_user):
    """Create multiple posts for testing."""
    posts = []
    for i in range(10):
        post = Post(
            title=f"Test Post {i}",
            content=f"Content for test post {i}",
            author_id=sample_user.id,
            is_published=True
        )
        db_session.add(post)
        posts.append(post)
    db_session.commit()
    return posts

# Custom assertions
def assert_valid_user_response(response_data):
    """Assert that user response data is valid."""
    required_fields = ["id", "username", "email", "is_active"]
    for field in required_fields:
        assert field in response_data, f"Missing required field: {field}"
    assert "hashed_password" not in response_data, "Password should not be returned"

def assert_valid_post_response(response_data):
    """Assert that post response data is valid."""
    required_fields = ["id", "title", "content", "author_id", "created_at"]
    for field in required_fields:
        assert field in response_data, f"Missing required field: {field}"
    assert "author" in response_data, "Author information should be included"

3. Pruebas End-to-End con Cypress

// cypress/e2e/user-journey.cy.js
describe('User Journey E2E Tests', () => {
  beforeEach(() => {
    // Clear cookies and localStorage before each test
    cy.clearCookies();
    cy.clearLocalStorage();
    
    // Visit the application
    cy.visit('/');
  });

  describe('Registration and Login Flow', () => {
    it('should allow user to register and login successfully', () => {
      const userData = {
        username: 'testuser',
        email: 'test@example.com',
        password: 'SecurePassword123!'
      };

      // Navigate to registration page
      cy.get('[data-testid="nav-register"]').click();
      cy.url().should('include', '/register');

      // Fill registration form
      cy.get('[data-testid="username-input"]').type(userData.username);
      cy.get('[data-testid="email-input"]').type(userData.email);
      cy.get('[data-testid="password-input"]').type(userData.password);
      cy.get('[data-testid="confirm-password-input"]').type(userData.password);

      // Submit registration
      cy.get('[data-testid="register-button"]').click();

      // Should redirect to login page
      cy.url().should('include', '/login');
      
      // Should show success message
      cy.get('[data-testid="success-message"]')
        .should('be.visible')
        .and('contain', 'Registration successful');

      // Login with new credentials
      cy.get('[data-testid="username-input"]').type(userData.username);
      cy.get('[data-testid="password-input"]').type(userData.password);
      cy.get('[data-testid="login-button"]').click();

      // Should redirect to dashboard
      cy.url().should('include', '/dashboard');
      
      // Should show user information
      cy.get('[data-testid="user-menu"]').should('contain', userData.username);
    });

    it('should show validation errors for invalid registration', () => {
      // Navigate to registration page
      cy.get('[data-testid="nav-register"]').click();

      // Submit empty form
      cy.get('[data-testid="register-button"]').click();

      // Should show validation errors
      cy.get('[data-testid="username-error"]').should('be.visible');
      cy.get('[data-testid="email-error"]').should('be.visible');
      cy.get('[data-testid="password-error"]').should('be.visible');

      // Try with invalid email
      cy.get('[data-testid="email-input"]').type('invalid-email');
      cy.get('[data-testid="register-button"]').click();
      
      cy.get('[data-testid="email-error"]')
        .should('be.visible')
        .and('contain', 'valid email');

      // Try with weak password
      cy.get('[data-testid="username-input"]').type('testuser');
      cy.get('[data-testid="password-input"]').type('123');
      cy.get('[data-testid="register-button"]').click();
      
      cy.get('[data-testid="password-error"]')
        .should('be.visible')
        .and('contain', 'at least 8 characters');
    });
  });

  describe('Post Creation and Management', () => {
    beforeEach(() => {
      // Login before each test
      cy.login('testuser', 'SecurePassword123!');
    });

    it('should create, edit, and delete a post successfully', () => {
      const postData = {
        title: 'My Test Post',
        content: 'This is the content of my test post. It should be long enough to be valid.',
        tags: ['testing', 'cypress', 'e2e']
      };

      // Navigate to create post page
      cy.get('[data-testid="nav-create-post"]').click();
      cy.url().should('include', '/posts/create');

      // Fill post form
      cy.get('[data-testid="title-input"]').type(postData.title);
      cy.get('[data-testid="content-textarea"]').type(postData.content);
      
      // Add tags
      postData.tags.forEach(tag => {
        cy.get('[data-testid="tag-input"]').type(`${tag}{enter}`);
      });

      // Submit form
      cy.get('[data-testid="create-post-button"]').click();

      // Should redirect to post detail page
      cy.url().should('match', /\/posts\/\d+/);

      // Verify post content
      cy.get('[data-testid="post-title"]').should('contain', postData.title);
      cy.get('[data-testid="post-content"]').should('contain', postData.content);
      
      // Verify tags
      postData.tags.forEach(tag => {
        cy.get('[data-testid="post-tags"]').should('contain', tag);
      });

      // Edit the post
      cy.get('[data-testid="edit-post-button"]').click();
      cy.url().should('match', /\/posts\/\d+\/edit/);

      const updatedTitle = 'Updated Test Post';
      cy.get('[data-testid="title-input"]').clear().type(updatedTitle);
      cy.get('[data-testid="update-post-button"]').click();

      // Verify updated content
      cy.get('[data-testid="post-title"]').should('contain', updatedTitle);

      // Add a comment
      const commentText = 'This is a test comment';
      cy.get('[data-testid="comment-input"]').type(commentText);
      cy.get('[data-testid="add-comment-button"]').click();

      // Verify comment appears
      cy.get('[data-testid="comments-section"]')
        .should('contain', commentText);

      // Like the post
      cy.get('[data-testid="like-button"]').click();
      cy.get('[data-testid="like-button"]')
        .should('have.class', 'liked')
        .and('contain', '1');

      // Unlike the post
      cy.get('[data-testid="like-button"]').click();
      cy.get('[data-testid="like-button"]')
        .should('not.have.class', 'liked')
        .and('contain', '0');

      // Delete the post
      cy.get('[data-testid="delete-post-button"]').click();
      cy.get('[data-testid="confirm-delete"]').click();

      // Should redirect to posts list
      cy.url().should('include', '/posts');
      
      // Verify post is no longer in list
      cy.get('[data-testid="posts-list"]').should('not.contain', postData.title);
    });

    it('should search and filter posts', () => {
      // Create multiple posts with different content
      const posts = [
        { title: 'JavaScript Tutorial', content: 'Learn JavaScript programming', tags: ['javascript', 'tutorial'] },
        { title: 'Python Guide', content: 'Python programming guide', tags: ['python', 'guide'] },
        { title: 'React Patterns', content: 'Common React patterns', tags: ['react', 'patterns'] }
      ];

      posts.forEach(post => {
        cy.createPost(post.title, post.content, post.tags);
      });

      // Navigate to posts list
      cy.get('[data-testid="nav-posts"]').click();
      cy.url().should('include', '/posts');

      // Test search functionality
      cy.get('[data-testid="search-input"]').type('JavaScript');
      cy.get('[data-testid="search-button"]').click();

      // Should only show JavaScript post
      cy.get('[data-testid="posts-list"]')
        .should('contain', 'JavaScript Tutorial')
        .and('not.contain', 'Python Guide')
        .and('not.contain', 'React Patterns');

      // Clear search
      cy.get('[data-testid="clear-search"]').click();

      // Test tag filtering
      cy.get('[data-testid="tag-filter"]').select('javascript');
      
      cy.get('[data-testid="posts-list"]')
        .should('contain', 'JavaScript Tutorial')
        .and('not.contain', 'Python Guide');

      // Test sorting
      cy.get('[data-testid="sort-select"]').select('title');
      
      // Posts should be sorted alphabetically
      cy.get('[data-testid="posts-list"] [data-testid="post-title"]')
        .then($titles => {
          const titles = $titles.map((i, el) => Cypress.$(el).text()).get();
          const sortedTitles = [...titles].sort();
          expect(titles).to.deep.equal(sortedTitles);
        });
    });
  });

  describe('User Profile Management', () => {
    beforeEach(() => {
      cy.login('testuser', 'SecurePassword123!');
    });

    it('should update user profile successfully', () => {
      const profileData = {
        firstName: 'John',
        lastName: 'Doe',
        bio: 'Software developer passionate about testing',
        location: 'San Francisco, CA'
      };

      // Navigate to profile page
      cy.get('[data-testid="nav-profile"]').click();
      cy.url().should('include', '/profile');

      // Edit profile
      cy.get('[data-testid="edit-profile-button"]').click();

      // Fill profile form
      cy.get('[data-testid="first-name-input"]').type(profileData.firstName);
      cy.get('[data-testid="last-name-input"]').type(profileData.lastName);
      cy.get('[data-testid="bio-textarea"]').type(profileData.bio);
      cy.get('[data-testid="location-input"]').type(profileData.location);

      // Upload profile picture
      cy.get('[data-testid="avatar-upload"]').attachFile('fixtures/avatar.jpg');

      // Save profile
      cy.get('[data-testid="save-profile-button"]').click();

      // Verify profile updates
      cy.get('[data-testid="profile-display"]').should('contain', profileData.firstName);
      cy.get('[data-testid="profile-display"]').should('contain', profileData.lastName);
      cy.get('[data-testid="profile-display"]').should('contain', profileData.bio);
      cy.get('[data-testid="profile-display"]').should('contain', profileData.location);

      // Verify avatar is uploaded
      cy.get('[data-testid="profile-avatar"]')
        .should('have.attr', 'src')
        .and('match', /avatar/);
    });

    it('should change password successfully', () => {
      const newPassword = 'NewSecurePassword456!';

      // Navigate to settings
      cy.get('[data-testid="nav-settings"]').click();
      cy.url().should('include', '/settings');

      // Change password
      cy.get('[data-testid="current-password-input"]').type('SecurePassword123!');
      cy.get('[data-testid="new-password-input"]').type(newPassword);
      cy.get('[data-testid="confirm-new-password-input"]').type(newPassword);

      cy.get('[data-testid="change-password-button"]').click();

      // Should show success message
      cy.get('[data-testid="success-message"]')
        .should('be.visible')
        .and('contain', 'Password changed successfully');

      // Logout and login with new password
      cy.get('[data-testid="nav-logout"]').click();
      cy.url().should('include', '/login');

      cy.get('[data-testid="username-input"]').type('testuser');
      cy.get('[data-testid="password-input"]').type(newPassword);
      cy.get('[data-testid="login-button"]').click();

      // Should login successfully
      cy.url().should('include', '/dashboard');
    });
  });

  describe('Responsive Design', () => {
    it('should work on mobile devices', () => {
      // Set mobile viewport
      cy.viewport('iphone-x');

      // Test navigation menu
      cy.get('[data-testid="mobile-menu-toggle"]').should('be.visible').click();
      cy.get('[data-testid="mobile-menu"]').should('be.visible');

      // Test responsive layout
      cy.visit('/posts');
      cy.get('[data-testid="posts-grid"]').should('be.visible');
      
      // Posts should be in single column on mobile
      cy.get('[data-testid="posts-grid"]')
        .should('have.css', 'grid-template-columns')
        .and('match', /1fr/);

      // Test mobile forms
      cy.get('[data-testid="nav-create-post"]').click();
      cy.get('[data-testid="mobile-form"]').should('be.visible');
      
      // Form should be full width on mobile
      cy.get('[data-testid="create-post-form"]')
        .should('have.css', 'width')
        .and('match', /100%/);
    });

    it('should work on tablet devices', () => {
      // Set tablet viewport
      cy.viewport('ipad-2');

      // Test tablet layout
      cy.visit('/posts');
      cy.get('[data-testid="posts-grid"]').should('be.visible');
      
      // Posts should be in two columns on tablet
      cy.get('[data-testid="posts-grid"]')
        .should('have.css', 'grid-template-columns')
        .and('match', /1fr.*1fr/);
    });
  });

  describe('Performance Tests', () => {
    it('should load pages within acceptable time limits', () => {
      const pages = [
        '/',
        '/posts',
        '/dashboard',
        '/profile'
      ];

      pages.forEach(page => {
        cy.visit(page);
        
        // Check page load time
        cy.window().then((win) => {
          const loadTime = win.performance.timing.loadEventEnd - win.performance.timing.navigationStart;
          expect(loadTime).to.be.lessThan(3000); // 3 seconds
        });
      });
    });

    it('should handle large lists efficiently', () => {
      // Create many posts
      for (let i = 0; i < 100; i++) {
        cy.createPost(`Post ${i}`, `Content for post ${i}`, ['test']);
      }

      cy.visit('/posts');
      
      // Should implement pagination or virtual scrolling
      cy.get('[data-testid="posts-list"]').should('be.visible');
      
      // Should not load all 100 posts at once
      cy.get('[data-testid="post-item"]').should('have.length.lessThan', 50);
      
      // Test pagination
      cy.get('[data-testid="pagination"]').should('be.visible');
      cy.get('[data-testid="next-page"]').click();
      cy.url().should('include', 'page=2');
    });
  });

  describe('Accessibility Tests', () => {
    it('should be accessible to keyboard users', () => {
      // Test keyboard navigation
      cy.get('body').tab();
      cy.focused().should('have.attr', 'data-testid', 'skip-to-content');

      // Navigate through main navigation
      cy.get('[data-testid="nav-home"]').focus();
      cy.focused().should('have.attr', 'data-testid', 'nav-home');

      // Test form accessibility
      cy.visit('/register');
      cy.get('[data-testid="username-input"]').focus();
      cy.focused().should('have.attr', 'data-testid', 'username-input');

      // Check for proper labels
      cy.get('[data-testid="username-input"]').should('have.attr', 'aria-label');
      cy.get('[data-testid="password-input"]').should('have.attr', 'aria-label');
    });

    it('should have proper ARIA attributes', () => {
      cy.visit('/posts');
      
      // Check for proper ARIA roles
      cy.get('[data-testid="main-content"]').should('have.attr', 'role', 'main');
      cy.get('[data-testid="navigation"]').should('have.attr', 'role', 'navigation');
      
      // Check for ARIA labels
      cy.get('[data-testid="search-input"]').should('have.attr', 'aria-label', 'Search posts');
      cy.get('[data-testid="search-button"]').should('have.attr', 'aria-label', 'Search');
      
      // Check for ARIA descriptions
      cy.get('[data-testid="form-help"]').should('have.attr', 'aria-describedby');
    });
  });

  describe('Error Handling', () => {
    it('should handle network errors gracefully', () => {
      // Simulate network failure
      cy.intercept('POST', '/api/v1/posts', { forceNetworkError: true });
      
      cy.login('testuser', 'SecurePassword123!');
      cy.visit('/posts/create');
      
      // Fill and submit form
      cy.get('[data-testid="title-input"]').type('Test Post');
      cy.get('[data-testid="content-textarea"]').type('Test content');
      cy.get('[data-testid="create-post-button"]').click();
      
      // Should show error message
      cy.get('[data-testid="error-message"]')
        .should('be.visible')
        .and('contain', 'Network error');
    });

    it('should handle 404 errors', () => {
      cy.visit('/non-existent-page', { failOnStatusCode: false });
      
      // Should show 404 page
      cy.get('[data-testid="error-page"]').should('be.visible');
      cy.get('[data-testid="error-code"]').should('contain', '404');
      cy.get('[data-testid="error-message"]').should('contain', 'Page not found');
    });

    it('should handle server errors', () => {
      cy.intercept('GET', '/api/v1/posts', { statusCode: 500 });
      
      cy.visit('/posts');
      
      // Should show server error message
      cy.get('[data-testid="error-message"]')
        .should('be.visible')
        .and('contain', 'Server error');
    });
  });
});

// Custom commands for reusable actions
Cypress.Commands.add('login', (username, password) => {
  cy.visit('/login');
  cy.get('[data-testid="username-input"]').type(username);
  cy.get('[data-testid="password-input"]').type(password);
  cy.get('[data-testid="login-button"]').click();
  cy.url().should('include', '/dashboard');
});

Cypress.Commands.add('createPost', (title, content, tags = []) => {
  cy.visit('/posts/create');
  cy.get('[data-testid="title-input"]').type(title);
  cy.get('[data-testid="content-textarea"]').type(content);
  
  tags.forEach(tag => {
    cy.get('[data-testid="tag-input"]').type(`${tag}{enter}`);
  });
  
  cy.get('[data-testid="create-post-button"]').click();
  cy.url().should('match', /\/posts\/\d+/);
});

// cypress.config.js
module.exports = {
  e2e: {
    baseUrl: 'http://localhost:3000',
    supportFile: 'cypress/support/e2e.js',
    specPattern: 'cypress/e2e/**/*.cy.js',
    viewportWidth: 1280,
    viewportHeight: 720,
    video: true,
    screenshotOnRunFailure: true,
    defaultCommandTimeout: 10000,
    requestTimeout: 10000,
    responseTimeout: 10000,
    pageLoadTimeout: 30000,
    env: {
      username: 'testuser',
      password: 'SecurePassword123!'
    },
    setupNodeEvents(on, config) {
      // Custom task to clear test database
      on('task', {
        clearTestDb() {
          // Implementation to clear test database
          return null;
        },
        seedTestDb() {
          // Implementation to seed test database
          return null;
        }
      });
    }
  }
};

Pirámide de Pruebas de Software

Niveles de Pruebas

graph TD
    A[E2E Tests] --> B[Integration Tests]
    B --> C[Unit Tests]
    
    A1[<br/>Pruebas Rápidas<br/>Cobertura Alta<br/>Económicas] --> C
    B1[<br/>Medio<br/>Medio<br/>Medio] --> B
    C1[<br/>Pruebas Lentas<br/>Cobertura Baja<br/>Costosas] --> A
    
    style A fill:#ff9999
    style B fill:#ffcc99
    style C fill:#99ff99

Comparativa de Frameworks de Pruebas

Frameworks de Testing en JavaScript

FrameworkTipoCaracterísticasAplicación
JestUnit/IntegrationTodo en uno, Snapshot TestingReact/Node.js
MochaUnitFlexible, Chai AssertionNode.js/Browser
CypressE2ETodo en uno, Time TravelAplicaciones Web
PlaywrightE2EMulti-navegador, Ejecución ParalelaAplicaciones Web

Frameworks de Testing en Python

FrameworkTipoCaracterísticasAplicación
PyTestUnit/IntegrationFixtures, Pruebas ParametrizadasAplicaciones Python
UnittestUnitIntegrado, Estilo xUnitLibrería Estándar Python
SeleniumE2EAutomatización de NavegadoresAplicaciones Web
Robot FrameworkE2EConducido por Palabras ClaveAutomatización de Pruebas

TDD vs. BDD

Test-Driven Development (TDD)

graph LR
    A[Red] --> B[Green]
    B --> C[Refactor]
    C --> A
    
    A1[Escribe un test que falla] --> A
    B1[Implementa código mínimo] --> B
    C1[Mejora la calidad del código] --> C

Behavior-Driven Development (BDD)

Feature: User Registration
  As a new user
  I want to register an account
  So that I can access the application

  Scenario: Successful registration
    Given I am on the registration page
    When I fill in valid registration details
    And I submit the registration form
    Then I should see a success message
    And I should receive a confirmation email
    And my account should be created

Ventajas e Inconvenientes

Ventajas del Testing de Software

  • Garantía de Calidad: Detección temprana de errores
  • Prevención de Regresiones: Suite de pruebas automatizada
  • Documentación: Las pruebas actúan como documentación viva
  • Seguridad en Refactorización: Confianza al hacer cambios en el código
  • Reducción de Costos: Menores gastos en corrección de errores

Inconvenientes

  • Inversión de Tiempo: El desarrollo de pruebas requiere tiempo
  • Mantenimiento: Las pruebas deben actualizarse con el código
  • Complejidad: Configuración de pruebas complejas
  • Falsos Positivos: Las pruebas pueden fallar sin razón
  • Curva de Aprendizaje: Necesario aprender los frameworks

Preguntas Frecuentes de Examen

  1. ¿Cuál es la diferencia entre Unit Tests e Integration Tests? Unit Tests prueban componentes aislados, mientras que Integration Tests validan la interacción entre múltiples componentes.

  2. Explica el ciclo Red-Green-Refactor del TDD. Red: Escribe una prueba que falla, Green: Implementa código mínimo para pasar la prueba, Refactor: Mejora la calidad del código.

  3. ¿Cuándo utilizamos E2E Tests? Los E2E Tests se usan para flujos de usuario completos, validando el funcionamiento del sistema en su totalidad.

  4. ¿Cuáles son las ventajas de BDD? BDD mejora la comunicación entre desarrolladores y stakeholders, además de producir pruebas legibles y comprensibles.

Continúa en la Ruta de Aprendizaje de Testing de Software

El siguiente artículo en la ruta de aprendizaje de Testing de Software cubre Pirámide de pruebas: Unit, Integration y E2E Tests en proporción — cómo balancear adecuadamente las diferentes capas de pruebas.

Referencias Principales

  1. https://jestjs.io/
  2. https://docs.pytest.org/
  3. https://www.cypress.io/
  4. https://cucumber.io/
Volver al blog
Share:

Entradas relacionadas