Skip to content
IRC-CodingIRC-Coding
TestabdeckungCode CoverageLine CoverageBranch CoverageMutation TestingJestIstanbulSoftwarequalität

Softwarequalität und Testabdeckung: Code Coverage, Metriken und Strategien

Testabdeckung und Code Coverage: Line, Branch, Path Coverage, Mutation Testing, Coverage-Ziele und Praxisbeispiel mit Jest und Istanbul.

S

schutzgeist

6 min read
Softwarequalität und Testabdeckung: Code Coverage, Metriken und Strategien

Softwarequalität und Testabdeckung

Testabdeckung (Code Coverage) ist eine der wichtigsten Metriken in der Softwarequalität. Sie misst, wie viel Prozent des Codes durch automatisierte Tests ausgeführt werden. Doch hohe Testabdeckung allein garantiert keine hohe Qualität — es kommt darauf an, was und wie gemessen wird.

In a Nutshell

Testabdeckung quantifiziert den Anteil des Codes, der durch Tests ausgeführt wird. Die wichtigsten Metriken sind Line Coverage, Branch Coverage und Function Coverage. Ein Coverage-Wert von 80% bedeutet: 80% der Zeilen wurden während der Testausführung mindestens einmal erreicht. Das sagt aber nichts darüber aus, ob die Tests auch die richtigen Assertions enthalten.

Kompakte Fachbeschreibung

Code Coverage ist ein Maß für den Grad, in dem der Quellcode eines Programms durch Tests ausgeführt wird. Sie wird als Prozentsatz ausgedrückt und kann auf verschiedenen Ebenen gemessen werden: Zeilen, Anweisungen, Zweigen (Branches), Funktionen und Pfaden. Coverage-Tools instrumentieren den Code während der Ausführung und zeichnen auf, welche Code-Bereiche durchlaufen wurden. Die Metrik hilft, ungetestete Bereiche zu identifizieren, ist aber keine Garantie für Fehlerfreiheit — ein Test kann eine Zeile ausführen, ohne das erwartete Verhalten zu verifizieren.

Wer braucht Testabdeckung und warum?

Testabdeckung ist relevant für:

  • Entwickler: um zu sehen, welche Code-Bereiche noch ungetestet sind
  • Teams: um eine Mindestqualitätsschwelle zu definieren
  • QA-Engineers: um Risiko-Bereiche zu identifizieren
  • Projektmanager: um den Reifegrad der Teststrategie zu bewerten
  • Compliance: in regulierten Branchen (Medizin, Automotive, Finanzwesen) sind Coverage-Schwellen oft vorgeschrieben

Coverage-Metriken im Detail

Line Coverage (Zeilenabdeckung)

Misst, wie viele Zeilen des Quellcodes durch Tests ausgeführt wurden. Die einfachste und am häufigsten verwendete Metrik.

function classify(score) {
  if (score >= 90) return 'A';    // Zeile 1
  if (score >= 80) return 'B';    // Zeile 2
  if (score >= 70) return 'C';    // Zeile 3
  return 'F';                      // Zeile 4
}

Ein Test mit classify(95) deckt nur Zeile 1 ab → 25% Line Coverage. Tests mit classify(95) und classify(65) decken Zeile 1 und 4 ab → 50%.

Branch Coverage (Zweigabdeckung)

Misst, wie viele Verzweigungen (if/else, switch, ternary) durch Tests abgedeckt wurden. Jede Verzweigung hat zwei Zweige: true und false.

function classify(score) {
  if (score >= 90) return 'A';    // Branch 1: true/false
  if (score >= 80) return 'B';    // Branch 2: true/false
  if (score >= 70) return 'C';    // Branch 3: true/false
  return 'F';
}

6 Branches insgesamt (3× true, 3× false). Ein Test mit classify(95) deckt Branch 1-true ab, aber nicht Branch 1-false → 1/6 = 17% Branch Coverage.

Function Coverage (Funktionsabdeckung)

Misst, wie viele Funktionen im Code mindestens einmal aufgerufen wurden. Besonders wichtig für Module mit vielen kleinen Funktionen.

Path Coverage (Pfadabdeckung)

