Was mit Ihrem Code passiert, bevor er in Produktion geht

Sie haben gerade ein Feature fertiggestellt. Sie testen es lokal, es funktioniert, Sie pushen den Code. Was passiert jetzt?

Zwischen diesem Push und dem Moment, in dem Ihr Code in Produktion läuft, muss eine Menge passieren. Nicht nur das Erstellen des Artefakts, sondern auch die Prüfung, ob der Code tatsächlich sicher, korrekt und wartbar ist. Wenn Sie diese Prüfungen überspringen, setzen Sie darauf, dass jeder Entwickler im Team jedes Mal perfekten Code schreibt. Das tut niemand.

Der Prozess, bei dem jedes Mal, wenn jemand Code pusht, automatisierte Prüfungen durchgeführt werden, heißt Continuous Integration (CI). Die Idee ist einfach: Probleme früh erkennen, wenn sie billig zu beheben sind. Ein Fehler, der fünf Minuten nach einem Push gefunden wird, kostet einen Kaffee. Ein Fehler, der in Produktion gefunden wird, kostet einen Incident-Report, einen Rollback und eine lange Nacht.

Beginnen Sie mit Unit-Tests

Das Erste, was die meisten Pipelines ausführen, sind Unit-Tests. Aber was als Unit-Test zählt, ist wichtiger, als die meisten Teams glauben.

Ein guter Unit-Test testet nicht einfach eine einzelne Funktion oder Methode, nur weil es sie gibt. Er testet ein sinnvolles Verhalten von einem relevanten Einstiegspunkt aus. Für einen Backend-Service ist dieser Einstiegspunkt normalerweise ein API-Endpunkt oder ein Use Case. Der Test ruft diesen Endpunkt auf und prüft, ob die Antwort Ihren Erwartungen entspricht. Im Hintergrund durchläuft die Anfrage den Controller, die Service-Schicht, die Domänenlogik und die Repository-Grenze. Der interne Anwendungspfad wird tatsächlich ausgeführt; externe Dienste wie Produktionsdatenbanken, Message Queues und Drittanbieter-APIs werden durch kontrollierte Test-Doubles, lokale Testinstanzen, Mocks oder Stubs ersetzt.

Dieser Ansatz hat einen praktischen Vorteil: Sie können die interne Implementierung einer Funktion ändern, ohne den Test zu brechen. Der Test kümmert sich darum, was das System tut, nicht wie es es tut. Das bedeutet, Sie können frei refaktorieren, solange das Verhalten gleich bleibt.

Unit-Tests sollten schnell sein. Wenn sie länger als ein paar Sekunden dauern, stimmt etwas nicht. Weil sie schnell sind, können Sie sie bei jedem Commit ausführen. Das gibt Entwicklern sofortiges Feedback: "Ihre Änderung hat etwas kaputt gemacht, beheben Sie es jetzt."

Linting erkennt Stil und Code-Gerüche

Nachdem die Unit-Tests bestanden sind, folgt in der Regel die Linting-Prüfung. Linting testet keine Logik. Es prüft, ob der Code konsistenten Stilregeln folgt und ob es Muster gibt, die oft zu Fehlern führen.

Ein Linter erkennt Dinge wie:

  • Eine deklarierte, aber nie verwendete Variable
  • Eine Funktion, die zu lang ist und aufgeteilt werden sollte
  • Inkonsistente Einrückungen oder Benennungen
  • Potenziell unsichere Muster, die korrekt aussehen, es aber nicht sind

Linting mag im Vergleich zur Korrektheit wie ein nebensächliches Anliegen wirken, aber es ist wichtiger, als Sie denken. Code, der konsistent aussieht, ist leichter zu lesen. Code, der leichter zu lesen ist, ist leichter zu reviewen. Code, der leichter zu reviewen ist, hat weniger Fehler. Es ist eine Kette, die mit einer einfachen automatisierten Prüfung beginnt.

Integrationstests prüfen die Verbindungen

Unit-Tests beweisen, dass das Anwendungsverhalten funktioniert, während externe Nachbarn kontrolliert werden. Integrationstests beweisen, dass die Anwendung mit echten Abhängigkeiten funktionieren kann.

