Eine Legacy-Rails-App ohne Tests zu ändern, gleicht einer Operation ohne Röntgenbild. Sie wissen nicht, was Sie kaputt machen, bis es zu spät ist. Veraltete Ruby-Versionen, fehlende Dokumentation und eng gekoppelter Code machen Updates riskant. Regressionstests sind das Sicherheitsnetz, das dieses Risiko kontrollierbar macht.
Wie bereiten Sie eine Legacy-App auf Tests vor?
Codebestand analysieren
Starten Sie mit rake stats. Der Befehl zeigt die Grösse der App: Codezeilen, Controller, Modelle, vorhandene Tests. Viele Legacy-Apps haben Boilerplate-Tests mit minimaler Abdeckung.
Konzentrieren Sie die Analyse auf:
- Hochkomplexe Bereiche: Lange Methoden, verschachtelte Bedingungen
- Eng gekoppelter Code: Modelle, die direkt auf andere Modelle zugreifen
- Nicht dokumentierte Abschnitte: Code ohne Kommentare oder Tests
Prüfen Sie Gemfile und Gemfile.lock auf veraltete Bibliotheken. Achten Sie auf Warnungen bei bundle install oder beim Rails-Start. Diese deuten auf Kompatibilitätsprobleme mit modernen Testwerkzeugen hin.
Dokumentieren Sie alles: kritische Funktionen, bekannte Probleme, Abschnitte, die Refactoring brauchen. Das ist Ihr Fahrplan.
Testumgebung einrichten
- Separate Konfiguration in
config/environments/test.rb - Dedizierte Testdatenbank, die die Produktions-Engine spiegelt
- Umgebungsvariablen und Stubs für externe Dienste
- Transaktionsbasierte Fixtures und Datenbank-Bereinigung
- Logging speziell für Tests konfigurieren
RSpec oder Minitest?
| Framework | Vorteile | Nachteile |
|---|---|---|
| RSpec | Flexible Syntax, starke Matcher, grosse Community | Mehr Setup-Aufwand |
| Minitest | Leichtgewichtig, schnell, in Rails integriert | Weniger ausdrucksstark |
Für Legacy-Projekte empfehlen wir RSpec: Die ausdrucksstarke Syntax macht Tests für schlecht dokumentierten Code lesbarer. Installation:
# Gemfile
gem 'rspec-rails', group: [:development, :test]
# Terminal
bundle install
rails generate rspec:install
Wie fügen Sie Tests schrittweise hinzu?
Schritt 1: Kritische Funktionen identifizieren
Priorisieren Sie nach Geschäftsrisiko:
- Zahlungsabwicklung: Alles, was Geld bewegt
- Benutzerauthentifizierung: Login, Session-Management, Passwort-Reset
- Kern-Geschäftslogik: Die Prozesse, die Umsatz generieren
- Datenimporte/-exporte: Schnittstellen zu externen Systemen
Für jede Funktion dokumentieren Sie: Geschäftliche Bedeutung, erwartetes Verhalten, bekannte Randfälle.
Schritt 2: Unit-Tests für Kernlogik
Beginnen Sie mit den wichtigsten Methoden:
# spec/services/payment_service_spec.rb
RSpec.describe PaymentService do
describe '#process' do
it 'charges the correct amount' do
result = described_class.new(amount: 1500.75, currency: 'CHF').process
expect(result).to be_success
expect(result.charged_amount).to eq(1500.75)
end
it 'rejects negative amounts' do
result = described_class.new(amount: -100, currency: 'CHF').process
expect(result).to be_failure
end
it 'handles zero amount' do
result = described_class.new(amount: 0, currency: 'CHF').process
expect(result).to be_failure
end
end
end
Schritt 3: Integrationstests für Workflows
Testen Sie End-to-End-Prozesse: Registrierung, Login, Bestellung abschliessen. Decken Sie auch Fehlerfälle ab (Zahlungsfehler, ungültige Eingaben).
Schritt 4: Tests ausführen und Fehler analysieren
Fehler sind zu erwarten. Kategorisieren Sie:
- Echte Bugs: Code verhält sich falsch
- Test-Probleme: Test ist fehlerhaft geschrieben
- Kompatibilitätsprobleme: Veraltete Gems kollidieren mit Testwerkzeugen
Beheben Sie kritische Bugs zuerst. Widerstehen Sie der Versuchung, Tests anzupassen, um fehlerhaftes Verhalten durchzulassen.
Wie bleiben Regressionstests langfristig nützlich?
CI/CD-Integration
Automatisieren Sie die Tests:
- Unit-Tests bei jedem Commit (schnelles Feedback)
- Integrationstests bei Pull Requests (gründliche Prüfung)
- GitHub Actions oder GitLab CI konfigurieren
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- run: bundle exec rspec
Testabdeckung überwachen
SimpleCov zeigt Lücken auf:
require 'simplecov'
SimpleCov.start 'rails' do
minimum_coverage 70
refuse_coverage_drop
end
Steigern Sie die Mindestabdeckung schrittweise: 50 % > 60 % > 70 % > 80 %.
Tests aktuell halten
- Tests bei jeder Feature-Änderung aktualisieren
- Obsolete Tests für entfernte Features löschen
- Dokumentation der Testfälle pflegen
Praktische Umsetzung: Der USEO-Ansatz
Bei Legacy-Projekten, die wir übernehmen, gehen wir Regressionstests methodisch an:
- Characterization Tests zuerst: Bevor wir irgendetwas ändern, schreiben wir Tests, die das aktuelle Verhalten dokumentieren, auch wenn es fehlerhaft ist. Das gibt uns ein Sicherheitsnetz für alle folgenden Änderungen.
- Risiko-Matrix erstellen: Wir bewerten jede Funktion nach zwei Achsen: Geschäftskritikalität und Änderungshäufigkeit. Funktionen mit hohem Wert auf beiden Achsen bekommen zuerst Tests.
- Test-Doubles für externe Systeme: Legacy-Apps haben oft hartcodierte Abhängigkeiten zu Drittsystemen (Payment-Gateways, E-Mail-Provider). Wir führen Adapter-Patterns ein und mocken diese in Tests, bevor wir die eigentliche Logik testen.
- Parallele Test-Suites: Wir trennen schnelle Unit-Tests (< 30s) von langsamen Integrationstests. Entwickler führen die schnelle Suite lokal aus, die vollständige Suite läuft in CI.
In einem kürzlichen Projekt haben wir so innerhalb von 6 Wochen von 0 % auf 72 % Testabdeckung für die geschäftskritischen Pfade aufgebaut, ohne den laufenden Betrieb zu beeinträchtigen.
FAQs
Welche Funktionen sollten zuerst getestet werden?
Funktionen mit dem höchsten Geschäftsrisiko: Zahlungsabwicklung, Authentifizierung, Kerngeschäftslogik. Analysieren Sie vergangene Bug-Reports, um problematische Bereiche zu identifizieren.
Welche Herausforderungen gibt es bei RSpec in Legacy-Apps?
Eng gekoppelter Code erschwert das Isolieren von Komponenten. Beginnen Sie mit Integrationstests auf hoher Ebene. Nutzen Sie Test-Doubles (Mocks, Stubs) für schwer testbare Abhängigkeiten. Refactoring in kleinen Schritten verbessert die Testbarkeit über Zeit.