Per assegnare a ogni scenario un risultato e un controllo di riesecuzione distinti in Xcode, converti un test unitario XCTest basato su un ciclo in un @Test parametrizzato di Swift Testing. Questa guida usa un calcolo locale del layout di una griglia per mostrare la migrazione e verificarne il comportamento nei risultati.
Perché un ciclo XCTest non riporta separatamente ogni scenario?
XCTest individua i metodi di test in una sottoclasse di XCTestCase. Questi metodi non accettano parametri e hanno nomi che iniziano con test. Un ciclo all’interno di un metodo individuato resta parte di quel metodo; le sue iterazioni non diventano test individuati separatamente. Aggiungere il nome di uno scenario a un’asserzione può migliorare la diagnostica, ma non crea un controllo di esecuzione individuale. Consulta Defining Test Cases and Test Methods di Apple.
Il ciclo di vita segue lo stesso confine. XCTest esegue i metodi di configurazione dell’istanza prima di ogni metodo di test e i metodi di pulizia dopo. Un ciclo di cinque iterazioni non riceve quindi automaticamente cinque cicli di configurazione e pulizia. Se un’iterazione modifica un fixture, quella successiva può ereditare lo stato, a meno che il test non lo reimposti esplicitamente. Apple documenta questa sequenza in Set Up and Tear Down State in Your Tests.
Swift Testing offre il risultato che serve in questo caso: una funzione accetta gli argomenti degli scenari e il runner tratta le relative invocazioni come casi indipendenti. Xcode ne mostra gli argomenti e offre controlli di esecuzione individuali. Apple illustra questo comportamento in Go further with Swift Testing.
La distinzione è importante quando si diagnosticano gli errori. Il fallimento di un’asserzione e un’uscita anticipata sono eventi diversi: un ciclo può continuare a eseguire i controlli dopo una normale asserzione, mentre un errore lanciato o un return esplicito possono impedire l’esecuzione delle iterazioni successive. Separare gli scenari in casi elimina il flusso di controllo condiviso tra gli scenari. Non rende recuperabile un errore che termina il processo.
Preparare il target di test unitari esistente
Per questa procedura, usa Xcode 16 o versioni successive. Swift Testing è incluso in queste distribuzioni di Xcode e nelle toolchain di Swift.org a partire da Swift 6.0. Il progetto Swift consiglia di usare la versione integrata per le normali attività di test; questo esempio non richiede dipendenze da pacchetti esterni. Consulta Obtaining Swift Testing.
Mantieni il bundle di test unitari esistente. Apple supporta esplicitamente Swift Testing e XCTest nello stesso target, quindi una migrazione graduale può iniziare con un solo nuovo file Swift. In Xcode, scegli File > New > New File From Template, seleziona Swift Testing Unit Test e aggiungi il file al target di test unitari esistente. Se il progetto non ha un target di test, creane uno Unit Testing Bundle tramite File > New > Target. Questi passaggi sono documentati in Adding tests to your Xcode project.
Questa guida testa direttamente il codice di calcolo. Non automatizza una finestra desktop. Il percorso documentato da Apple per l’automazione dell’interfaccia continua a usare XCTest; usa questo approccio parametrizzato per la logica sottostante che determina il layout. La stessa guida all’aggiunta di test descrive questa distinzione tra framework.
Trasformare ogni scenario in un record completo di argomenti
Supponiamo che un’app per Mac disponga delle tessere in righe. Con quattro colonne, zero tessere richiedono zero righe, quattro tessere riempiono una riga e cinque tessere ne richiedono due. L’obiettivo concreto è mantenere questi controlli rendendo al contempo ogni scenario visibile ed eseguibile in modo indipendente.
Usa un singolo record che contenga il nome dello scenario, gli input e il risultato atteso. In questo modo, quando aggiungi o riordini i casi, il risultato atteso resta associato al relativo input. I cinque record seguenti sono dati di test illustrativi, non osservazioni provenienti da un’applicazione esistente.
| Scenario | Numero di tessere | Colonne | Righe attese | Scopo |
|---|---|---|---|---|
empty |
0 | 4 | 0 | Input vuoto |
one-tile |
1 | 4 | 1 | Prima riga incompleta |
exact-row |
4 | 4 | 1 | Limite esatto |
wrap |
5 | 4 | 2 | Prima tessera oltre il limite |
two-full-rows |
8 | 4 | 2 | Multiplo esatto |
Inserisci questo file completo e autonomo nel target di test unitari. Include un’implementazione sintetica che consente di provarlo senza un modulo dell’app o altri file. Non usa servizi, mock, file o accesso alla rete.
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)
}
}
La migrazione sposta l’iterazione in @Test(arguments:): ogni elemento diventa l’argomento di un’invocazione. Codable fornisce il supporto alla codifica necessario per rieseguire casi specifici, mentre CustomTestStringConvertible fornisce un’etichetta leggibile per lo scenario. Apple documenta la parametrizzazione in Implementing parameterized tests e le descrizioni personalizzate degli argomenti in Go further with Swift Testing.
Il fixture viene creato intenzionalmente all’interno della funzione di test. Ogni invocazione crea una propria TileLayout; nulla dipende da uno scenario precedente. Se stai migrando un fixture esistente, sposta la configurazione in modo esplicito invece di aspettarti che vengano eseguiti gli hook XCTest ereditati. Le suite di Swift Testing non ereditano da XCTestCase; Apple spiega come mappare la configurazione su un inizializzatore e la pulizia di classe o attore su deinit in Migrating a test from XCTest.
Il calcolo accetta conteggi di tessere non negativi e conteggi di colonne strettamente positivi. I suoi precondizioni definiscono questo contratto; i valori non validi non rientrano nei casi di questo esempio. Se il codice reale dell’applicazione deve restituire un errore recuperabile per input non validi, modifica il contratto dell’API e aggiungi i test corrispondenti prima di ampliare questa tabella.
Verificare i risultati separati e rieseguire un singolo caso non riuscito
Esegui la funzione parametrizzata usando il rombo accanto alla sua dichiarazione. Poi apri il Test navigator ed espandi la funzione per esaminarne i casi. Per eseguire un solo caso, posiziona il puntatore sul rombo di quel caso e fai clic sul relativo controllo di riproduzione. Facendo clic sul controllo di riproduzione della funzione principale esegui tutti i suoi casi. Apple documenta entrambe le operazioni in Running tests and interpreting results.
Per una verifica controllata dei risultati, modifica temporaneamente il valore expectedRows del record wrap, passando da 2 a 3. Esegui l’intera funzione parametrizzata. Il risultato atteso è l’esecuzione di cinque casi, con il confronto wrap che fallisce perché l’implementazione restituisce 2. Seleziona quel caso e rieseguilo singolarmente, poi ripristina il valore atteso 2. Si tratta di una modifica deliberata dei dati di test, non di una prova di un difetto dell’implementazione.
Esamina i dettagli Results e Arguments dell’aspettativa non riuscita per confrontare il valore calcolato con l’input dello scenario. Per l’esecuzione completa, apri View > Navigators > Reports, seleziona l’azione di test ed esamina la sezione Tests. Questi controlli di ispezione sono descritti nella guida di Apple ai risultati dei test.
L’esecuzione selettiva richiede anche un requisito ulteriore rispetto alle etichette leggibili: il runner deve poter associare in modo deterministico ogni argomento. La documentazione attuale di Apple accetta CustomTestArgumentEncodable, alcuni tipi RawRepresentable, Encodable oppure Identifiable con un identificatore codificabile. Il record Codable dell’esempio soddisfa il requisito tramite codifica. Una stringa di visualizzazione personalizzata, da sola, non è sufficiente. Consulta Implementing parameterized tests.
L’aritmetica attesa per tutti e cinque i record è stata verificata localmente con la divisione intera e il resto di Python. Un’implementazione errata illustrativa che usa solo la divisione con arrotondamento per difetto fallisce per one-tile e wrap. Il file Swift è stato esaminato, ma non compilato in questo ambiente di ricerca perché Swift non era disponibile; l’esecuzione descritta sopra in Xcode resta il passaggio di verifica eseguibile.
Mantenere il numero di casi previsto e il ciclo di vita
Un controllo utile durante la migrazione confronta tre elementi: il numero di record di scenario, i casi eseguiti nell’esecuzione completa e la possibilità di eseguire da solo un caso selezionato. In questo esempio, il numero previsto è cinque. Se il numero è diverso, controlla la costruzione degli argomenti e la selezione dei test prima di modificare il calcolo.
Mantieni unite le aspettative abbinate. Passare due raccolte separate a arguments: genera il prodotto cartesiano. Cinque input e cinque risultati attesi produrrebbero quindi 25 combinazioni invece di cinque scenari abbinati. Usa raccolte separate quando ogni combinazione è significativa; usa record o zip() per i dati abbinati. Apple spiega la distinzione in Implementing parameterized tests.
Se usi zip(), controlla la lunghezza delle raccolte prima di costruire le coppie: Swift si ferma alla sequenza più corta. Un risultato atteso aggiuntivo potrebbe così restare inutilizzato senza generare un caso. Un singolo record di scenario evita questo specifico disallineamento. Il comportamento di troncamento è documentato nel riferimento Swift zip(::).
Anche l’ordine di esecuzione influisce sulla decisione di migrazione. Per impostazione predefinita, Swift Testing esegue i casi in parallelo. Questo esempio ha argomenti immutabili e un fixture locale, quindi i risultati non dipendono dall’ordine. Per una funzione parametrizzata che richiede temporaneamente un’esecuzione seriale, aggiungi .serialized all’attributo @Test. Applicando questo trait a una suite, vengono serializzati anche i test e i casi che contiene, ma non le suite non correlate. Consulta Running tests serially or in parallel.
L’esecuzione seriale non crea uno stato nuovo né definisce un contratto di dipendenza tra scenari. Se un caso riesce solo dopo che un altro ha modificato qualcosa, riprogetta la sua configurazione affinché possa essere eseguito in modo indipendente. Se i passaggi costituiscono intenzionalmente un’unica operazione continua, mantienili in un solo test e parametrizza invece le operazioni complete.
In questo esempio migrato, usa sempre le asserzioni di Swift Testing. Quando riutilizzi helper che contengono asserzioni XCTest, controlla il comportamento di interoperabilità della toolchain: la guida attuale di Apple alla migrazione documenta modalità e valori predefiniti che variano a seconda della toolchain e della versione degli strumenti del pacchetto. Mantenere questo file interamente in Testing evita tale dipendenza. Consulta Migrating a test from XCTest.
Se la migrazione deve attendere, l’alternativa supportata in XCTest consiste nell’usare un metodo di test denominato e senza parametri per ogni scenario, con helper condivisi per il calcolo e le asserzioni, se opportuno. Nella sua guida all’aggiunta di test, Apple consiglia un test per ogni scenario. In questo modo si mantengono risultati separati per metodo senza richiedere la parametrizzazione di Swift Testing.
La conversione è completa quando ogni scenario originale corrisponde a un caso eseguito, il fixture parte dallo stato previsto e un caso selezionato può essere eseguito correttamente da solo. Durante la modifica, mantieni gli input limite e i risultati attesi: il reporting indipendente è utile perché rende questi controlli esistenti più facili da individuare, diagnosticare e ripetere.
Fonti
- 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 — progetto Swift
- 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