Code Coverage fuer Bash-Skripte messen und verbessern
AI generated
$_
#!/
Bash · Code Coverage · kcov · Testing
Code Coverage fuer Bash-Skripte messen
mit kcov ungetestete Codepfade sichtbar machen

Wer eine bats-Testsuite fuer Bash-Skripte hat, weiss noch lange nicht, welche Zeilen tatsaechlich ausgefuehrt wurden. Code Coverage mit kcov schliesst diese Luecke, zeigt Zeile fuer Zeile, was Tests wirklich abdecken, und macht ungetestete Fehlerpfade sichtbar, lange bevor sie in Produktion zum Problem werden.

18 Min. Lesezeit kcov · bashcov · CI-Schwellenwerte Bash 4.x · 5.x · Linux

1. Warum Code Coverage auch fuer Shell-Skripte relevant ist

In vielen anderen Programmiersprachen ist Code Coverage ein selbstverstaendlicher Bestandteil der Testinfrastruktur, in der Bash-Welt wird die Kennzahl dagegen haeufig ignoriert. Dabei gilt fuer Shell-Skripte genau dasselbe Prinzip wie fuer jede andere Sprache: Eine Testsuite, die nur den Erfolgsfall prueft, aber nie den Fehlerpfad mit ungueltigen Argumenten oder fehlenden Dateien ausfuehrt, hinterlaesst blinde Flecken, die genau dann sichtbar werden, wenn ein Skript in Produktion auf einen unerwarteten Zustand trifft.

Code Coverage fuer Bash-Skripte beantwortet eine einfache, aber wichtige Frage: Welche Zeilen des Skripts wurden waehrend eines Testlaufs tatsaechlich ausgefuehrt, und welche nie? Ein Backup-Skript mit einer Fehlerbehandlung fuer den Fall, dass die Zieldiskette voll ist, mag im Code vorhanden sein, aber wenn kein einziger Testfall diesen Zustand simuliert, bleibt diese Zeile ungetestet und ihr tatsaechliches Verhalten unbekannt, bis der Fehler zum ersten Mal live auftritt.

Das Werkzeug kcov bringt Code Coverage in die Bash-Welt, indem es ein Skript instrumentiert ausfuehrt und dabei protokolliert, welche Zeilen tatsaechlich durchlaufen wurden. Kombiniert mit einer bestehenden bats-Testsuite entsteht so ein vollstaendiges Bild: nicht nur, ob Tests gruen sind, sondern auch, wie viel vom tatsaechlichen Code diese Tests ueberhaupt beruehren. Die folgenden Abschnitte zeigen den kompletten Workflow von der Installation bis zur Durchsetzung von Coverage-Schwellenwerten in der CI-Pipeline.

2. kcov installieren und erste Messung durchfuehren

kcov ist ein eigenstaendiges Kommandozeilenwerkzeug, das Code Coverage fuer kompilierte Binaries und Skriptsprachen misst, ohne dass die zu messende Anwendung selbst angepasst werden muss. Fuer Bash bedeutet das: kcov haengt sich an die Ausfuehrung eines Skripts und protokolliert auf Betriebssystemebene, welche Zeilen tatsaechlich ausgefuehrt wurden, ohne dass das Skript selbst irgendeine Instrumentierung enthalten muss.

Die Installation erfolgt unter Debian und Ubuntu ueber den Paketmanager, alternativ kompiliert man kcov aus dem Quellcode fuer neuere Versionen mit zusaetzlichen Features. Der grundlegende Aufruf kcov ausgabeverzeichnis ./skript.sh fuehrt das Skript ganz normal aus und schreibt gleichzeitig einen vollstaendigen HTML-Coverage-Report in das angegebene Ausgabeverzeichnis, den man anschliessend im Browser oeffnen kann.


# Install kcov on Debian/Ubuntu
sudo apt-get install kcov