Misst, wie viele möglichen Ausführungspfade durch den Code abgedeckt wurden. Die strengste Metrik, da sie alle Kombinationen von Verzweigungen betrachtet. Bei n unabhängigen Verzweigungen gibt es 2^n Pfade — Path Coverage ist bei komplexem Code oft unrealistisch.

Statement Coverage (Anweisungsabdeckung)

Ähnlich wie Line Coverage, bezieht sich aber auf einzelne Anweisungen statt Zeilen. Eine Zeile kann mehrere Anweisungen enthalten.

Praxisbeispiel: Jest mit Istanbul Coverage

Setup

// package.json
{
  "scripts": {
    "test": "jest",
    "test:coverage": "jest --coverage"
  },
  "jest": {
    "collectCoverageFrom": [
      "src/**/*.js",
      "!src/**/*.spec.js",
      "!src/index.js"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80,
        "statements": 80
      }
    }
  }
}

Beispiel-Modul

// src/utils/discount.js
export function calculateDiscount(price, customerType) {
  if (typeof price !== 'number' || price < 0) {
    throw new Error('Price must be a non-negative number');
  }

  switch (customerType) {
    case 'premium':
      return price * 0.8;   // 20% Rabatt
    case 'vip':
      return price * 0.7;   // 30% Rabatt
    case 'standard':
      return price;          // Kein Rabatt
    default:
      throw new Error(`Unknown customer type: ${customerType}`);
  }
}

Tests mit vollständiger Branch Coverage

// src/utils/discount.spec.js
import { calculateDiscount } from './discount.js';

describe('calculateDiscount', () => {
  test('premium customer gets 20% discount', () => {
    expect(calculateDiscount(100, 'premium')).toBe(80);
  });

  test('vip customer gets 30% discount', () => {
    expect(calculateDiscount(100, 'vip')).toBe(70);
  });

  test('standard customer gets no discount', () => {
    expect(calculateDiscount(100, 'standard')).toBe(100);
  });

  test('throws on negative price', () => {
    expect(() => calculateDiscount(-1, 'premium'))
      .toThrow('Price must be a non-negative number');
  });

  test('throws on non-number price', () => {
    expect(() => calculateDiscount('100', 'premium'))
      .toThrow('Price must be a non-negative number');
  });

  test('throws on unknown customer type', () => {
    expect(() => calculateDiscount(100, 'unknown'))
      .toThrow('Unknown customer type: unknown');
  });
});

Coverage-Report

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |
 discount |     100 |      100 |     100 |     100 |
----------|---------|----------|---------|---------|-------------------

Coverage-Ziele: Welche Werte sind sinnvoll?

ProjekttypEmpfohlene CoverageBegründung
Bibliothek / SDK90-100%Hohe Stabilität, viele Konsumenten
Web-Anwendung70-80%Pragmatisch, Fokus auf Geschäftslogik
Prototyp / MVP40-60%Schnelle Iteration, Tests für Kernlogik
Regulierte Software100% BranchZwingend erforderlich (IEC 62304, DO-178C)
Legacy-Code50%+ schrittweiseErhöhung bei Refactoring

Faustregel: 80% Line Coverage mit guten Assertions ist wertvoller als 100% Coverage ohne Assertions.

Häufige Fallstricke

1. 100% Coverage ohne Assertions

// Schlecht: Zeile wird ausgeführt, aber nichts geprüft
test('calculateDiscount runs', () => {
  calculateDiscount(100, 'premium'); // Keine Assertion!
});

Coverage: 100%, Qualität: 0%.

2. Coverage-Gaming

Entwickler schreiben Tests nur um Coverage-Schwellen zu erreichen, ohne sinnvolle Assertions. Abhilfe: Mutation Testing einsetzen.

3. Ignore-Pragmas übermäßig verwenden

/* istanbul ignore next */
export function complexFunction() { ... }

Jedes ignore versteckt ungetesteten Code. Verwende es nur für plattform-spezifischen Code oder bewusst ausgeschlossene Fehlerpfade.

4. Nur Line Coverage messen

Line Coverage ist die schwächste Metrik. Branch Coverage findet ungetestete Edge-Cases, die Line Coverage übersieht.

Mutation Testing: Die nächste Stufe

Mutation Testing verändert den Code systematisch (z.B. > zu >=, + zu -) und prüft, ob die Tests den Mutanten abfangen. Wenn ein Mutant überlebt, ist der Test unzureichend.

