iOS-URL-Parameter auf dem Cloud-Mac zuverlässig testen
Im Suchfeld steht C++ 入门, auf dem Server kommt jedoch C 入门 an. Oder zwei Filter werden mit demselben Schlüssel übergeben, am Ende bleibt aber nur einer übrig. Solche Fehler liegen meist nicht an verlorenen Netzwerkpaketen, sondern an unterschiedlichen Annahmen bei der clientseitigen Kodierung und der serverseitigen Dekodierung. Wer den URL-Builder auf einem Cloud-Mac als reine Funktion isoliert und seine Grenzfälle mit XCTest absichert, findet die Ursache leichter als durch wiederholtes Mitschneiden von Produktionsanfragen. Ein Simulator ist dafür nicht nötig.
Zuerst den Parametervertrag festlegen
Ein Query-String ist kein Dictionary. Für die Schnittstelle muss zunächst feststehen: Darf ein Schlüssel mehrfach vorkommen? Ist die Reihenfolge relevant? Sind ein fehlender Parameter, ein Parameter ohne Wert und eine leere Zeichenfolge gleichbedeutend? Verwendet der Server die Regeln der Formulardekodierung? Dass Frontend und Backend jeweils „die Standardbibliothek verwenden“, bedeutet nicht, dass sie diese Fragen gleich beantworten.
So sind flag, flag= und das vollständige Fehlen von flag drei unterschiedliche Eingaben. Der Client kann sie mit nil als Wert eines URLQueryItem, einer leeren Zeichenfolge beziehungsweise dem Weglassen des Eintrags ausdrücken. Ob das serverseitige Framework diese Unterschiede erhält, muss mit dem tatsächlich eingesetzten Parser geprüft werden.
Ein wörtliches Pluszeichen in einer gewöhnlichen URL-Abfrage ist nicht automatisch ein Leerzeichen. Die Interpretation von Pluszeichen als Leerzeichen gehört zur Formularkodierung und lässt sich nicht pauschal auf alle Abfrageschnittstellen übertragen.
Diese Vereinbarungen sollten sich in den Testnamen wiederfinden. Ein einzelner Test nach dem Muster „normale Suche erfolgreich“ reicht nicht: Chinesischer Text kann korrekt ankommen, während Pluszeichen oder wiederholte Filter weiterhin Probleme verursachen.
URLs mit einer reinen Funktion aufbauen
Legen Sie ein macOS-Unit-Test-Target an und fügen Sie die folgende Swift-Datei hinzu. Die Beispieladresse wird nur lokal geparst; es werden keine Anfragen gesendet. Bei der Einbindung in die Anwendung ersetzen Sie die feste Adresse durch die Schnittstellenkonfiguration des Projekts. Parameterwerte müssen als unveränderter Originaltext übergeben werden, nicht als zuvor manuell prozentkodierte Zeichenfolgen.
import Foundation
import XCTest
func makeQueryURL(
items: [URLQueryItem],
formDecoder: Bool = false
) throws -> URL {
var parts = URLComponents()
parts.scheme = "https"
parts.host = "xcodenode.com"
parts.path = "/query-fixture"
parts.queryItems = items
if formDecoder, let encoded = parts.percentEncodedQuery {
parts.percentEncodedQuery = encoded.replacingOccurrences(
of: "+",
with: "%2B"
)
}
return try XCTUnwrap(parts.url)
}
final class QueryEncodingTests: XCTestCase {
func testReservedCharactersRoundTrip() throws {
let value = "C++ 入门 & x=1 # 50% / ?"
let url = try makeQueryURL(items: [
URLQueryItem(name: "q", value: value)
])
let parts = try XCTUnwrap(
URLComponents(url: url, resolvingAgainstBaseURL: false)
)
let items = try XCTUnwrap(parts.queryItems)
XCTAssertEqual(items.count, 1)
XCTAssertEqual(items.first?.value, value)
XCTAssertNil(parts.fragment)
}
func testDuplicatesAndEmptyValues() throws {
let url = try makeQueryURL(items: [
URLQueryItem(name: "tag", value: "swift"),
URLQueryItem(name: "tag", value: "ios"),
URLQueryItem(name: "flag", value: nil),
URLQueryItem(name: "empty", value: "")
])
let parts = try XCTUnwrap(
URLComponents(url: url, resolvingAgainstBaseURL: false)
)
let items = try XCTUnwrap(parts.queryItems)
XCTAssertEqual(items.map(\.name), [
"tag", "tag", "flag", "empty"
])
XCTAssertEqual(items.map(\.value), [
"swift", "ios", nil, ""
])
}
func testFormDecoderReceivesLiteralPlus() throws {
let value = "C++ 50%"
let url = try makeQueryURL(
items: [URLQueryItem(name: "q", value: value)],
formDecoder: true
)
let parts = try XCTUnwrap(
URLComponents(url: url, resolvingAgainstBaseURL: false)
)
let query = try XCTUnwrap(parts.percentEncodedQuery)
XCTAssertFalse(query.contains("+"))
let pair = try XCTUnwrap(
query.split(separator: "=", maxSplits: 1).last
)
let decoded = String(pair)
.replacingOccurrences(of: "+", with: " ")
.removingPercentEncoding
XCTAssertEqual(decoded, value)
}
}
Der erste Test prüft, dass reservierte Zeichen keine zusätzlichen Parameter oder Fragmente erzeugen. Der zweite bestätigt, dass doppelte Schlüssel und die Reihenfolge im Array erhalten bleiben. Der dritte simuliert einen einzelnen Durchlauf der Formulardekodierung. Er bildet keinen vollständigen serverseitigen Parser ab, sondern prüft lediglich den Grenzfall des Pluszeichens in einem einzelnen Wert. Einen Integrationstest der Schnittstelle ersetzt er nicht.
Mehr Fehlerfälle testen statt mehr Netzwerkzugriffe
Ergänzen Sie dieselbe Test-Fixture um chinesischen Text, kombinierende Zeichen, Emojis, leere Werte, führende und nachgestellte Leerzeichen sowie die wörtliche Zeichenfolge %2B. Der letzte Fall ist besonders wichtig: Ein vom Benutzer eingegebenes Prozentzeichen muss kodiert werden und darf nicht als bereits kodiertes Pluszeichen gelten.
Wenn %252B auftaucht, prüfen Sie zuerst, ob die Eingabe ein unkodiertes Pluszeichen oder die wörtliche Zeichenfolge %2B war. Im ersten Fall deutet das Ergebnis meist auf eine doppelte Kodierung hin; im zweiten kann es genau richtig sein. Wer allein wegen %25 einen Fehler annimmt, kann mit der vermeintlichen Korrektur neue Probleme verursachen.
Kodieren Sie nicht die gesamte bereits zusammengesetzte URL mit einem pauschalen, weit gefassten Zeichensatz. Parameternamen, Parameterwerte und Pfade gehören zu unterschiedlichen Kontexten. Das direkte Einfügen von &, = oder # kann die Struktur verändern. Überlassen Sie die Struktur dem Builder und prüfen Sie anschließend Anzahl, Namen und Werte der geparsten Einträge getrennt.
Für überlange Suchbegriffe ist eine eigene Begrenzung der Eingabelänge nötig. Die Bytezahl nach der Kodierung unterscheidet sich von der Zeichenzahl. Testen Sie die Begrenzungsstrategie daher anhand des Schnittstellenvertrags, statt anzunehmen, dass ein chinesisches Zeichen nur ein Byte belegt.
Signierte Anfragen zusätzlich auf Byte-Ebene prüfen
Semantisch gleiche URLs müssen nicht aus denselben Bytes bestehen. Die Parameterreihenfolge, die Darstellung von Leerzeichen und die Groß- oder Kleinschreibung in Prozent-Escape-Sequenzen können die Signatur beeinflussen. Bei gewöhnlichen Abfragen genügt es, gleiche Werte nach dem Kodieren und Dekodieren zu prüfen. Signaturtests müssen zusätzlich die vom Protokoll vorgeschriebene kanonische Zeichenfolge verifizieren.
Verlangt das Protokoll eine Sortierung, muss zuerst feststehen, wie doppelte Schlüssel sortiert werden. Erst danach wird die endgültige Zeichenfolge signiert. Muss die Reihenfolge erhalten bleiben, verwenden Sie weiterhin das Array der Einträge und bauen Sie es nicht über ein Dictionary neu auf. Wechseln Sie nach dem Signieren nicht mehr den Modus zur Behandlung von Pluszeichen, sonst stimmt der übertragene Inhalt nicht mehr mit der Signatureingabe überein.
Festgelegte Testdaten sollten doppelte Schlüssel, leere Werte und Nicht-ASCII-Text enthalten. Prüfen Sie die endgültige kodierte Zeichenfolge mit einer Assertion. „Festgelegt“ meint hier das im Projekt vereinbarte Format, nicht die ungeprüfte Übernahme einer einmaligen Systemausgabe als dauerhaften Standard. Auch serverseitig sollten dieselben Testdaten vorliegen, damit sich identische Signatureingaben auf beiden Seiten bestätigen lassen.
Auf dem Cloud-Knoten ausführen und Nachweise sichern
Für das Test-Target reicht Foundation aus; eine App-Signierung muss nicht eingerichtet werden. Führen Sie die Tests über das freigegebene Scheme des Test-Targets aus. Die folgenden Namen sind durch die tatsächlichen Projektnamen zu ersetzen. Das Ergebnisverzeichnis darf noch nicht existieren.
set -euo pipefail
mkdir -p artifacts
result="artifacts/query-$(uuidgen).xcresult"
xcodebuild test \
-project QueryContract.xcodeproj \
-scheme QueryContract \
-destination 'platform=macOS' \
-resultBundlePath "$result"
Halten Sie vor dem Lauf die Xcode-Version, die Commit-Kennung und den Builder-Modus fest. Innerhalb derselben Testserie muss die Toolchain unverändert bleiben. Prüfen Sie nach einem Toolchain-Upgrade die Signaturbytes erneut, statt nur auf die Anzahl grüner Testergebnisse zu achten.
Für die Abnahme sind vier Punkte entscheidend: Die Originalwerte wurden nicht vorab kodiert; doppelte Schlüssel gingen nicht in einem Dictionary verloren; der Schalter für die Formulardekodierung richtet sich nach dem Schnittstellenvertrag; und der tatsächliche serverseitige Parser besteht dieselben Testfälle. Im Fehlerbericht genügen die Eingabekategorie und das kodierte Ergebnis. Sobald echte Suchbegriffe oder Tokens betroffen sind, verwenden Sie stattdessen synthetische Testdaten.
Am Ende sichern Tests der reinen Funktion die clientseitige Struktur ab, während serverseitige Vertragstests die Dekodierungsgrenzen prüfen. Erst wenn beide Ebenen bestehen, ist die korrekte Übertragung der Parameter belegt — und nicht bloß, dass zwei clientseitige Parsing-Vorgänge zufällig dasselbe Ergebnis liefern.
Häufig gestellte Fragen
Kodiert URLComponents jedes Pluszeichen als %2B?
Nein. Ein Pluszeichen darf in einer URL-Abfrage unverändert bleiben, während ein Formulardekoder es als Leerzeichen lesen kann. Verlangt der Endpunkt Formulardekodierung, ersetzen Sie nach der Kodierung verbleibende Pluszeichen in percentEncodedQuery durch %2B und testen auch den Serverdekoder.
Darf ich Parameter vor dem Signieren in ein Dictionary umwandeln?
Nur wenn das Protokoll doppelte Schlüssel verbietet, die Reihenfolge ignoriert und eine Normalisierung festlegt. Andernfalls behalten Sie ein URLQueryItem-Array und prüfen die endgültige kodierte Bytefolge für die Signatur separat.
Der passende Cloud-Mac für Ihre Entwicklungsprojekte
Physischer Apple-Silicon-Knoten statt VM. Mieten Sie tage-, wochen-, monats- oder quartalsweise. Modelle und Preise sind transparent einsehbar; die aktuelle Verfügbarkeit wird im Portal in Echtzeit angezeigt.