# Or build the latest version from source for full Bash support
git clone https://github.com/SimonKagstrom/kcov.git
cd kcov && mkdir build && cd build
cmake .. && make && sudo make install

# Run a script under kcov coverage measurement
kcov --include-path=. ./coverage-output ./deploy.sh

# Open the generated HTML report
xdg-open ./coverage-output/deploy.sh/index.html

# Coverage summary is also printed to stdout, e.g.:
# Percent covered: 73.500

Ein wichtiges Detail: Ohne das Flag --include-path misst kcov auch alle eingebundenen Bibliotheken aus dem System, etwa /usr/lib/bash-interne Funktionen, was den Report unnoetig aufblaeht und die eigentlich interessante Code Coverage des eigenen Skripts verwaesert. Mit --include-path=. beschraenkt sich die Messung auf Dateien im aktuellen Projektverzeichnis, was fuer die meisten Anwendungsfaelle die richtige Einstellung ist.

3. Coverage-Reports lesen: Zeilen, Branches, Funktionen

Der von kcov erzeugte HTML-Report zeigt den Quellcode des Skripts zeilenweise eingefaerbt: gruen fuer Zeilen, die mindestens einmal ausgefuehrt wurden, rot fuer Zeilen, die nie erreicht wurden. Diese einfache visuelle Darstellung macht Code Coverage fuer Bash sofort greifbar, ohne dass man rohe Zahlen interpretieren muss: Ein Blick auf den Report zeigt direkt, welche Fehlerbehandlungsbloecke, welche case-Zweige oder welche Funktionen nie von einem Testfall erreicht wurden.

Neben der reinen Zeilenabdeckung liefert kcov auch Informationen zur Branch Coverage, also der Frage, ob beide Seiten einer Bedingung, sowohl der wahre als auch der falsche Zweig eines if, tatsaechlich durchlaufen wurden. Eine Zeile kann als "ausgefuehrt" gelten, obwohl nur einer von zwei moeglichen Zweigen jemals getestet wurde, ein Detail, das reine Zeilen-Coverage verschleiert, aber fuer die tatsaechliche Testqualitaet entscheidend ist.


# Example: a function with a branch that coverage should reveal as untested

validate_backup_size() {
  local size_bytes="$1"
  local min_size=$((10 * 1024 * 1024))  # 10 MB minimum

  if [[ "$size_bytes" -lt "$min_size" ]]; then
    echo "[ERROR] Backup suspiciously small: ${size_bytes} bytes" >&2
    return 1
  else
    echo "[OK] Backup size looks reasonable: ${size_bytes} bytes"
    return 0
  fi
}

# If tests only ever call validate_backup_size with a large value,
# the kcov report will show the "if" branch (line 6-7) as never
# covered — a real gap, since the error path was never verified.

Der textuelle Zusammenfassungsbericht am Ende eines kcov-Laufs zeigt zusaetzlich eine aggregierte Prozentzahl pro Datei und fuer das gesamte Projekt. Diese Zahl allein sagt aber noch nichts darueber aus, ob die getesteten Zeilen auch tatsaechlich sinnvoll geprueft wurden, ein Punkt, der im Abschnitt ueber die Grenzen von Code Coverage vertieft wird.

4. Coverage mit bats-Testsuiten kombinieren

Der eigentliche Mehrwert von Code Coverage fuer Bash entsteht, wenn kcov nicht ein einzelnes Skript, sondern eine komplette bats-Testsuite instrumentiert. Da bats-core selbst ein Bash-Skript ausfuehrt, kann kcov direkt den bats-Aufruf umschliessen, wodurch die Coverage-Messung ueber alle Testfaelle hinweg aggregiert wird, statt fuer jeden einzelnen Testfall separat gemessen zu werden.

