Damit jedes Szenario in Xcode ein eigenes Ergebnis und eine eigene Möglichkeit zum erneuten Ausführen erhält, migriere einen schleifenbasierten XCTest-Unit-Test zu Swift Testings parametrisiertem @Test. Diese Anleitung veranschaulicht die Migration anhand einer lokalen Berechnung des Kachel-Layouts und überprüft das Auswertungsverhalten.
Warum wird in einer XCTest-Schleife nicht jedes Szenario separat ausgewertet?
XCTest erkennt Testmethoden in einer Unterklasse von XCTestCase. Diese Methoden haben keine Parameter und ihre Namen beginnen mit test. Eine Schleife innerhalb einer erkannten Methode bleibt Teil dieser Methode; ihre Iterationen werden nicht als separat erkannte Tests behandelt. Ein Szenarioname in einer Assertion kann die Diagnose erleichtern, erzeugt aber keine eigene Möglichkeit zum Ausführen. Siehe Apples Defining Test Cases and Test Methods.
Auch der Lebenszyklus richtet sich nach derselben Grenze. XCTest führt die Instanz-Setup-Methoden vor jeder Testmethode und die Teardown-Methoden danach aus. Eine Schleife mit fünf Iterationen erhält daher nicht automatisch fünf Setup- und Teardown-Zyklen. Wenn eine Iteration eine Test-Fixture verändert, kann die nächste Iteration diesen Zustand übernehmen, sofern der Test ihn nicht ausdrücklich zurücksetzt. Apple beschreibt diesen Ablauf unter Set Up and Tear Down State in Your Tests.
Swift Testing bietet das hier benötigte Ergebnis: Eine Funktion nimmt Szenarioargumente entgegen, und der Runner behandelt ihre Aufrufe als unabhängige Fälle. Xcode zeigt die Argumente an und bietet einzelne Steuerelemente zum Ausführen. Apple demonstriert dieses Verhalten unter Go further with Swift Testing.
Der Unterschied ist bei der Fehlerdiagnose wichtig. Ein fehlgeschlagenes Assertion und ein vorzeitiger Abbruch sind unterschiedliche Ereignisse: Eine Schleife kann nach einer gewöhnlichen Assertion weiterprüfen, während ein geworfener Fehler oder ein ausdrückliches return die Ausführung späterer Iterationen verhindern kann. Werden die Szenarien in einzelne Fälle aufgeteilt, entfällt dieser gemeinsame Kontrollfluss zwischen ihnen. Ein Prozessabbruch lässt sich dadurch nicht abfangen.
Vorhandenes Unit-Test-Target vorbereiten
Verwende für diesen Ablauf Xcode 16 oder neuer. Swift Testing ist in diesen Xcode-Versionen und in Swift.org-Toolchains ab Swift 6.0 enthalten. Das Swift-Projekt empfiehlt für gewöhnliche Testarbeiten die integrierte Version; für dieses Beispiel ist keine externe Paketabhängigkeit erforderlich. Siehe Obtaining Swift Testing.
Behalte das vorhandene Unit-Test-Bundle bei. Apple unterstützt Swift Testing und XCTest ausdrücklich im selben Target, sodass eine schrittweise Migration mit einer neuen Swift-Datei beginnen kann. Wähle in Xcode File > New > New File From Template, dann Swift Testing Unit Test, und füge die Datei dem vorhandenen Unit-Test-Target hinzu. Wenn das Projekt noch kein Test-Target hat, erstelle über File > New > Target ein Unit Testing Bundle. Diese Schritte sind unter Adding tests to your Xcode project dokumentiert.
In dieser Anleitung wird Berechnungscode direkt getestet. Ein Desktop-Fenster wird nicht automatisiert. Apples dokumentierter Weg für die UI-Automatisierung verwendet weiterhin XCTest; nutze diesen parametrisierten Ansatz für die zugrunde liegende Logik, die das Layout bestimmt. Derselbe guide to adding tests beschreibt diese Abgrenzung zwischen den Frameworks.
Jedes Szenario in einen vollständigen Argumentdatensatz umwandeln
Angenommen, eine Mac-App ordnet Kacheln in Reihen an. Bei vier Spalten sind für null Kacheln null Reihen erforderlich, vier Kacheln füllen eine Reihe und fünf Kacheln benötigen zwei Reihen. Die konkrete Aufgabe besteht darin, diese Prüfungen beizubehalten und zugleich jedes Szenario unabhängig sichtbar und ausführbar zu machen.
Verwende einen Datensatz, der den Szenarionamen, die Eingaben und die erwartete Ausgabe enthält. So bleibt das erwartete Ergebnis mit seiner Eingabe verknüpft, wenn Fälle hinzugefügt oder umsortiert werden. Die folgenden fünf Datensätze sind beispielhafte Testdaten und keine Beobachtungen aus einer vorhandenen Anwendung.
| Szenario | Kachelanzahl | Spalten | Erwartete Reihen | Zweck |
|---|---|---|---|---|
empty |
0 | 4 | 0 | Leere Eingabe |
one-tile |
1 | 4 | 1 | Unvollständige erste Reihe |
exact-row |
4 | 4 | 1 | Exakter Grenzwert |
wrap |
5 | 4 | 2 | Erste Kachel jenseits des Grenzwerts |
two-full-rows |
8 | 4 | 2 | Exaktes Vielfaches |
Lege diese vollständige, eigenständige Datei im Unit-Test-Target ab. Sie enthält eine synthetische Implementierung, sodass sie sich ohne App-Modul oder weitere Dateien ausprobieren lässt. Sie verwendet keine Dienste, Mocks, Dateien oder Netzwerkzugriffe.
import Testing
private struct TileLayout {
func rowCount(tileCount: Int, columns: Int) -> Int {
precondition(tileCount >= 0)
precondition(columns > 0)
let fullRows = tileCount / columns
let partialRow = tileCount % columns == 0 ? 0 : 1
return fullRows + partialRow
}
}
private struct LayoutScenario:
Codable, Sendable, CustomTestStringConvertible {
let name: String
let tileCount: Int
let columns: Int
let expectedRows: Int
var testDescription: String { name }
static let examples: [LayoutScenario] = [
.init(name: "empty", tileCount: 0,
columns: 4, expectedRows: 0),
.init(name: "one-tile", tileCount: 1,
columns: 4, expectedRows: 1),
.init(name: "exact-row", tileCount: 4,
columns: 4, expectedRows: 1),
.init(name: "wrap", tileCount: 5,
columns: 4, expectedRows: 2),
.init(name: "two-full-rows", tileCount: 8,
columns: 4, expectedRows: 2)
]
}
struct TileLayoutTests {
@Test("Tile rows", arguments: LayoutScenario.examples)
fileprivate func rowCountMatchesScenario(
_ scenario: LayoutScenario
) {
let layout = TileLayout()
let actualRows = layout.rowCount(
tileCount: scenario.tileCount,
columns: scenario.columns
)
#expect(actualRows == scenario.expectedRows)
}
}
Bei der Migration wird die Iteration nach @Test(arguments:) verlagert: Jedes Element wird zum Argument eines Aufrufs. Codable stellt die Kodierungsunterstützung für gezielte erneute Ausführungen bereit, während CustomTestStringConvertible die lesbare Szenariobeschreibung liefert. Apple dokumentiert die Parametrisierung unter Implementing parameterized tests und benutzerdefinierte Argumentbeschreibungen unter Go further with Swift Testing.
Die Test-Fixture wird absichtlich innerhalb der Testfunktion erstellt. Jeder Aufruf erzeugt sein eigenes TileLayout; kein Szenario hängt von einem vorherigen ab. Verschiebst du eine vorhandene Fixture, migriere ihr Setup bewusst, statt davon auszugehen, dass geerbte XCTest-Hooks ausgeführt werden. Swift-Testing-Suites erben nicht von XCTestCase; Apple ordnet das Setup einem Initialisierer und das Teardown einer Klasse oder eines Actors deinit zu. Siehe Migrating a test from XCTest.
Die Berechnung akzeptiert nichtnegative Kachelanzahlen und strikt positive Spaltenanzahlen. Ihre Vorbedingungen legen diesen Vertrag fest; ungültige Werte gehören nicht zu den Fällen dieses Beispiels. Muss echter Anwendungscode für ungültige Eingaben einen behandelbaren Fehler zurückgeben, ändere den API-Vertrag und ergänze passende Tests, bevor du diese Tabelle erweiterst.
Separate Ergebnisse bestätigen und einen fehlgeschlagenen Fall erneut ausführen
Führe die parametrisierte Funktion über die Raute neben ihrer Deklaration aus. Öffne dann den Test navigator und erweitere die Funktion, um ihre Fälle zu prüfen. Zeige auf die Raute eines Falls und klicke auf dessen Wiedergabesteuerung, um nur diesen Fall auszuführen. Über die Wiedergabesteuerung der übergeordneten Funktion werden alle Fälle ausgeführt. Apple dokumentiert beide Aktionen unter Running tests and interpreting results.
Ändere für eine kontrollierte Überprüfung der Auswertung vorübergehend den Wert expectedRows des Datensatzes wrap von 2 zu 3. Führe die gesamte parametrisierte Funktion aus. Erwartet werden fünf Fallausführungen; der Vergleich wrap schlägt fehl, weil die Implementierung 2 zurückgibt. Wähle diesen Fall aus und führe ihn einzeln erneut aus. Stelle danach den erwarteten Wert auf 2 zurück. Dies ist eine bewusste Änderung der Testdaten und kein Beleg für einen Implementierungsfehler.
Prüfe unter Results und Arguments in den Details der fehlgeschlagenen Erwartung den berechneten Wert im Vergleich zur Eingabe des Szenarios. Öffne für den vollständigen Durchlauf View > Navigators > Reports, wähle die Testaktion aus und prüfe den Abschnitt Tests. Diese Steuerelemente zur Prüfung werden in Apples test-results guide beschrieben.
Für die selektive Ausführung ist neben lesbaren Bezeichnungen eine weitere Voraussetzung erforderlich: Der Runner muss jedes Argument deterministisch zuordnen können. Laut Apples aktueller Dokumentation werden CustomTestArgumentEncodable, bestimmte RawRepresentable-Typen, Encodable oder Identifiable mit einem kodierbaren Bezeichner akzeptiert. Der Codable-Datensatz des Beispiels erfüllt die Kodierungsanforderung. Eine benutzerdefinierte Anzeigebeschreibung allein reicht dafür nicht aus. Siehe Implementing parameterized tests.
Die erwartete Arithmetik für alle fünf Datensätze wurde lokal mit ganzzahliger Division und Restberechnung in Python überprüft. Eine beispielhafte fehlerhafte Implementierung, die nur Ganzzahldivision verwendet, schlägt bei one-tile und wrap fehl. Die Swift-Datei wurde geprüft, aber in dieser Rechercheumgebung nicht kompiliert, da Swift nicht verfügbar war; der Xcode-Durchlauf oben bleibt der ausführbare Verifizierungsschritt.
Beabsichtigte Fallanzahl und Lebenszyklus beibehalten
Eine nützliche Migrationsprüfung vergleicht drei Dinge: die Anzahl der Szenariodatensätze, die im vollständigen Durchlauf ausgeführten Fälle und die Möglichkeit, einen ausgewählten Fall allein auszuführen. Für dieses Beispiel beträgt die vorgesehene Anzahl fünf. Bei einer anderen Anzahl solltest du zunächst die Argumenterstellung und Testauswahl prüfen, bevor du die Berechnung änderst.
Fasse zusammengehörige Erwartungen zusammen. Werden zwei getrennte Sammlungen an arguments: übergeben, entsteht ihr kartesisches Produkt. Fünf Eingaben und fünf erwartete Ausgaben ergäben daher 25 Kombinationen statt fünf zusammengehöriger Szenarien. Verwende getrennte Sammlungen, wenn jede Kombination sinnvoll ist, und Datensätze oder zip() für zusammengehörige Daten. Apple erklärt den Unterschied unter Implementing parameterized tests.
Wenn du zip() verwendest, prüfe die Längen der Sammlungen, bevor du die Paare erstellst: Swift bricht beim kürzeren der beiden Sequenzen ab. Ein zusätzliches erwartetes Ergebnis könnte andernfalls ungenutzt bleiben, ohne einen Fall zu erzeugen. Ein einzelner Szenariodatensatz vermeidet diese Art von Abweichung. Das Abschneideverhalten ist in Swifts Referenz zu zip(::) dokumentiert.
Auch die Ausführungsreihenfolge beeinflusst die Migrationsentscheidung. Swift Testing führt Fälle standardmäßig parallel aus. Dieses Beispiel hat unveränderliche Argumente und eine lokale Fixture; seine Ergebnisse hängen daher nicht von der Reihenfolge ab. Benötigt eine parametrisierte Funktion vorübergehend eine serielle Ausführung, füge .serialized zum @Test-Attribut hinzu. Wird dieses Merkmal auf eine Suite angewendet, werden auch ihre enthaltenen Tests und Fälle serialisiert, nicht aber unabhängige Suites. Siehe Running tests serially or in parallel.
Serielle Ausführung erzeugt keinen frischen Zustand und legt keine Abhängigkeit zwischen Szenarien fest. Wenn ein Fall erst erfolgreich ist, nachdem ein anderer etwas geändert hat, gestalte seine Vorbereitung so um, dass er unabhängig ausgeführt werden kann. Bilden die Schritte bewusst einen zusammenhängenden Vorgang, behalte sie in einem Test bei und parametrisiere stattdessen vollständige Vorgänge.
Verwende im gesamten migrierten Beispiel Assertions aus Swift Testing. Wenn du Hilfsfunktionen wiederverwendest, die XCTest-Assertions enthalten, prüfe das Interoperabilitätsverhalten der Toolchain: Apples aktueller Migrationsleitfaden dokumentiert Modi und Standardeinstellungen, die je nach Toolchain und Version der Package Tools variieren. Wenn diese Datei vollständig Testing verwendet, entfällt diese Abhängigkeit. Siehe Migrating a test from XCTest.
Muss die Migration warten, ist die unterstützte XCTest-Alternative eine benannte, parameterlose Testmethode pro Szenario, ergänzt bei Bedarf durch gemeinsame Berechnungs- und Assertion-Hilfsfunktionen. Apple empfiehlt in seinem guide to adding tests einen Test pro Szenario. So bleiben die Ergebnisse der einzelnen Methoden getrennt, ohne dass die Parametrisierung von Swift Testing erforderlich ist.
Die Umstellung ist abgeschlossen, wenn jedem ursprünglichen Szenario ein ausgeführter Fall entspricht, seine Fixture mit dem vorgesehenen Zustand beginnt und ein ausgewählter Fall für sich allein erfolgreich ausgeführt werden kann. Behalte die Grenzwerte und erwarteten Ausgaben während der Umstellung bei: Die unabhängige Auswertung ist nützlich, weil sich diese vorhandenen Prüfungen damit leichter finden, diagnostizieren und wiederholen lassen.
Quellen
- Defining Test Cases and Test Methods — Apple
- Set Up and Tear Down State in Your Tests — Apple
- Go further with Swift Testing — Apple
- Obtaining Swift Testing — Swift-Projekt
- Adding tests to your Xcode project — Apple
- Implementing parameterized tests — Apple
- Migrating a test from XCTest — Apple
- Running tests and interpreting results — Apple
- zip(::) — Apple
- Running tests serially or in parallel — Apple