Skip to content
IRC-Coding IRC-Coding
Software Testing Grundlagen Unit Tests Integration Tests E2E Tests TDD BDD

Software Testing Grundlagen: Unit Tests, Integration Tests, E2E Tests, TDD & BDD

Software Testing Grundlagen mit Unit Tests, Integration Tests, E2E Tests, TDD und BDD. Test-Driven Development, Behavior-Driven Development mit praktischen Beispielen.

S

schutzgeist

2 min read

Software Testing Grundlagen: Unit Tests, Integration Tests, E2E Tests, TDD & BDD

Dieser Beitrag ist eine umfassende Einführung in die Software Testing Grundlagen – inklusive Unit Tests, Integration Tests, E2E Tests, TDD und BDD mit praktischen Beispielen.

In a Nutshell

Software Testing ist ein systematischer Prozess zur Überprüfung der Qualität und Funktionalität von Software, um sicherzustellen, dass sie den Anforderungen entspricht und fehlerfrei funktioniert.

Kompakte Fachbeschreibung

Software Testing ist die Überprüfung von Software auf Korrektheit, Vollständigkeit und Qualität durch gezielte Tests und Validierungsmethoden.

Kernkomponenten:

Unit Tests

  • Isolierte Tests: Einzelne Komponenten unabhängig testen
  • Schnelle Ausführung: Millisekunden bis Sekunden
  • Mocking: Abhängigkeiten simulieren
  • Coverage: Code-Abdeckung messen
  • Frameworks: JUnit, Jest, PyTest

Integration Tests

  • Komponenten-Integration: Mehrere Module zusammen testen
  • Datenbank-Tests: Persistenzschicht validieren
  • API-Tests: Schnittstellen zwischen Services testen
  • System-Integration: Gesamtsystem überprüfen
  • Test-Umgebungen: Staging/Test-Systeme

End-to-End (E2E) Tests

  • Benutzer-Szenarien: Komplette Workflows testen
  • Browser-Automatisierung: UI-Interaktionen simulieren
  • Cross-Browser: Verschiedene Browser testen
  • Mobile Tests: iOS/Android Anwendungen
  • Tools: Selenium, Cypress, Playwright

Test-Driven Development (TDD)

  • Red-Green-Refactor: Zyklischer Entwicklungsprozess
  • Test-First: Tests vor Implementierung schreiben
  • Kleine Schritte: Inkrementelle Entwicklung
  • Regression-Tests: Automatisierte Testsuite
  • Design-Verbesserung: Bessere Software-Architektur

Behavior-Driven Development (BDD)

  • Gherkin Syntax: Lesbare Testbeschreibungen
  • User Stories: Geschäftsanforderungen formulieren
  • Stakeholder-Kommunikation: Gemeinsames Verständnis
  • Cucumber: BDD Framework Implementierung
  • Living Documentation: Aktuelle Dokumentation

Prüfungsrelevante Stichpunkte

  • Unit Tests: Isolierte Tests einzelner Funktionen/Klassen
  • Integration Tests: Tests der Zusammenarbeit von Komponenten
  • E2E Tests: Komplette Anwender-Workflows von Anfang bis Ende
  • TDD: Test-Driven Development mit Red-Green-Refactor Zyklus
  • BDD: Behavior-Driven Development mit Gherkin Syntax
  • Test Pyramid: Mehr Unit Tests als Integration/E2E Tests
  • Mocking: Simulation von Abhängigkeiten für Tests
  • Code Coverage: Messung der Testabdeckung
  • IHK-relevant: Software-Qualitätssicherung und Testing-Strategien

Kernkomponenten

  1. Test Strategy: Planung und Priorisierung von Tests
  2. Test Automation: Automatisierte Testausführung
  3. Test Frameworks: Tools und Bibliotheken für Tests
  4. Continuous Testing: Integration in CI/CD Pipelines
  5. Test Data Management: Testdaten generieren und verwalten
  6. Test Reporting: Ergebnisse analysieren und dokumentieren
  7. Performance Testing: Last- und Stresstests
  8. Security Testing: Sicherheitslücken aufdecken

Praxisbeispiele

1. Unit Tests mit JavaScript und 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. Integration Tests mit Python und 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. End-to-End Tests mit 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;
        }
      });
    }
  }
};

Software Testing Pyramide

Test Levels

graph TD
    A[E2E Tests] --> B[Integration Tests]
    B --> C[Unit Tests]
    
    A1[<br/>Schnelle Tests<br/>Hohe Abdeckung<br/>Günstig] --> C
    B1[<br/>Mittel<br/>Mittel<br/>Mittel] --> B
    C1[<br/>Langsame Tests<br/>Geringe Abdeckung<br/>Teuer] --> A
    
    style A fill:#ff9999
    style B fill:#ffcc99
    style C fill:#99ff99

Test Frameworks Vergleich

JavaScript Testing Frameworks

FrameworkTypFeaturesAnwendung
JestUnit/IntegrationAll-in-One, Snapshot TestingReact/Node.js
MochaUnitFlexible, Chai AssertionNode.js/Browser
CypressE2EAll-in-One, Time TravelWeb Applications
PlaywrightE2EMulti-Browser, ParallelWeb Applications

Python Testing Frameworks

FrameworkTypFeaturesAnwendung
PyTestUnit/IntegrationFixtures, Parametrized TestsPython Applications
UnittestUnitBuilt-in, xUnit StylePython Standard Library
SeleniumE2EBrowser AutomationWeb Applications
Robot FrameworkE2EKeyword-DrivenTest Automation

TDD vs. BDD

Test-Driven Development (TDD)

graph LR
    A[Red] --> B[Green]
    B --> C[Refactor]
    C --> A
    
    A1[Schreiter fehlenden Test] --> A
    B1[Implementiere minimalen Code] --> B
    C1[Verbessere Code-Qualität] --> 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

Vorteile und Nachteile

Vorteile von Software Testing

  • Qualitätssicherung: Frühe Fehlererkennung
  • Regression-Verhinderung: Automatisierte Testsuite
  • Dokumentation: Tests als lebende Dokumentation
  • Refactoring-Sicherheit: Vertrauen bei Code-Änderungen
  • Kostenreduktion: Geringere Fehlerbehebungskosten

Nachteile

  • Zeitaufwand: Testentwicklung benötigt Zeit
  • Wartung: Tests müssen mit Code gewartet werden
  • Komplexität: Komplexe Testsetups
  • False Positives: Tests können fehlschlagen
  • Lernkurve: Test-Frameworks erlernen

Häufige Prüfungsfragen

  1. Was ist der Unterschied zwischen Unit Tests und Integration Tests? Unit Tests testen isolierte Komponenten, Integration Tests testen die Zusammenarbeit mehrerer Komponenten.

  2. Erklären Sie das TDD Red-Green-Refactor Zyklus! Red: Schreibe einen fehlenden Test, Green: Implementiere minimalen Code, Refactor: Verbessere Code-Qualität.

  3. Wann verwendet man E2E Tests? E2E Tests werden für komplette Benutzer-Workflows verwendet, um das Gesamtsystem zu validieren.

  4. Was sind die Vorteile von BDD? BDD verbessert die Kommunikation zwischen Entwicklern und Stakeholdern und erstellt lesbare, verständliche Tests.

Wichtigste Quellen

  1. https://jestjs.io/
  2. https://docs.pytest.org/
  3. https://www.cypress.io/
  4. https://cucumber.io/
Zurück zum Blog
Share:

Ähnliche Beiträge