Testing Arten Unit Integration E2E
Testing: Unit, Integration und E2E Tests
Verschiedene Test-Arten dienen verschiedenen Zwecken. Lernen Sie, welche Tests wann sinnvoll sind und wie Sie eine gute Teststrategie aufbauen.
Die Test-Pyramide
/\
/ \
/ E2E\ Wenige, langsam, teuer
/------\
/ \
/Integration\ Mittelviele
/--------------\
/ \
/ Unit Tests \ Viele, schnell, günstig
--------------------
Vergleich
| Aspekt | Unit | Integration | E2E |
|---|---|---|---|
| Scope | Einzelne Funktion | Mehrere Komponenten | Gesamtes System |
| Geschwindigkeit | Sehr schnell (ms) | Schnell (s) | Langsam (min) |
| Isolation | Komplett | Teilweise | Keine |
| Dependencies | Gemockt | Teilweise echt | Alle echt |
| Debugging | Einfach | Mittel | Schwer |
Unit Tests
// JavaScript (Jest)
// math.js
export function add(a, b) {
return a + b;
}
export function divide(a, b) {
if (b === 0) throw new Error('Division by zero');
return a / b;
}
// math.test.js
import { add, divide } from './math';
describe('Math functions', () => {
describe('add', () => {
test('adds two positive numbers', () => {
expect(add(2, 3)).toBe(5);
});
test('adds negative numbers', () => {
expect(add(-1, -2)).toBe(-3);
});
});
describe('divide', () => {
test('divides two numbers', () => {
expect(divide(10, 2)).toBe(5);
});
test('throws on division by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
});
});
Mocking in Unit Tests
// userService.js
export class UserService {
constructor(database) {
this.db = database;
}
async getUser(id) {
const user = await this.db.findById(id);
if (!user) throw new Error('User not found');
return user;
}
}
// userService.test.js
describe('UserService', () => {
let service;
let mockDb;
beforeEach(() => {
// Mock erstellen
mockDb = {
findById: jest.fn()
};
service = new UserService(mockDb);
});
test('returns user when found', async () => {
const mockUser = { id: 1, name: 'Max' };
mockDb.findById.mockResolvedValue(mockUser);
const user = await service.getUser(1);
expect(user).toEqual(mockUser);
expect(mockDb.findById).toHaveBeenCalledWith(1);
});
test('throws when user not found', async () => {
mockDb.findById.mockResolvedValue(null);
await expect(service.getUser(999)).rejects.toThrow('User not found');
});
});
Integration Tests
// API Integration Test (Supertest)
import request from 'supertest';
import app from './app';
import { db } from './database';
describe('Users API', () => {
beforeAll(async () => {
await db.migrate.latest();
});
beforeEach(async () => {
await db('users').truncate();
});
afterAll(async () => {
await db.destroy();
});
describe('GET /api/users/:id', () => {
test('returns user when exists', async () => {
// Arrange: Echte Daten in Test-DB
const [id] = await db('users').insert({
email: 'test@example.com',
name: 'Test User'
});
// Act
const response = await request(app)
.get(`/api/users/${id}`)
.expect(200);
// Assert
expect(response.body.data).toMatchObject({
email: 'test@example.com',
name: 'Test User'
});
});
test('returns 404 when not found', async () => {
await request(app)
.get('/api/users/999')
.expect(404);
});
});
describe('POST /api/users', () => {
test('creates user with valid data', async () => {
const response = await request(app)
.post('/api/users')
.send({
email: 'new@example.com',
name: 'New User',
password: 'secret123'
})
.expect(201);
expect(response.body.data.id).toBeDefined();
// Verifizieren in DB
const user = await db('users').where({ id: response.body.data.id }).first();
expect(user.email).toBe('new@example.com');
});
});
});
E2E Tests (Playwright)
npm install -D @playwright/test npx playwright install
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Login Flow', () => {
test('successful login redirects to dashboard', async ({ page }) => {
// Arrange
await page.goto('/login');
// Act
await page.fill('[name="email"]', 'user@example.com');
await page.fill('[name="password"]', 'password123');
await page.click('button[type="submit"]');
// Assert
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('h1')).toHaveText('Dashboard');
});
test('shows error on invalid credentials', async ({ page }) => {
await page.goto('/login');
await page.fill('[name="email"]', 'wrong@example.com');
await page.fill('[name="password"]', 'wrong');
await page.click('button[type="submit"]');
await expect(page.locator('.error-message')).toBeVisible();
await expect(page.locator('.error-message')).toContainText('Invalid credentials');
});
});
test.describe('Shopping Cart', () => {
test.beforeEach(async ({ page }) => {
// Login vor jedem Test
await page.goto('/login');
await page.fill('[name="email"]', 'user@example.com');
await page.fill('[name="password"]', 'password123');
await page.click('button[type="submit"]');
});
test('can add product to cart', async ({ page }) => {
await page.goto('/products');
await page.click('[data-testid="product-1"] button');
await expect(page.locator('.cart-count')).toHaveText('1');
});
});
PHP Testing (PHPUnit)
<?php
// Unit Test
class CalculatorTest extends TestCase
{
public function test_addition(): void
{
$calculator = new Calculator();
$this->assertEquals(5, $calculator->add(2, 3));
}
}
// Integration Test (Laravel)
class UserApiTest extends TestCase
{
use RefreshDatabase;
public function test_can_create_user(): void
{
$response = $this->postJson('/api/users', [
'email' => 'test@example.com',
'name' => 'Test User',
'password' => 'secret123'
]);
$response->assertStatus(201);
$this->assertDatabaseHas('users', [
'email' => 'test@example.com'
]);
}
}
Teststrategie
Empfohlene Verteilung:
- 70% Unit Tests: Schnell, isoliert, für Business-Logik
- 20% Integration Tests: API-Endpoints, DB-Interaktionen
- 10% E2E Tests: Kritische User-Flows (Login, Checkout)