Diese Kombination beantwortet die eigentlich relevante Frage: Welche Zeilen der zu testenden Bibliothek werden von der gesamten Testsuite abgedeckt, ueber alle Testfaelle hinweg? Ein einzelner Testfall deckt vielleicht nur einen kleinen Teil einer Funktion ab, aber zusammen mit weiteren Testfaellen, die andere Codepfade ansprechen, ergibt sich ein vollstaendigeres Bild der tatsaechlichen Testabdeckung.


#!/usr/bin/env bash
# scripts/run-coverage.sh — run the full bats suite under kcov
set -euo pipefail

readonly COVERAGE_DIR="coverage-report"
readonly BATS_BIN="$(command -v bats)"

# kcov wraps the bats binary itself, aggregating coverage
# across every single @test case in the suite
kcov \
  --include-path="$(pwd)/lib" \
  --exclude-pattern="/test_helper/,/.bats/" \
  "$COVERAGE_DIR" \
  "$BATS_BIN" test/

echo "Coverage report generated at: ${COVERAGE_DIR}/index.html"

# Extract the aggregated percentage for a quick console summary
grep -o 'covered: [0-9.]*' "${COVERAGE_DIR}/index.html" | head -1

Der Parameter --exclude-pattern ist hier entscheidend: Ohne ihn misst kcov auch die bats-Framework-interne Logik und die Test-Helper-Bibliotheken selbst mit, was die Coverage-Zahl des eigenen Projektcodes verfaelscht. Mit gezieltem Ein- und Ausschluss von Pfaden bleibt der Report auf genau die Dateien beschraenkt, die tatsaechlich Teil der eigenen Bibliothek sind, nicht des Test-Frameworks selbst.

5. Ungetestete Codepfade identifizieren und schliessen

Sobald ein Coverage-Report vorliegt, besteht der naechste Schritt darin, die rot markierten Zeilen systematisch durchzugehen und zu entscheiden, ob es sich um eine echte Testluecke handelt oder um Code, der bewusst nicht getestet werden muss, etwa reine Debug-Ausgaben. Fuer jede echte Luecke wird ein neuer Testfall geschrieben, der genau den Zustand herstellt, der zur Ausfuehrung dieser Zeile fuehrt.

Ein bewaehrtes Vorgehen: Man priorisiert Luecken nach Risiko, nicht nach Menge. Eine ungetestete Fehlerbehandlung in einem Backup-Skript, das im Falle eines Fehlers stillschweigend weiterlaeuft, wiegt schwerer als eine ungetestete Debug-Ausgabe. Code Coverage liefert die Landkarte, aber die Priorisierung, welche Luecken zuerst geschlossen werden, bleibt eine menschliche Entscheidung basierend auf der tatsaechlichen Kritikalitaet des jeweiligen Codepfads.


# lib/backup.sh — the function under scrutiny after a coverage report

backup_database() {
  local db_name="$1"
  local backup_dir="${2:-/var/backups}"

  if [[ ! -d "$backup_dir" ]]; then
    echo "[ERROR] Backup directory does not exist: $backup_dir" >&2
    return 2
  fi

  if ! mysqldump "$db_name" > "${backup_dir}/${db_name}.sql" 2>/dev/null; then
    echo "[ERROR] mysqldump failed for database: $db_name" >&2
    return 3
  fi

  echo "[OK] Backup created: ${backup_dir}/${db_name}.sql"
  return 0
}

#!/usr/bin/env bats
load "test_helper/bats-assert/load"

setup() {
  load "../lib/backup.sh"
}

# This test case was added specifically because coverage showed
# the "directory does not exist" branch was never exercised
@test "backup_database fails with exit code 2 for missing backup dir" {
  run backup_database "shop" "/nonexistent/path"
  assert_equal "$status" 2
  assert_output --partial "does not exist"
}

# This test case closes the second gap the coverage report revealed
@test "backup_database fails with exit code 3 when mysqldump fails" {
  mysqldump() { return 1; }
  export -f mysqldump

  run backup_database "shop" "/tmp"
  assert_equal "$status" 3
  assert_output --partial "mysqldump failed"
}

