Wer auf einem Cloud-Mac ein Kommandozeilenprogramm kompiliert, übersieht vor der Auslieferung leicht etwas anderes als Compilerfehler: den Aufrufvertrag. Hilfetexte landen im Datenstrom, Fehler liefern trotzdem den Exitcode null, oder das Tool funktioniert nur im aktuellen Verzeichnis des Entwicklers. Mit einem kleinen Swift-Tool legen wir deshalb Abnahmetests fest. Das Beispiel benötigt keine grafische Sitzung und eignet sich für unbeaufsichtigte Build-Jobs.
Den Aufrufvertrag zuerst festlegen
Das Beispieltool taskprobe nimmt --output und einen Text entgegen und schreibt den Text in die angegebene Datei. Der Dateipfad ist Pflicht; ein Hilfeaufruf endet erfolgreich, ein Argumentfehler dagegen nicht. Bevor wir die Funktion implementieren, legen wir fest, woran aufrufende Programme das Ergebnis erkennen.
| Szenario | Standardausgabe | Standardfehlerausgabe | Exitcode | Datei |
|---|---|---|---|---|
--help |
Aufrufhilfe | Leer | 0 | Wird nicht erstellt |
| Fehlende Argumente | Leer | Fehlermeldung | Ungleich null | Wird nicht erstellt |
| Erfolgreiches Schreiben | Erfolgsmeldung | Leer | 0 | Inhalt vollständig |
| Pfad nicht beschreibbar | Leer | Fehlermeldung | Ungleich null | Zieldatei wird nicht erstellt |
Die Standardausgabe ist ausschließlich für maschinell auswertbare Erfolgsergebnisse da, die Standardfehlerausgabe ausschließlich für Diagnosemeldungen. Aufrufende Skripte müssen sowohl den Exitcode als auch die erwartete Datei prüfen. Ein „Fertig“ im Log allein ist kein Erfolgsnachweis.
Atomares Schreiben verhindert, dass die Zieldatei mit nur einem Teil des neuen Inhalts überschrieben wird. Es garantiert nicht, dass die Daten nach einem Stromausfall dauerhaft gespeichert sind; dafür braucht es eine gesonderte Strategie zum Speichern und Wiederherstellen.
Ein minimales, testbares Tool kompilieren
Speichern und kompilieren Sie den Quellcode in einem separaten Verzeichnis. Der Ausgabepfad ist im Programm nicht fest vorgegeben: Der Aufrufer übergibt einen absoluten Pfad, damit sich das Arbeitsverzeichnis im Test gezielt wechseln lässt. Die Option für atomares Schreiben von Foundation verhindert, dass beim normalen Schreiben unvollständiger Inhalt am Zielpfad sichtbar wird.
WORK="$(mktemp -d)"
mkdir -p "$WORK/bin" "$WORK/home"
cat > "$WORK/main.swift" <<'SWIFT'
import Foundation
func fail(_ message: String) -> Never {
FileHandle.standardError.write(Data((message + "\n").utf8))
exit(2)
}
let args = Array(CommandLine.arguments.dropFirst())
if args == ["--help"] {
print("usage: taskprobe --output PATH TEXT")
exit(0)
}
guard args.count == 3, args[0] == "--output",
!args[1].isEmpty else {
fail("expected --output PATH TEXT")
}
let target = URL(fileURLWithPath: args[1])
do {
try Data(args[2].utf8).write(to: target, options: .atomic)
print("written")
} catch {
fail("cannot write output: \(error.localizedDescription)")
}
SWIFT
xcrun swiftc -O "$WORK/main.swift" -o "$WORK/bin/taskprobe"
xcrun verwendet die aktuell ausgewählte Entwickler-Toolchain. Vor dem Kompilieren lässt sich die Auswahl mit xcode-select -p prüfen; das ersetzt keinen tatsächlichen Test auf der Zielversion von macOS. Die Abnahme muss sich auf genau die Binärdatei beziehen, die ausgeliefert wird. Kompilieren Sie nach der Abnahme nicht erneut, um anschließend ein anderes Artefakt auszuliefern.
Erfolg und Fehler in einem separaten Verzeichnis prüfen
Das folgende Skript verwendet WORK aus derselben Shell-Sitzung wie der vorherige Abschnitt. Bei jeder Prüfung werden beide Ausgabeströme getrennt gespeichert. Der Test wechselt absichtlich in ein temporäres Arbeitsverzeichnis und entfernt die meisten geerbten Umgebungsvariablen. Benötigt das auszuliefernde Tool tatsächlich eine Variable, gehört sie ausdrücklich in den Aufrufvertrag. Sie darf nicht nur zufällig in der interaktiven Shell vorhanden sein.
set -eu
BIN="$WORK/bin/taskprobe"
cd "$WORK/home"
env -i PATH=/usr/bin:/bin HOME="$WORK/home" \
"$BIN" --help >"$WORK/help.out" 2>"$WORK/help.err"
grep -Fx 'usage: taskprobe --output PATH TEXT' "$WORK/help.out"
test ! -s "$WORK/help.err"
if "$BIN" --output >"$WORK/bad.out" 2>"$WORK/bad.err"; then
echo "missing arguments returned success" >&2
exit 1
fi
test ! -s "$WORK/bad.out"
grep -F 'expected --output' "$WORK/bad.err"
"$BIN" --output "$WORK/home/result.txt" 'hello 云端 Mac' \
>"$WORK/good.out" 2>"$WORK/good.err"
grep -Fx 'written' "$WORK/good.out"
test ! -s "$WORK/good.err"
grep -Fx 'hello 云端 Mac' "$WORK/home/result.txt"
if "$BIN" --output "$WORK/home/absent/result.txt" test \
>"$WORK/fail.out" 2>"$WORK/fail.err"; then
echo "unwritable path returned success" >&2
exit 1
fi
test ! -e "$WORK/home/absent/result.txt"
test ! -s "$WORK/fail.out"
test -s "$WORK/fail.err"
Der erste Fehlerfall prüft ungültige Argumente, der zweite einen Schreibfehler. Erzeugen Sie ein „nicht beschreibbares Verzeichnis“ nicht mit chmod: Hat der Testprozess besondere Berechtigungen, kann das Ergebnis anders ausfallen. Ein nicht vorhandenes Elternverzeichnis ist direkter, deckt aber ebenfalls nur eine Art von I/O-Fehler ab und beweist nicht, dass alle Datenträgerfehler wie erwartet behandelt werden.
Drei häufige Ursachen für falsch positive Tests erkennen
Eine Pipeline verdeckt den Exitcode
Bei taskprobe ... | tee output.log stammt der Exitcode normalerweise vom letzten Befehl der Pipeline, nicht vom Tool. Leiten Sie in Abnahmeskripten vorzugsweise beide Ausgabeströme direkt um und prüfen Sie anschließend den Befehlsstatus. Falls eine Pipeline nötig ist, aktivieren Sie pipefail in einer Shell, die diese Option unterstützt, und dokumentieren Sie die verwendete Shell.
Das aktuelle Verzeichnis passt nur zufällig
Relative Pfade hängen vom Startverzeichnis ab. Der obige Test startet die Binärdatei aus home und übergibt einen absoluten Ausgabepfad, um versteckte Annahmen über das Arbeitsverzeichnis aufzudecken. Muss ein echtes Tool mitgelieferte Ressourcen lesen, sollte es deren eindeutig festgelegten Speicherort verwenden. Es darf nicht voraussetzen, dass ein Job immer im Wurzelverzeichnis des Quellcodes startet.
Fehlermeldungen werden als Daten behandelt
Allein zu prüfen, ob „der Befehl etwas ausgegeben hat“, ist wertlos. Ein nachgelagerter Job könnte die Standardausgabe direkt als Ergebnisdatei speichern. Die Abnahme muss gleichzeitig sicherstellen: Bei Fehlern ist die Standardausgabe leer, die Standardfehlerausgabe enthält Text und der Exitcode ist ungleich null. Bei Erfolg ist die Standardfehlerausgabe leer; außerdem wird der tatsächliche Inhalt der erzeugten Datei geprüft. Gibt das Tool strukturierte Daten aus, muss auch deren Format validiert werden, statt nur die Dateigröße zu vergleichen.
Vor der Auslieferung die Grenzen der Tests prüfen
Legen Sie das Abnahmeskript neben dem Build-Artefakt ab und lassen Sie es vom selben Build-Job ausführen. Halten Sie nach erfolgreichem Test die Prüfsumme der Binärdatei und Angaben zur ausgewählten Toolchain fest, damit sich beides bei der Übergabe abgleichen lässt. Das ausführende Konto sollte auf dieselben Verzeichnisse zugreifen können wie das Konto des späteren Jobs. Verwenden Sie für Dateitests jeweils ein neues temporäres Verzeichnis, damit alte Artefakte test -e nicht unbeabsichtigt bestehen lassen.
Die Tests in diesem Artikel simulieren weder Stromausfälle noch volle Datenträger oder gleichzeitige Änderungen der Zieldatei durch andere Prozesse. Sie beweisen auch nicht, dass das Programm auf ungetesteten Systemversionen läuft. Wenn solche Garantien nötig sind, ergänzen Sie jeweils Fehlerinjektion, Koordination konkurrierender Zugriffe und eine Abnahme auf dem Zielsystem. Migrieren Sie Quellcode, Skripte und Abnahmeprotokolle vor Ablauf der Mietzeit. Die übernehmende Person muss sämtliche Prüfungen in der neuen Umgebung erneut ausführen: „Hat früher funktioniert“ ist kein Abnahmeergebnis für eine neue Maschine.
Häufig gestellte Fragen
Warum gehören Fehlermeldungen nicht in die Standardausgabe?
Aufrufende Skripte können die Standardausgabe als Datendatei speichern. Fehlermeldungen gehören nach stderr und müssen von einem Exitcode ungleich null begleitet werden.
Beweist ein Dateitest bereits atomisches Schreiben?
Nein. Der Test prüft sichtbare Ergebnisse. Die Implementierung muss außerdem sicherstellen, dass temporäre Datei und Zieldatei auf demselben Dateisystem liegen.
Sie brauchen einen exklusiven Mac in der Cloud?
Wählen Sie Modell, Standort und Abrechnungszeitraum. Sie erhalten einen exklusiven physischen Mac. Die tatsächliche Verfügbarkeit sehen Sie live in der Konsole.