Ein Team exportiert Datensätze aus einer iOS-App als CSV und lässt auf einem entfernten Build-Knoten Tests laufen. Die Unit-Tests melden, dass die Datei erfolgreich geschrieben wurde. Beim Öffnen stellt ein Kollege jedoch fest, dass eine Bemerkung auf zwei Zeilen verteilt ist und eine andere Nutzereingabe als Tabellenformel interpretiert wird. Abgenommen werden muss hier nicht die Teilen-Schaltfläche, sondern die exportierten Bytes, die Feldgrenzen und der Umgang mit gefährlichen Eingaben. Auf einem Cloud-Mac lassen sich diese Prüfungen ohne manuelle Interaktion bei jedem Build ausführen.
Den Exportvertrag zuerst festlegen
Dieser Artikel geht von einem eindeutigen Textformat aus: UTF-8 ohne Byte Order Mark; jedes Feld steht in doppelten Anführungszeichen, und doppelte Anführungszeichen innerhalb eines Felds werden verdoppelt. Zwischen Datensätzen und am Dateiende steht jeweils CRLF. Zeilenumbrüche innerhalb eines Felds bleiben unverändert. Deshalb darf man nicht die gesamte CSV-Datei zunächst an Zeilenumbrüchen aufteilen, um Datensätze zu zählen.
Hinzu kommt eine Sicherheitsregel: Beginnt ein Freitextfeld nach dem Entfernen führender und nachgestellter Leer- und Steuerzeichen mit =, +, - oder @, schlägt der Export fehl. Damit werden auch manche negativen Zahlen zurückgewiesen, die als gewöhnlicher Text zulässig wären. Braucht das Produkt tatsächlich numerische Spalten, sollten Werte anhand der jeweiligen Spaltendefinition geparst und validiert werden, statt die Regel für alle Textspalten zu lockern. Auch die Kopfzeile durchläuft dieselbe Kodierungsfunktion, damit es nicht zwei unterschiedliche Escaping-Implementierungen gibt.
Anführungszeichen in CSV regeln die Feldabgrenzung, nicht die Interpretation von Formeln durch Tabellenprogramme. Ein vorangestelltes einfaches Anführungszeichen führt bei verschiedenen Importprogrammen nicht unbedingt zum selben Ergebnis. Gefährliche Eingaben zurückzuweisen ist einfacher zu definieren und zu testen; das Verhalten des vorgesehenen Importprogramms muss trotzdem geprüft werden.
Prüfbare Bytes mit einer Funktion erzeugen
Die Kodierungslogik gehört in eine Swift-Datei, auf die sowohl die App als auch das Test-Target zugreifen können. Die folgende Funktion nimmt ein zweidimensionales String-Array entgegen. In der Anwendung lässt sich das Datenmodell zuvor in Spalten mit festgelegter Reihenfolge umwandeln. trimmingCharacters dient ausschließlich der Risikoerkennung; beim Export bleibt der ursprüngliche Wert erhalten, statt Nutzerdaten unbemerkt zu verändern.
import Foundation
enum CSVExportError: Error {
case unsafeValue
}
func csvField(_ value: String) throws -> String {
let ignored = CharacterSet.whitespacesAndNewlines
.union(.controlCharacters)
let probe = value.trimmingCharacters(in: ignored)
if let first = probe.first, "=+-@".contains(first) {
throw CSVExportError.unsafeValue
}
let escaped = value.replacingOccurrences(of: "\"", with: "\"\"")
return "\"" + escaped + "\""
}
func csvDocument(_ rows: [[String]]) throws -> Data {
let lines = try rows.map { row in
try row.map(csvField).joined(separator: ",")
}
return Data((lines.joined(separator: "\r\n") + "\r\n").utf8)
}
Der aufrufende Code sollte außerdem prüfen, ob jede Zeile so viele Spalten wie die Kopfzeile hat, festlegen, ob bei einem leeren Datensatz nur die Kopfzeile exportiert oder ein Hinweis angezeigt wird, und Fehler in sichtbare Meldungen übersetzen. Nach dem Abfangen von unsafeValue darf nicht stattdessen der ungeprüfte Originaltext ausgegeben werden: Die Tests könnten dann bestehen, während die tatsächlich geteilte Datei die Regel umgeht. Enthält der Export sensible Daten, müssen vor dem Teilen auch die Zieldatei und ihre Zugriffsrechte geprüft werden.
Erfolgs- und Ablehnungsfall gemeinsam testen
Tests sollten die tatsächliche Ausgabe vergleichen und nicht nur prüfen, ob Data nicht leer ist. Der erste Test deckt Kommas, Anführungszeichen, chinesische Zeichen und einen Zeilenumbruch innerhalb eines Felds ab. Der zweite stellt sicher, dass ein führender Tabulator die Prüfung nicht umgeht.
import XCTest
final class CSVExportTests: XCTestCase {
func testQuotingAndLineEndings() throws {
let data = try csvDocument([
["name", "note"],
["张三", "甲,\"乙\"\n丙"]
])
XCTAssertEqual(
String(decoding: data, as: UTF8.self),
"\"name\",\"note\"\r\n\"张三\",\"甲,\"\"乙\"\"\n丙\"\r\n"
)
}
func testFormulaLikeTextIsRejected() {
for value in ["=1+1", "\t@SUM(A1:A2)", "+1", " -2"] {
XCTAssertThrowsError(try csvDocument([[value]]))
}
}
}
Diese Tests belegen das vereinbarte Ausgabeformat und die Ablehnungsregel. Sie belegen nicht, dass jedes Tabellenprogramm die Datei wie erwartet importiert. Insbesondere Zeilenumbrüche innerhalb von Anführungszeichen, Text im Datumsformat und Nicht-ASCII-Zeichen müssen mit dem Importverfahren geprüft werden, das die Empfänger tatsächlich verwenden.
Nachprüfbare Ergebnisse auf dem Build-Knoten sichern
Zuerst prüfen, ob die Xcode-Kommandozeilenwerkzeuge auf dem Cloud-Mac verfügbar sind. Anschließend die Tests mit einem im Projekt freigegebenen Scheme ausführen. Die Beispielnamen müssen durch die tatsächlichen Namen im Repository ersetzt werden; mit xcodebuild -list -project App.xcodeproj lässt sich der Projekteinstieg vorab prüfen. Das Test-Target sollte direkt auf die Datei mit der Kodierungslogik zugreifen können, statt eine separate Kopie der Implementierung zu verwenden.
xcodebuild test \
-project App.xcodeproj \
-scheme App \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-only-testing:AppTests/CSVExportTests \
-resultBundlePath "$PWD/CSVExport.xcresult"
Der Simulatorname muss zu einer auf diesem Knoten installierten Runtime passen. Das lässt sich vorab mit xcrun simctl list devices available prüfen. Der Pfad CSVExport.xcresult darf vor der Ausführung noch nicht existieren. Bei wiederholten Läufen braucht jeder Job ein eigenes Verzeichnis, damit alte Ergebnisse nicht stören. Dieser Test prüft ausschließlich die Kodierung in reinem Swift und benötigt keine grafische Oberfläche. Soll zusätzlich der Teilen-Ablauf geprüft werden, sind für UI-Tests eine eigene Sitzung und passende Berechtigungen vorzubereiten.
Von Testdaten zur tatsächlichen Datei
Nach erfolgreichen Unit-Tests über den tatsächlichen Exportpfad der App eine Datei mit festgelegten Beispieldaten erzeugen. Sie sollte mindestens ein leeres Feld, ein Komma, doppelte Anführungszeichen, einen Zeilenumbruch innerhalb eines Felds und chinesische Zeichen enthalten. Dabei prüfen, ob die Datei wirklich mit demselben csvDocument erzeugt wird und nicht die Teilen-Schicht erneut Strings zusammensetzt. Beim Speichern an einem für die App beschreibbaren Ort kann Data.write(to:options: .atomic) verhindern, dass eine nur teilweise geschriebene Datei gelesen wird. Auch das Aufräumen temporärer Dateien nach einem fehlgeschlagenen Teilen-Vorgang sollte anhand des App-Ablaufs getestet werden.
Die „Zeilenzahl“ im Terminal allein ist kein verlässliches Erfolgskriterium: Innerhalb von Anführungszeichen können Zeilenumbrüche vorkommen. Belastbarer ist es, die Beispieldaten Feld für Feld zu importieren und Spaltenreihenfolge, letzten Datensatz und Fehlermeldungen zu prüfen. Wandelt das Importprogramm Kennungen eigenmächtig in Zahlen oder Datumstext in Datumswerte um, betrifft das einen weiteren Formatvertrag. Dafür braucht es ausdrücklich definierte Spaltentypen oder ein spezielles Austauschformat; CSV-Anführungszeichen fixieren keinen Zelltyp.
Checkliste vor dem Merge
- Kopfzeile und Datenzeilen haben dieselbe Spaltenzahl und -reihenfolge; leere Werte werden gemäß Vertrag ausgegeben.
- Für UTF-8-Text, Anführungszeichen, Kommas und Zeilenumbrüche innerhalb von Feldern gibt es exakte Assertions.
- Formelartige Eingaben lösen einen sichtbaren Fehler aus; es wird keine ungeprüfte Ersatzdatei erzeugt.
- Die CI verwendet einen auf dem Knoten tatsächlich vorhandenen Simulator und bewahrt für jeden Lauf eigenständige Testergebnisse auf.
- Die Teilen-Schicht liest die getesteten Bytes; die manuelle Abnahme im Ziel-Importprogramm dokumentiert jede automatische Typumwandlung.
Die am schwersten zu entdeckenden Fehler beim CSV-Export stecken oft nicht im Standardbeispiel, sondern in einer Bemerkung aus echten Nutzerdaten. Grenzfälle zuerst in feste Tests zu überführen und danach die fertige Datei zu prüfen, ist wesentlich verlässlicher, als Probleme erst nach dem Export mit bloßem Auge zu suchen.
Häufig gestellte Fragen
Müssen Anführungszeichen innerhalb eines zitierten CSV-Felds maskiert werden?
Ja. Jedes innere doppelte Anführungszeichen wird verdoppelt; anschließend wird das gesamte Feld in doppelte Anführungszeichen gesetzt.
Verhindert ein vorangestelltes Apostroph Formeln in jeder Tabellenanwendung?
Nein. Importprogramme verhalten sich unterschiedlich. Das Beispiel weist verdächtige Textwerte zurück; der tatsächliche Import muss separat getestet werden.
Führen Sie Ihren nächsten Xcode-Build auf einer dedizierten physischen Maschine aus.
Wählen Sie Apple-Silicon-Modell, Standort und Laufzeit und führen Sie Ihre eigenen Build-Prozesse über eine Remote-Verbindung aus. Die tatsächliche Verfügbarkeit sehen Sie beim Bestellen in der Konsole.