Nach dem Hinzufuegen dieser beiden Testfaelle zeigt ein erneuter kcov-Lauf beide zuvor roten Zeilen als gruen an, und die aggregierte Coverage-Prozentzahl steigt entsprechend. Wichtig ist dabei, dass die neuen Tests nicht nur die Zeile ausfuehren, sondern auch den korrekten Exit-Code und die korrekte Fehlermeldung pruefen, wie im vorherigen Artikel zu Unit-Tests fuer Bash-Funktionen beschrieben.

6. Coverage-Schwellenwerte in CI-Pipelines durchsetzen

Eine einmalige Coverage-Messung ist nur eine Momentaufnahme. Um zu verhindern, dass Code Coverage mit der Zeit wieder sinkt, weil neue Funktionen ohne begleitende Tests hinzugefuegt werden, laesst sich ein Mindest-Schwellenwert direkt in der CI-Pipeline durchsetzen. Faellt die gemessene Abdeckung unter diesen Wert, schlaegt der Build fehl, bevor der Code gemergt werden kann.

kcov selbst bringt kein eingebautes Schwellenwert-Flag mit, aber die im HTML-Report eingebettete Zusammenfassung laesst sich mit einfachen Shell-Mitteln extrahieren und gegen einen Grenzwert pruefen. Dieses Muster laesst sich direkt in eine GitLab-CI- oder GitHub-Actions-Pipeline integrieren, sodass ein sinkender Coverage-Wert automatisch den Merge-Request blockiert, statt erst spaeter manuell aufzufallen.


#!/usr/bin/env bash
# scripts/check-coverage-threshold.sh — enforce a minimum coverage percentage
set -euo pipefail

readonly MIN_COVERAGE=80
readonly COVERAGE_DIR="coverage-report"

kcov --include-path="$(pwd)/lib" "$COVERAGE_DIR" bats test/ > /dev/null

# Extract the aggregated percentage from kcov's summary output
actual_coverage=$(kcov --include-path="$(pwd)/lib" "$COVERAGE_DIR" bats test/ 2>&1 \
  | grep -oP 'Percent covered: \K[0-9.]+' | head -1)

echo "Measured coverage: ${actual_coverage}%"
echo "Required threshold: ${MIN_COVERAGE}%"

if (( $(echo "$actual_coverage < $MIN_COVERAGE" | bc -l) )); then
  echo "[ERROR] Coverage ${actual_coverage}% is below the required ${MIN_COVERAGE}% threshold" >&2
  exit 1
fi

echo "[OK] Coverage threshold met"

In der Praxis empfiehlt es sich, den Schwellenwert nicht sofort auf einen hohen Wert wie 90 Prozent zu setzen, sondern schrittweise zu erhoehen: zunaechst den aktuellen Ist-Stand als Baseline festlegen, sodass keine bestehende Coverage sinken darf, und den Schwellenwert dann bei jeder Verbesserung der Testsuite manuell anheben. Das verhindert Frustration durch einen von Anfang an unerreichbaren Zielwert, waehrend die Kennzahl trotzdem stetig in die richtige Richtung wandert.

7. bashcov als Alternative zu kcov

bashcov ist eine Ruby-basierte Alternative speziell fuer Bash-Skripte, die auf dem etablierten SimpleCov-Framework aus dem Ruby-Oekosystem aufbaut. Waehrend kcov ein generisches, sprachuebergreifendes Werkzeug ist, das ueber Ptrace-basierte Instrumentierung funktioniert, ist bashcov gezielt fuer Bash-Skripte konzipiert und bringt eine einfachere Installation sowie eine gewohntere HTML-Report-Optik fuer Teams mit, die bereits mit SimpleCov aus anderen Projekten vertraut sind.

