Teststrategie für Smolitux UI
Einführung
Diese Teststrategie definiert den Ansatz für das Testen der Smolitux UI Komponentenbibliothek. Unser Ziel ist es, eine hohe Qualität und Zuverlässigkeit der Komponenten sicherzustellen, indem wir verschiedene Testebenen und -methoden kombinieren.
Testebenen
1. Unit-Tests
Unit-Tests prüfen die Funktionalität einzelner Komponenten in Isolation.
Werkzeuge:
- Jest als Test-Runner
- React Testing Library für komponentenbasierte Tests
- jest-axe für Barrierefreiheitstests
Testumfang:
- Rendering mit verschiedenen Props
- Interaktionen (Klicks, Eingaben, etc.)
- Zustandsänderungen
- Callback-Aufrufe
- Styling und Klassennamen
- Barrierefreiheit
Beispiel:
// Button.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from '../Button';
describe('Button', () => {
test('renders correctly with default props', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
test('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
test('is disabled when disabled prop is true', () => {
render(<Button disabled>Click me</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
});
2. Barrierefreiheitstests
Spezielle Tests zur Überprüfung der Barrierefreiheit von Komponenten.
Werkzeuge:
- jest-axe für automatisierte Barrierefreiheitstests
- cypress-axe für E2E-Barrierefreiheitstests
Testumfang:
- ARIA-Attribute
- Semantische HTML-Struktur
- Tastaturnavigation
- Farbkontrast
- Screenreader-Kompatibilität
Beispiel:
// Button.a11y.test.tsx
import React from 'react';
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
import { Button } from '../Button';
expect.extend(toHaveNoViolations);
describe('Button Accessibility', () => {
test('should not have accessibility violations', async () => {
const { container } = render(<Button>Click me</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
test('should not have accessibility violations when disabled', async () => {
const { container } = render(<Button disabled>Click me</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
3. Integrationstests
Integrationstests prüfen das Zusammenspiel mehrerer Komponenten.
Werkzeuge:
- Jest und React Testing Library
- Storybook für visuelle Tests
Testumfang:
- Komponentenkomposition
- Datenaustausch zwischen Komponenten
- Komplexe Interaktionen
- Zustandsmanagement
Beispiel:
// Form.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { Form, Input, Button } from '../index';
describe('Form Integration', () => {
test('submits form data when button is clicked', () => {
const handleSubmit = jest.fn();
render(
<Form onSubmit={handleSubmit}>
<Input name="username" defaultValue="testuser" />
<Button type="submit">Submit</Button>
</Form>
);
fireEvent.click(screen.getByRole('button'));
expect(handleSubmit).toHaveBeenCalledWith({ username: 'testuser' });
});
});
4. Visuelle Regressionstests
Visuelle Tests prüfen das Erscheinungsbild der Komponenten.
Werkzeuge:
- Storybook
- Playwright oder Cypress für Screenshots
- Reg-suit oder Percy für visuelle Vergleiche
Testumfang:
- Layout und Styling
- Responsives Design
- Themes und Farbschemata
- Animationen und Übergänge
Beispiel:
// visual.test.js
const { test, expect } = require('@playwright/test');
test('Button visual regression', async ({ page }) => {
await page.goto('http://localhost:6006/iframe.html?id=core-inputs-button--primary');
// Vergleiche Screenshot mit Referenz
await expect(page).toHaveScreenshot('button-primary.png');
});
5. End-to-End-Tests
E2E-Tests prüfen die Komponenten in einer realistischen Umgebung.
Werkzeuge:
- Cypress oder Playwright
- Storybook als Testumgebung
Testumfang:
- Benutzerflüsse
- Browserkompatibilität
- Leistung und Ladezeiten
- Netzwerkinteraktionen
Beispiel:
// button.cy.js
describe('Button Component', () => {
beforeEach(() => {
cy.visit('http://localhost:6006/iframe.html?id=core-inputs-button--primary');
});
it('should handle click events', () => {
cy.get('button').click();
// Prüfe Ergebnis des Klicks
});
it('should be accessible', () => {
cy.injectAxe();
cy.checkA11y();
});
});
Testabdeckung
Wir streben folgende Testabdeckung an:
- Unit-Tests: >90% Codeabdeckung
- Barrierefreiheitstests: 100% der Komponenten
- Integrationstests: Alle wichtigen Komponentenkombinationen
- Visuelle Tests: Alle Komponenten in allen Varianten
- E2E-Tests: Kritische Benutzerflüsse
Testautomatisierung
Continuous Integration
Tests werden automatisch bei jedem Pull Request ausgeführt:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Run accessibility tests
run: npm run test:a11y
Pre-commit Hooks
Lokale Tests vor dem Commit:
// package.json
{
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix",
"jest --findRelatedTests"
]
}
}
Testdaten
Mock-Daten
Für konsistente Tests verwenden wir Mock-Daten:
// mocks/data.ts
export const mockUser = {
id: '1',
name: 'Test User',
email: 'test@example.com'
};
export const mockProducts = [
{ id: '1', name: 'Product 1', price: 10 },
{ id: '2', name: 'Product 2', price: 20 }
];
Test-Utilities
Wiederverwendbare Test-Utilities:
// test-utils.tsx
import React, { ReactElement } from 'react';
import { render, RenderOptions } from '@testing-library/react';
import { ThemeProvider } from '../ThemeProvider';
const AllProviders = ({ children }: { children: React.ReactNode }) => {
return (
<ThemeProvider>
{children}
</ThemeProvider>
);
};
const customRender = (
ui: ReactElement,
options?: Omit<RenderOptions, 'wrapper'>
) => render(ui, { wrapper: AllProviders, ...options });
export * from '@testing-library/react';
export { customRender as render };
Testumgebung
Setup
// jest.setup.js
import '@testing-library/jest-dom';
import 'jest-axe/extend-expect';
// Mock für window.matchMedia
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
Konfiguration
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
testMatch: [
'**/__tests__/**/*.test.[jt]s?(x)',
'**/?(*.)+(spec|test).[jt]s?(x)'
],
testPathIgnorePatterns: ['/node_modules/', '/.next/'],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
},
moduleNameMapper: {
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
},
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/**/*.stories.{js,jsx,ts,tsx}'
],
};
Testdokumentation
Testplan
Für jede Komponente wird ein Testplan erstellt:
# Testplan für Button-Komponente
## Unit-Tests
- [ ] Rendering mit verschiedenen Varianten (primary, secondary, etc.)
- [ ] Rendering mit verschiedenen Größen
- [ ] Klick-Event-Handling
- [ ] Disabled-Zustand
## Barrierefreiheitstests
- [ ] ARIA-Attribute
- [ ] Tastaturnavigation
- [ ] Farbkontrast
## Visuelle Tests
- [ ] Alle Varianten und Größen
- [ ] Hover- und Fokus-Zustände
- [ ] Dark Mode
## E2E-Tests
- [ ] Klick-Interaktionen
- [ ] Formular-Submission
Testberichte
Nach jedem Testlauf werden Berichte generiert:
- Jest Coverage Report
- Axe Accessibility Report
- Visual Regression Report
- E2E Test Report
Best Practices
- Testpyramide: Mehr Unit-Tests als Integrationstests, mehr Integrationstests als E2E-Tests.
- Testbare Komponenten: Komponenten so gestalten, dass sie leicht zu testen sind.
- Deterministische Tests: Tests sollten immer die gleichen Ergebnisse liefern.
- Isolierte Tests: Tests sollten unabhängig voneinander sein.
- Aussagekräftige Tests: Tests sollten klar dokumentieren, was getestet wird.
- Schnelle Tests: Tests sollten schnell ausgeführt werden können.
- Wartbare Tests: Tests sollten einfach zu warten sein.
Fehlerbehebung
Häufige Probleme
-
Flaky Tests: Tests, die manchmal fehlschlagen
- Lösung: Deterministische Mocks, stabile Selektoren, Wartezeiten
-
Langsame Tests: Tests, die zu lange dauern
- Lösung: Parallelisierung, Mocking, Fokus auf Unit-Tests
-
Schwer zu wartende Tests: Tests, die bei kleinen Änderungen brechen
- Lösung: Testen von Verhalten statt Implementierung, stabile Selektoren