0Pricing
DevOps Bootcamp · Lektion

Funktionen mit Bats-core per Unit-Test prüfen

Strukturieren Sie Testdateien, Assertions sowie Setup und Teardown, um einzelne Bash-Funktionen zu überprüfen.

Funktionen mit Bats-core per Unit-Test prüfen ist eine kostenlose DevOps Bootcamp-Lektion auf CoddyKit. Dies ist Lektion 1 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des DevOps Bootcamp-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der DevOps Bootcamp-Kurs umfasst insgesamt 4 Lektionen.

Was ist Bats-core und warum sollte man es verwenden?

Bats-core (Bash Automated Testing System) ist das De-facto-Unit-Test-Framework für Bash. Damit können Sie strukturierte, reproduzierbare Tests für Ihre Shell-Funktionen und Skripte schreiben – genauso, wie Sie JUnit für Java oder pytest für Python verwenden würden.

  • Jeder Test ist ein @test-Block mit einer verständlichen Beschreibung.
  • Tests sind erfolgreich, wenn jeder darin ausgeführte Befehl den Exit-Code 0 zurückgibt.
  • Tests schlagen beim ersten Exit-Code ungleich null oder bei einer fehlgeschlagenen Assertion fehl.
  • Die Ausgabe ist TAP-kompatibel, sodass CI-Systeme (GitHub Actions, Jenkins, GitLab CI) sie nativ verstehen.

Installieren Sie Bats über Ihren Paketmanager oder klonen Sie das Repository:

# Install via git (recommended — always latest)
git clone https://github.com/bats-core/bats-core.git
cd bats-core && sudo ./install.sh /usr/local

# Or on macOS with Homebrew
brew install bats-core

# Verify installation
bats --version
# bats 1.x.y

Ihre erste Bats-Testdatei

Eine Bats-Testdatei hat die Erweiterung .bats und beginnt mit einem speziellen Shebang. Der grundlegende Baustein ist die Direktive @test, gefolgt von einer Beschreibung und einem Befehlsblock.

  • Der Shebang #!/usr/bin/env bats teilt der Shell mit, wie die Datei ausgeführt werden soll.
  • Jeder @test-Block ist ein unabhängiger Testfall.
  • Sie können eine einzelne Datei mit bats my_tests.bats oder ein ganzes Verzeichnis mit bats test/ ausführen.

Im Folgenden sehen Sie die minimale Struktur einer Bats-Testdatei:

#!/usr/bin/env bats
# File: test/hello.bats

@test "echo outputs the expected string" {
  result=$(echo "hello world")
  [ "$result" = "hello world" ]
}

@test "false command causes test to fail" {
  # Uncommenting the next line would make this test fail:
  # false
  true
}

Die zu testende Funktion mit „load“ laden

In realen Projekten befinden sich Ihre Bash-Funktionen in Bibliotheksdateien und nicht direkt in der Testdatei. Bats stellt den Helfer load bereit, um externe Dateien relativ zum Verzeichnis der Testdatei zu laden.

  • load '../lib/math.sh' lädt die Datei, bevor jeder Test ausgeführt wird.
  • Nach dem Laden stehen alle in dieser Datei definierten Funktionen in Ihren Testblöcken zur Verfügung.
  • Bewahren Sie Ihre Bibliotheksfunktionen zur sauberen Trennung in einem Verzeichnis lib/ und die Tests in einem Verzeichnis test/ auf.

Beispiel für die Projektstruktur und den zugehörigen Test:

# Project layout:
# lib/math.sh       <- functions to test
# test/math.bats    <- test file

# lib/math.sh
add() {
  echo $(( $1 + $2 ))
}

divide() {
  if [ "$2" -eq 0 ]; then
    echo "Error: division by zero" >&2
    return 1
  fi
  echo $(( $1 / $2 ))
}

# test/math.bats
#!/usr/bin/env bats

load '../lib/math.sh'

@test "add returns correct sum" {
  result=$(add 3 4)
  [ "$result" = "7" ]
}

Grundlegende Assertions: run, $status, $output

Der Befehl run bildet das Herzstück des Testens mit Bats. Anstatt einen Befehl direkt auszuführen, verpacken Sie ihn in run. Dadurch werden Exit-Code und Ausgabe erfasst, ohne dass der Test sofort fehlschlägt.

  • $status – enthält den Exit-Code des letzten run-Befehls.
  • $output – enthält die zusammengeführte stdout-Ausgabe des letzten run-Befehls.
  • $lines – ein Array, dessen Elemente jeweils eine Ausgabezeile enthalten (${lines[0]}, ${lines[1]} usw.).