# Stryker Mutation Testing für JavaScript
npx stryker run

Beispiel-Mutanten:

  • if (score >= 90)if (score > 90) — findet Tests, die genau 90 nicht prüfen
  • return price * 0.8return price * 0.9 — findet Tests ohne exakte Assertion
  • return 'A'return 'B' — findet Tests ohne Rückgabewert-Prüfung

Tools im Überblick

ToolSpracheMetrikenBesonderheit
Istanbul/nycJavaScriptLine, Branch, FuncStandard in Jest
Coverage.pyPythonLine, BranchStandard in pytest
JaCoCoJavaLine, Branch, MethodStandard in Maven/Gradle
gcovC/C++Line, BranchGCC-integriert
StrykerJS/TSMutation ScoreMutation Testing
PITJavaMutation ScoreMutation Testing

Prüfungsrelevante Stichpunkte

  • Unterscheidung Line, Branch, Path und Function Coverage
  • Coverage ist notwendige, aber nicht hinreichende Bedingung für Qualität
  • Coverage-Schwellen in CI/CD-Pipelines als Quality Gate
  • Mutation Testing als Ergänzung zur Coverage-Messung
  • Coverage-Tools: Istanbul, JaCoCo, Coverage.py, gcov
  • 100% Coverage ohne Assertions ist wertlos
  • ISO 25010: Testbarkeit als Qualitätsmerkmal

FAQ

1. Line vs Branch Coverage?

Line misst Zeilen, Branch misst Verzweigungen (true/false). Branch ist strenger.

2. Ist 100% Coverage genug?

Nein, ohne Assertions ist 100% Coverage wertlos.

3. Sinnvoller Schwellwert?

Web-Apps 70-80%, Bibliotheken 90-100%.

4. Was ist Mutation Testing?

Verändert Code systematisch und prüft, ob Tests Mutanten abfangen.

5. Was ist Path Coverage?

Misst alle Ausführungspfade. Strengste Metrik, oft unrealistisch.

6. Coverage in Jest konfigurieren?

coverageThreshold in jest.config.js setzen.

7. Was ist Istanbul?

Standard-Coverage-Tool für JavaScript, von Jest genutzt.

8. Coverage in CI/CD erzwingen?

Ja, als Quality Gate gegen ungetesteten Code.

9. Function Coverage?

Misst, wie viele Funktionen aufgerufen wurden.

10. Was ist Coverage-Gaming?

Tests ohne Assertions nur für Schwellen. Abhilfe: Mutation Testing.

11. Tools für andere Sprachen?

Coverage.py (Python), JaCoCo (Java), gcov (C/C++).

12. Was ist istanbul ignore?

Schließt Code von Coverage aus, sparsam verwenden.

13. Was ist ein LCOV-Report?

Standardformat für Coverage-Daten für CI/CD-Integration.

14. Coverage in Legacy-Code?

Charakterisierungstests schreiben, schrittweise refactoren.

15. Coverage vs Testqualität?

Coverage misst Ausführung, Testqualität misst Fehlererkennung.

Weiter im Softwarequalität Lernpfad

Der nächste Artikel im Softwarequalität Lernpfad behandelt Clean Code und SOLID Prinzipien — die Grundlagen für lesbaren, wartbaren und erweiterbaren Code.

Quellen

  1. https://istanbul.js.org/
  2. https://stryker-mutator.io/
  3. https://jestjs.io/docs/configuration#coveragethreshold-object
  4. https://iso25000.com/index.php/en/iso-25000-standards/iso-25010.html

Buchempfehlungen zur Softwarequalität

Wenn Du Dich weiter mit Testabdeckung, Softwarequalität und Testing beschäftigen möchtest, empfehlen wir Dir die folgenden Bücher:

Software Engineering

Bücher über Softwarequalität, Clean Code, Code Reviews und Softwareentwicklungsprozesse

Software Engineering: Umfassendes Handbuch für die Praxis

Software Engineering: Umfassendes Handbuch für die Praxis

Bei Amazon ansehen

Affiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.

Refactoring: Wie Sie bestehenden Code verbessern von Martin Fowler

Refactoring: Wie Sie bestehenden Code verbessern von Martin Fowler

Bei Amazon ansehen

Affiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.

Zurück zum DEV Blog
Share:

Ähnliche Beiträge