Für einen Backend-Service könnte ein Integrationstest eine echte Testdatenbank hochfahren, den Service starten und überprüfen, ob ein API-Aufruf tatsächlich Daten korrekt liest und schreibt. Oder er könnte testen, ob ein Hintergrund-Worker eine Nachricht an eine Queue senden und die Antwort verarbeiten kann.

Integrationstests sind langsamer als Unit-Tests. Sie brauchen echte Abhängigkeiten: eine Datenbank, eine Queue, vielleicht einen Cache. Deshalb laufen sie nach den Unit-Tests, nicht davor. Wenn ein Unit-Test fehlschlägt, macht es keinen Sinn, Integrationstests auszuführen. Die Änderung ist bereits kaputt.

Manche Teams führen Integrationstests gegen eine dedizierte Testumgebung durch, die die Produktion so genau wie möglich abbildet. Andere führen sie innerhalb der CI-Pipeline mit Containern durch. In beiden Fällen ist das Ziel dasselbe: Probleme erkennen, die nur auftreten, wenn Komponenten interagieren.

Sicherheitsscans suchen nach Schwachstellen in Ihrem Code

Sicherheitsscans prüfen den von Ihnen geschriebenen Code auf häufige Schwachstellen. Dinge wie SQL-Injection, Cross-Site-Scripting oder die falsche Verwendung kryptografischer Funktionen.

Diese Scans sind automatisierte Werkzeuge, die nach Mustern suchen, die als gefährlich bekannt sind. Sie sind nicht perfekt. Sie können Dinge übersehen und Fehlalarme produzieren. Aber sie erkennen die offensichtlichen Probleme, die menschliche Prüfer übersehen könnten.

Eine Schwachstelle, die während der CI entdeckt wird, kostet ein Ticket und eine Behebung. Eine Schwachstelle, die in Produktion entdeckt wird, kostet einen Sicherheitsvorfall, eine Offenlegung und viel Vertrauen.

Abhängigkeitsprüfungen suchen nach Schwachstellen in Ihren Bibliotheken

Fast kein Backend-Service wird von Grund auf neu geschrieben. Sie verwenden Frameworks, Bibliotheken und Pakete. Jedes davon ist Code, der von jemand anderem geschrieben wurde, und dieser Code kann Schwachstellen enthalten.

Die Abhängigkeitsprüfung vergleicht die Versionen Ihrer Bibliotheken mit öffentlichen Schwachstellendatenbanken. Wenn eine von Ihnen verwendete Bibliothek ein bekanntes Sicherheitsproblem hat, wird die Prüfung dies melden. Einige Teams konfigurieren die Pipeline so, dass der Build blockiert wird, wenn eine kritische Schwachstelle gefunden wird. Andere lassen ihn passieren, benachrichtigen aber das Team zur manuellen Überprüfung.

Diese Prüfung ist wichtig, weil Schwachstellen in Abhängigkeiten häufig und oft schwerwiegend sind. Der Log4j-Vorfall im Jahr 2021 ist ein bekanntes Beispiel: Eine weit verbreitete Logging-Bibliothek hatte eine Schwachstelle, die Remote Code Execution ermöglichte. Teams, die eine Abhängigkeitsprüfung in ihrer Pipeline hatten, fanden es schnell heraus. Teams, die keine hatten, brauchten Tage, um herauszufinden, was sie verwendeten.

Die Reihenfolge der Prüfungen ist wichtig

Die Reihenfolge dieser Prüfungen ist nicht zufällig. Sie folgt einer praktischen Logik:

Das folgende Diagramm zeigt den Pipeline-Fluss und welche Prüfungen den Build blockieren.

Hier ist ein minimales GitHub Actions-Workflow, der diese Reihenfolge erzwingt:

name: CI Pipeline

on: [push]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - run: npm run lint

  unit-test:
    needs: lint
    runs-on: ubuntu-latest
    steps:
      - run: npm test -- --coverage

  integration-test:
    needs: unit-test
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
    steps:
      - run: npm run test:integration

  security-scan:
    needs: integration-test
    runs-on: ubuntu-latest
    steps:
      - run: npm run audit

  dependency-check:
    needs: security-scan
    runs-on: ubuntu-latest
    steps:
      - run: npm audit --audit-level=high