So können Sie sowohl Erfolgs- als auch Fehlerfälle prüfen:

#!/usr/bin/env bats

load '../lib/math.sh'

@test "divide 10 by 2 returns 5" {
  run divide 10 2
  [ "$status" -eq 0 ]
  [ "$output" = "5" ]
}

@test "divide by zero returns exit code 1" {
  run divide 10 0
  [ "$status" -eq 1 ]
}

@test "divide by zero prints error message" {
  run divide 10 0
  # $output captures stderr too when redirected inside the function
  [[ "$output" == *"division by zero"* ]]
}

Ausdrucksstarke Assertions mit bats-assert verwenden

Die integrierten [ ]-Assertions funktionieren, liefern aber wenig hilfreiche Fehlermeldungen. Die Hilfsbibliothek bats-assert stellt ausdrucksstarke Assertions bereit, die genau ausgeben, was schiefgelaufen ist.

  • assert_success – prüft, ob $status gleich 0 ist.
  • assert_failure – prüft, ob $status ungleich null ist.
  • assert_output – prüft, ob $output der angegebenen Zeichenkette entspricht.
  • assert_output --partial – prüft, ob die Ausgabe das Teilstück enthält.
  • refute_output --partial – prüft, ob die Ausgabe das Teilstück NICHT enthält.

Installieren Sie die Bibliothek, indem Sie bats-core/bats-assert in einen Ordner test/helpers/ klonen, und laden Sie sie anschließend:

#!/usr/bin/env bats

# Load bats-assert (cloned into test/helpers/bats-assert)
load 'helpers/bats-assert/load'
load '../lib/math.sh'

@test "add 5 and 3 gives 8" {
  run add 5 3
  assert_success
  assert_output "8"
}

@test "divide by zero fails with descriptive message" {
  run divide 9 0
  assert_failure
  assert_output --partial "division by zero"
}

@test "add does not output an error" {
  run add 1 1
  refute_output --partial "Error"
}

setup und teardown: Hooks im Testlebenszyklus

Bats stellt zwei spezielle Funktionen bereit – setup und teardown –, die automatisch um jeden Test herum ausgeführt werden. Verwenden Sie sie, um gemeinsam genutzten Zustand vorzubereiten und aufzuräumen, damit jeder Test in einer bekannten Umgebung startet.

  • setup() wird vor jedem einzelnen @test-Block ausgeführt.
  • teardown() wird nach jedem einzelnen @test-Block ausgeführt, auch wenn der Test fehlschlägt.
  • Typische Einsatzbereiche sind das Erstellen temporärer Verzeichnisse, das Setzen von Umgebungsvariablen und das Entfernen temporärer Dateien nach dem Test.
#!/usr/bin/env bats

load '../lib/fileutils.sh'

setup() {
  # Create a fresh temp directory before every test
  TEST_DIR=$(mktemp -d)
  export TEST_DIR
}

teardown() {
  # Always clean up, even on test failure
  rm -rf "$TEST_DIR"
}

@test "write_file creates a file with correct content" {
  run write_file "$TEST_DIR/hello.txt" "hello world"
  assert_success
  [ -f "$TEST_DIR/hello.txt" ]
  [ "$(cat "$TEST_DIR/hello.txt")" = "hello world" ]
}

@test "write_file fails when directory does not exist" {
  run write_file "/nonexistent/dir/file.txt" "data"
  assert_failure
}

setup_file und teardown_file: Hooks auf Suite-Ebene

Manchmal müssen aufwendige Ressourcen nur einmal pro Datei eingerichtet werden – nicht vor jedem einzelnen Test. Dafür stellt Bats setup_file und teardown_file bereit.

  • setup_file() wird einmal vor allen Tests in der Datei ausgeführt.
  • teardown_file() wird einmal nach allen Tests in der Datei ausgeführt.
  • Verwenden Sie BATS_FILE_TMPDIR (automatisch verfügbar), um Daten zwischen setup_file und Ihren Tests gemeinsam zu nutzen – normale Variablen bleiben über Subshells hinweg nicht erhalten.

Typischer Anwendungsfall: Einen Mock-Server starten oder einmalig eine Binärdatei erstellen und am Ende wieder aufräumen:

#!/usr/bin/env bats

setup_file() {
  # Build the project binary once for all tests in this file
  make build --silent
  export BINARY="$PWD/bin/myapp"
  echo "Binary built: $BINARY"
}

teardown_file() {
  # Remove the binary after all tests complete
  rm -f "$BINARY"
  echo "Cleaned up binary"
}