Der Nachteil von bashcov: Es erfordert eine Ruby-Umgebung als Abhaengigkeit, was in reinen Bash- oder Container-Umgebungen ohne Ruby einen zusaetzlichen Installationsschritt bedeutet. kcov hingegen ist ein natives C++-Binary ohne Laufzeitabhaengigkeiten, was es fuer schlanke CI-Container oft praktischer macht. Beide Werkzeuge liefern aber prinzipiell dieselbe Kernfunktion, Zeilen-Coverage-Messung fuer Bash-Skripte ohne Codeaenderung am zu messenden Skript.


# Install bashcov via RubyGems (requires Ruby to be installed)
gem install bashcov

# Run a script or test suite under bashcov measurement
bashcov ./deploy.sh

# Run an entire bats test suite under bashcov
bashcov -- bats test/

# bashcov generates a SimpleCov-style HTML report in ./coverage/
xdg-open ./coverage/index.html

Fuer Teams, die bereits Ruby-Tooling im Einsatz haben, etwa fuer andere Testframeworks oder Deployment-Skripte, kann bashcov die pragmatischere Wahl sein. Fuer reine Bash- oder Go-lastige Umgebungen ohne bestehende Ruby-Abhaengigkeit bleibt kcov meist die schlankere Option, gerade in minimalen Docker-Images fuer CI-Pipelines.

8. Grenzen von Code Coverage: hohe Zahl, schlechte Tests

Eine hohe Code-Coverage-Zahl ist keine Garantie fuer gute Tests. Ein Testfall, der eine Funktion aufruft, aber keine einzige Assertion auf das Ergebnis enthaelt, erzeugt technisch gesehen hundert Prozent Zeilen-Coverage fuer diese Funktion, ohne tatsaechlich zu pruefen, ob das Verhalten korrekt ist. Coverage misst, welcher Code ausgefuehrt wurde, nicht, ob das Ergebnis dieser Ausfuehrung tatsaechlich validiert wurde.

Ein zweites Problem: Coverage sagt nichts ueber die Qualitaet der getesteten Eingabewerte aus. Eine Funktion, die mit demselben Eingabewert zehnmal aufgerufen wird, erreicht dieselbe Zeilen-Coverage wie eine Funktion, die mit zehn unterschiedlichen, sorgfaeltig ausgewaehlten Grenzfaellen getestet wird, obwohl die zweite Testsuite erheblich wertvoller ist. Aus diesem Grund sollte Code Coverage immer als notwendige, aber nicht hinreichende Bedingung fuer gute Tests verstanden werden, ergaenzt um manuelle Reviews der tatsaechlichen Assertions in jedem Testfall.

9. Coverage-Tools im Vergleich

Je nach Projektumgebung und bereits vorhandenem Tooling eignen sich kcov, bashcov und eine rein manuelle Coverage-Analyse unterschiedlich gut.

Werkzeug Abhaengigkeiten Report-Format Am besten geeignet fuer
kcov Keine Laufzeitabhaengigkeit HTML, Cobertura XML Schlanke CI-Container, sprachuebergreifende Projekte
bashcov Benoetigt Ruby SimpleCov HTML Teams mit bestehendem Ruby-Tooling
Manuelle Analyse Keine Keine strukturierte Ausgabe Sehr kleine, einmalige Skripte
ShellCheck (statisch) Keine Warnungen, keine Coverage Ergaenzung, kein Coverage-Ersatz

In der Praxis ist kcov fuer die meisten Bash-Projekte die pragmatischste Wahl, da es ohne zusaetzliche Laufzeitumgebung auskommt und sich problemlos in bestehende CI-Container integrieren laesst. bashcov lohnt sich vor allem dort, wo Ruby ohnehin bereits Teil des Toolchains ist. ShellCheck ersetzt keine Coverage-Messung, ergaenzt sie aber sinnvoll, da es Probleme wie unquotierte Variablen bereits vor der Testausfuehrung statisch findet.

Mironsoft

Shell-Automatisierung, Testing und Deployment-Infrastruktur