flowchart TD A[Code Push] --> B[Unit Tests] B -->|Bestanden| C[Linting] B -->|Fehlgeschlagen| X[Pipeline blockieren] C -->|Bestanden| D[Integrationstests] C -->|Fehlgeschlagen| X D -->|Bestanden| E[Sicherheitsscans] D -->|Fehlgeschlagen| X E -->|Bestanden| F[Abhängigkeitsprüfungen] E -->|Warnung| F E -->|Fehlgeschlagen| X F -->|Bestanden| G[Artefakt erstellen] F -->|Warnung| G F -->|Fehlgeschlagen| X X --> H[Entwickler benachrichtigen]
  1. Unit-Tests und Linting laufen zuerst, weil sie schnell sind. Sie filtern sofort offensichtlich kaputte oder unordentliche Änderungen heraus.
  2. Integrationstests laufen als nächstes. Sie sind langsamer, aber immer noch wichtig. Wenn Unit-Tests bestanden werden, aber Integrationstests fehlschlagen, wissen Sie, dass die Komponenten nicht zusammenarbeiten.
  3. Sicherheitsscans und Abhängigkeitsprüfungen laufen zuletzt. Sie sind oft die langsamsten und fallen bei einer typischen Änderung seltener aus.

Diese Reihenfolge gibt Entwicklern schnelles Feedback für häufige Probleme. Wenn Sie einen Unit-Test brechen, wissen Sie es innerhalb von Sekunden. Wenn Sie eine verwundbare Abhängigkeit einführen, finden Sie es innerhalb von Minuten heraus, nicht Tagen.

Nicht jede Prüfung muss die Pipeline blockieren

Manche Teams führen alle Prüfungen als blockierend durch: Wenn eine Prüfung fehlschlägt, stoppt die Pipeline und das Artefakt wird nicht erstellt. Andere Teams erlauben, dass bestimmte Prüfungen mit Warnungen durchlaufen, insbesondere Sicherheitsscans, die Fehlalarme produzieren könnten.

Das Wichtige ist die Konsistenz. Jede Änderung durchläuft die gleichen Prüfungen. Qualität hängt nicht davon ab, ob ein Entwickler daran gedacht hat, Tests lokal auszuführen. Sie hängt von der Pipeline ab.

Eine praktische Checkliste für Ihre CI-Pipeline

Wenn Sie eine CI-Pipeline für einen Backend-Service einrichten oder überprüfen, hier ist eine kurze Checkliste:

  • Unit-Tests laufen bei jedem Commit und sind in unter einer Minute abgeschlossen
  • Linting läuft vor oder zusammen mit den Unit-Tests
  • Integrationstests laufen gegen echte Abhängigkeiten in einer kontrollierten Umgebung
  • Sicherheitsscans prüfen Ihren eigenen Code auf häufige Schwachstellen
  • Abhängigkeitsprüfungen vergleichen Ihre Bibliotheken mit bekannten Schwachstellendatenbanken
  • Die Pipeline schlägt schnell fehl: schnelle Prüfungen laufen zuerst, langsamere Prüfungen später
  • Jede Änderung durchläuft die gleichen Prüfungen, unabhängig davon, wer sie geschrieben hat

Was als Nächstes kommt

Sobald alle diese Prüfungen bestanden sind, wird das Artefakt erstellt und ist bereit. Aber das Erstellen des Artefakts ist nur die halbe Geschichte. Die nächste Frage ist, wie man diese neue Version auf Servern bereitstellt, ohne Benutzer zu stören. Hier kommen Bereitstellungsstrategien ins Spiel, und das werden wir als Nächstes behandeln.

Für den Moment ist die Erkenntnis diese: Die Qualität Ihres Produktionssystems wird lange vor der Produktion bestimmt. Sie wird in den Minuten nach jedem Push bestimmt, wenn automatisierte Prüfungen entscheiden, ob Ihr Code sicher ist, fortzufahren. Machen Sie diese Prüfungen schnell, machen Sie sie konsistent, und lassen Sie sie jedes Mal laufen.