setup() {
  # Still runs before each individual test
  TEST_TMP=$(mktemp -d)
}

teardown() {
  rm -rf "$TEST_TMP"
}

@test "myapp --version outputs version string" {
  run "$BINARY" --version
  assert_output --partial "1.0"
}

Funktionen testen, die Dateien ändern

Ein sehr häufiges Muster ist das Testen von Bash-Funktionen, die aus dem Dateisystem lesen oder darin schreiben. Die wichtigste Technik besteht darin, temporäre Verzeichnisse zu verwenden (über mktemp -d in setup), damit Tests niemals echte Dateien berühren und sich nicht gegenseitig beeinflussen.

  • Arbeiten Sie immer innerhalb von $TEST_DIR (oder $BATS_TEST_TMPDIR – in aktuellen Bats-Versionen automatisch verfügbar).
  • Verwenden Sie die Hilfsbibliothek bats-file für übersichtliche Datei-Assertions wie assert_file_exists und assert_file_contains.
  • Verwenden Sie niemals fest codierte Pfade wie /tmp/myfile – parallele Testläufe würden miteinander kollidieren.
#!/usr/bin/env bats

load 'helpers/bats-assert/load'
load 'helpers/bats-file/load'
load '../lib/fileutils.sh'

setup() {
  TEST_DIR="$BATS_TEST_TMPDIR"
}

# lib/fileutils.sh defines:
# append_line() { echo "$2" >> "$1"; }

@test "append_line adds a line to an existing file" {
  echo "first line" > "$TEST_DIR/log.txt"

  run append_line "$TEST_DIR/log.txt" "second line"
  assert_success

  assert_file_contains "$TEST_DIR/log.txt" "second line"
}

@test "append_line creates file if it does not exist" {
  run append_line "$TEST_DIR/new.txt" "hello"
  assert_success
  assert_file_exists "$TEST_DIR/new.txt"
}

Externe Befehle mocken

Funktionen rufen häufig externe Programme wie curl, aws oder git auf. In Unit-Tests möchten Sie Ihre Logik testen, nicht den tatsächlichen externen Befehl. Die sauberste Mocking-Technik in Bats besteht darin, in setup eine Shell-Funktion mit demselben Namen wie der Befehl zu definieren – sie hat Vorrang vor der echten Binärdatei.

  • Definieren Sie in setup eine Funktion wie curl() { echo 'mocked response'; return 0; } und exportieren Sie sie.
  • Verwenden Sie export -f curl, damit die Funktion in von run gestarteten Subshells sichtbar ist.
  • Für komplexere Szenarien können Sie den Mock auch in eine temporäre Datei im PATH schreiben.
#!/usr/bin/env bats

load 'helpers/bats-assert/load'
load '../lib/network.sh'

# lib/network.sh defines:
# fetch_status() {
#   local url="$1"
#   local code
#   code=$(curl -s -o /dev/null -w "%{http_code}" "$url")
#   echo "$code"
# }

setup() {
  # Override 'curl' with a mock function
  curl() {
    # Simulate a 200 OK response
    echo "200"
    return 0
  }
  export -f curl
}

@test "fetch_status returns 200 when curl reports 200" {
  run fetch_status "https://example.com"
  assert_success
  assert_output "200"
}

Tests überspringen und mit Tags versehen

Nicht jeder Test kann jederzeit ausgeführt werden – manchmal benötigen Sie eine echte Netzwerkverbindung, ein bestimmtes Tool oder ein bestimmtes Betriebssystem. Bats stellt skip bereit, um einen Test bedingt und mit einer informativen Nachricht zu überspringen, anstatt ihn auszukommentieren oder die Testsuite zu unterbrechen.

  • Rufen Sie skip "reason" an beliebiger Stelle innerhalb eines @test-Blocks auf, um den Test zu überspringen.
  • Übersprungene Tests werden in der Ausgabe als S angezeigt und zählen nicht als Fehler.
  • Bats 1.5+ unterstützt Tags: Kennzeichnen Sie Tests mit # bats test_tags=slow,network und filtern Sie sie mit bats --filter-tags network test/.
#!/usr/bin/env bats

# bats test_tags=network
@test "API returns valid JSON" {
  # Skip if no internet connectivity
  if ! ping -c1 -W1 8.8.8.8 &>/dev/null; then
    skip "No network connection available"
  fi

  run curl -s "https://api.example.com/health"
  assert_success
  assert_output --partial '"status"'
}