Ungetestete Codepfade in Bash-Skripten aufdecken?

Wir richten kcov-basierte Code-Coverage-Messung fuer eure bats-Testsuiten ein, definieren realistische CI-Schwellenwerte und schliessen gezielt die wichtigsten Testluecken in kritischen Deployment- und Backup-Skripten.

Coverage-Setup

kcov-Integration mit bestehenden bats-Testsuiten

Luecken schliessen

Priorisierte Testfaelle fuer kritische, ungetestete Fehlerpfade

CI-Integration

Coverage-Schwellenwerte in GitLab CI und GitHub Actions durchsetzen

10. Zusammenfassung

Code Coverage fuer Bash-Skripte macht sichtbar, was reine Testergebnisse verschweigen: welche Zeilen tatsaechlich ausgefuehrt wurden und welche seit dem Schreiben des Codes nie einen einzigen Testlauf durchlaufen haben. kcov instrumentiert Bash-Skripte ohne Codeaenderung, erzeugt farbcodierte HTML-Reports und laesst sich direkt mit bestehenden bats-Testsuiten kombinieren, um die Abdeckung ueber alle Testfaelle hinweg zu aggregieren.

Coverage-Schwellenwerte in der CI-Pipeline verhindern schleichenden Rueckgang der Testqualitaet, waehrend priorisierte Ergaenzung von Testfaellen fuer die riskantesten ungetesteten Codepfade den groessten Sicherheitsgewinn bringt. Wichtig bleibt dabei die Erkenntnis, dass eine hohe Coverage-Zahl allein noch keine gute Testsuite garantiert, solange die tatsaechlichen Assertions in jedem Testfall nicht ebenfalls kritisch geprueft werden.

Code Coverage fuer Bash-Skripte — Das Wichtigste auf einen Blick

kcov

Instrumentiert Bash-Skripte ohne Codeaenderung, erzeugt farbcodierte HTML-Reports, keine Laufzeitabhaengigkeit.

bats-Kombination

kcov um den bats-Aufruf legen, um Coverage ueber die gesamte Testsuite hinweg zu aggregieren.

CI-Schwellenwerte

Mindestwert durchsetzen, schrittweise erhoehen, statt sofort einen unerreichbaren Zielwert zu setzen.

Grenzen kennen

Hohe Zeilen-Coverage ersetzt keine sorgfaeltigen Assertions und keine durchdachten Testfaelle.

11. FAQ: Code Coverage fuer Bash-Skripte

1Was ist Code Coverage fuer Bash?
Misst welche Zeilen waehrend Tests tatsaechlich ausgefuehrt wurden, zeigt ungetestete Codepfade.
2Was ist kcov?
Sprachuebergreifendes Coverage-Tool ohne Laufzeitabhaengigkeit, erzeugt HTML-Reports.
3Skript fuer kcov anpassen?
Nein, kcov instrumentiert von aussen, ohne Codeaenderung.
4kcov mit bats kombinieren?
kcov um den bats-Aufruf legen, Coverage wird ueber alle Testfaelle aggregiert.
5Zeilen- vs. Branch Coverage?
Zeilen-Coverage prueft Ausfuehrung, Branch Coverage prueft beide Seiten einer Bedingung.
6Schwellenwert in CI durchsetzen?
Prozentzahl aus Report extrahieren, gegen Mindestwert pruefen, bei Unterschreitung abbrechen.
7bashcov statt kcov?
Ruby-basierte Alternative, sinnvoll bei bestehendem Ruby-Tooling im Team.
8100 Prozent Coverage garantiert fehlerfrei?
Nein, misst nur Ausfuehrung, nicht die Qualitaet der Assertions.
9Luecken priorisieren?
Nach Risiko: kritische Fehlerpfade vor Debug-Ausgaben.
10Sofort 90 Prozent anstreben?
Nein, Baseline festlegen und Schwellenwert schrittweise erhoehen.