# bats test_tags=unit
@test "slug function lowercases and replaces spaces" {
  # Always runs — pure function, no external deps
  slug() { echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' ' '-'; }
  run slug "Hello World"
  assert_output "hello-world"
}

# Run only unit tests:
# bats --filter-tags unit test/

Eine vollständige Testsuite strukturieren

Ein gut organisiertes Bats-Projekt folgt einer vorhersehbaren Verzeichnisstruktur. Dadurch lassen sich neue Mitwirkende leichter einarbeiten und CI-Pipelines einfacher integrieren.

Empfohlene Struktur:

  • lib/ – produktive Bash-Funktionen (eine Datei pro Aufgabenbereich: math.sh, fileutils.sh).
  • test/ – eine .bats-Datei pro Bibliotheksdatei (math.bats, fileutils.bats).
  • test/helpers/ – bats-assert, bats-file und bats-support als Git-Submodule.
  • Makefile – ein test-Target, sodass Mitwirkende einfach make test ausführen können.

Führen Sie die gesamte Testsuite mit einem Befehl aus:

# Makefile
.PHONY: test
test:
	bats test/

# Run all tests recursively (Bats 1.5+)
# bats --recursive test/

# Run a specific file
# bats test/math.bats

# Run with verbose (TAP) output for CI
# bats --tap test/

# Example directory tree:
# .
# |-- lib/
# |   |-- math.sh
# |   `-- fileutils.sh
# |-- test/
# |   |-- helpers/
# |   |   |-- bats-assert/
# |   |   `-- bats-file/
# |   |-- math.bats
# |   `-- fileutils.bats
# `-- Makefile

Wissenscheck: Bats-core-Assertions

Testen Sie Ihr Verständnis des grundlegenden Testmechanismus von Bats-core.

Zusammenfassung: Bash mit Bats-core per Unit-Tests testen

In dieser Lektion haben Sie gelernt, wie Sie mit Bats-core Unit-Tests für einzelne Bash-Funktionen strukturieren und schreiben. Hier sind die wichtigsten Erkenntnisse:

  • Struktur der Testdatei – Verwenden Sie den Shebang #!/usr/bin/env bats und @test-Blöcke mit aussagekräftigen Namen.
  • load – Laden Sie Ihre Bibliotheksdateien, damit Funktionen in Tests verfügbar sind, ohne sie per Copy-and-paste zu duplizieren.
  • run + $status + $output – das zentrale Trio; verwenden Sie immer run, um Ergebnisse zu erfassen, ohne den Test sofort fehlschlagen zu lassen.
  • bats-assert – Verwenden Sie vorzugsweise assert_success, assert_failure und assert_output anstelle von einfachen [ ]-Assertions, um verständliche Fehlermeldungen zu erhalten.
  • setup / teardown – werden vor bzw. nach jedem Test ausgeführt; setup_file / teardown_file werden einmal pro Datei ausgeführt.
  • Mocking – Überschreiben Sie externe Befehle mit gleichnamigen Shell-Funktionen, die mit export -f exportiert werden.
  • skip – Überspringen Sie bedingt Tests, die auf nicht verfügbaren Ressourcen beruhen.
  • Projektstruktur – Halten Sie lib/, test/ und test/helpers/ zur besseren Wartbarkeit und CI-Integration getrennt.

Mit diesen Mustern verleihen Sie Ihren Bash-Projekten dieselbe Testdisziplin, die Sie auf jedes moderne Softwareprojekt anwenden würden.

Häufig gestellte Fragen

Ist die Lektion „Funktionen mit Bats-core per Unit-Test prüfen“ kostenlos?

Ja — der vollständige Text von „Funktionen mit Bats-core per Unit-Test prüfen“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des DevOps Bootcamp-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der DevOps Bootcamp-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Funktionen mit Bats-core per Unit-Test prüfen“?

Strukturieren Sie Testdateien, Assertions sowie Setup und Teardown, um einzelne Bash-Funktionen zu überprüfen. Du übst DevOps Bootcamp mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um DevOps Bootcamp zu starten?

Keine Vorkenntnisse erforderlich. DevOps Bootcamp auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 1 von 4.

Wie lange dauert die Lektion „Funktionen mit Bats-core per Unit-Test prüfen“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser DevOps Bootcamp-Lektion Code schreiben und ausführen?

Ja. Jede DevOps Bootcamp-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Funktionen mit Bats-core per Unit-Test prüfen
  2. Befehle mocken und externe Tools stubben
  3. Fixtures, temporäre Umgebungen und Coverage
  4. Shell-Tests in CI-Pipelines ausführen
← Zurück zu DevOps Bootcamp