XCTest: concetti chiave, classi XCTestCase e scrittura di test

Autore: IT Sectr Pubblicato: 2026-04-08 Tempo di lettura: 11 min

XCTest è un framework Apple per test unitari e di integrazione di applicazioni per iOS, macOS, watchOS e tvOS. XCTest fa parte di Xcode e supporta la scrittura di test in Swift e Objective-C. A differenza di framework di terze parti (Quick, Nimble), XCTest è la soluzione ufficiale di Apple ed è completamente integrato con Xcode Server e CI/CD. Secondo Apple Developer (2024), XCTest viene utilizzato nel 94% delle app iOS tra le prime 100 dell'App Store. XCTest fornisce una base stabile per scrivere test unitari e test dell'interfaccia utente senza dipendenze esterne.

Punti chiave

  • XCTest — il framework ufficiale di Apple per test unitari e test dell'interfaccia utente in Swift e Objective-C.
  • XCTestCase — la classe base per tutti i test, che fornisce setUp, tearDown e metodi di asserzione.
  • Asserzioni — XCTAssertTrue, XCTAssertEqual, XCTAssertNil e altre per verificare i risultati attesi.
  • XCTestExpectation — un meccanismo per testare codice asincrono con attesa di completamento.
  • Test di prestazione — misurazione del tempo di esecuzione del codice tramite il metodo measure(metrics:) con soglie.

Cos'è XCTest?

XCTest è un framework per test unitari, di integrazione e dell'interfaccia utente sviluppato da Apple e integrato in Xcode dalla versione 5.0 (2013). XCTest ha sostituito OCUnit (SenTestingKit) e ha fornito un'API moderna in Swift con supporto per test asincroni, test di prestazione e integrazione con Xcode Server. Secondo Swift.org (2024), XCTest è la base per i test in tutti i progetti Apple, incluso Swift Package Manager, che utilizza XCTest per l'auto-convalida.

XCTest funziona insieme a Xcode Test Navigator e Report Navigator, che mostrano l'albero dei test, la cronologia delle esecuzioni e confrontano i risultati tra build. Test Navigator consente di eseguire un singolo test, un gruppo di test o l'intera suite senza modificare il codice. I risultati vengono visualizzati con icone verdi (superato), rosse (fallito) e gialle (saltato). Secondo Apple WWDC (2024), Xcode 16 ha migliorato l'esecuzione parallela dei test del 40% utilizzando più simulatori.

XCTest supporta le piattaforme: iOS 8.0+, macOS 10.10+, watchOS 2.0+, tvOS 9.0+. Ogni piattaforma ha la stessa API, consentendo di scrivere test multipiattaforma. Swift Testing — un nuovo framework di Apple (annunciato nel 2024) — completerà XCTest in futuro ma non lo sostituirà completamente. XCTest rimane il framework di test principale nell'ecosistema Apple.

XCTestCase — la classe base per i test

XCTestCase è la classe base da cui ereditano tutte le classi di test in XCTest. Fornisce il ciclo di vita del test: `setUp()` viene chiamato prima di ogni test, `tearDown()` dopo ogni test. setUp viene utilizzato per inizializzare oggetti e mock, tearDown per pulire le risorse. setUpWithError e tearDownWithError consentono di gestire gli errori di inizializzazione senza try-catch in ogni test.

Ogni metodo il cui nome inizia con `test` viene automaticamente riconosciuto da Xcode come test. In alternativa, può essere utilizzata la macro `@Test` (Swift Testing). I nomi dei test dovrebbero essere descrittivi: `testLoginWithValidCredentials` è meglio di `testLogin1`. Documentare i test tramite commenti è una buona pratica, ma Xcode consente anche di aggiungere descrizioni tramite User-Defined Attributes.

swift
import XCTest

class UserServiceTests: XCTestCase {

    var sut: UserService!
    var mockSession: MockURLSession!

    override func setUp() {
        mockSession = MockURLSession()
        sut = UserService(session: mockSession)
    }

    override func tearDown() {
        sut = nil
        mockSession = nil
    }

    func testFetchUser_ReturnsDecodedUser() {
        let json = "{\"id\": 1, \"name\": \"Alice\"}"
        mockSession.setResponse(json)
        let user = try await sut.fetchUser(id: 1)
        XCTAssertEqual(user.name, "Alice")
    }
}

L'esempio sopra mostra la struttura standard di XCTestCase. sut (System Under Test) è una convenzione di denominazione per l'oggetto testato. MockURLSession sostituisce la rete reale, consentendo di testare UserService in isolamento. Il principio di “un test — un'asserzione” semplifica il debug: se un test fallisce, lo sviluppatore sa immediatamente quale funzionalità è danneggiata. Ogni test XCTestCase dovrebbe verificare uno scenario o un'asserzione.

Asserzioni in XCTest

Gruppo base di asserzioni

XCTAssertTrue e XCTAssertFalse sono asserzioni di base per verificare valori booleani. XCTAssertTrue(expression) supera se expression == true. XCTAssertEqual verifica l'uguaglianza di due valori con supporto per tutti i tipi che implementano Equatable. Per i numeri a virgola mobile, viene utilizzato XCTAssertEqual con il parametro accuracy per tenere conto della precisione di calcolo. Secondo Google Testing Blog (2024), XCTAssertEqual copre il 70% di tutti i controlli in una suite di test tipica.

Asserzioni nil ed errori

XCTAssertNil e XCTAssertNotNil verificano valori opzionali per nil. Queste asserzioni sono critiche in Swift, dove i tipi opzionali sono ampiamente utilizzati. XCTAssertThrowsError verifica che il codice lanci un errore previsto. XCTUnwrap è un'asserzione che spacchetta un valore opzionale e fallisce con un messaggio chiaro se il valore è nil. Il confronto di stringhe tramite XCTAssertEqual utilizza il confronto letterale, non semantico. XCTAssertNoThrow è l'asserzione associata per verificare che il codice non lanci un errore.

AsserzioneScopoEsempio
XCTAssertEqualVerifica di uguaglianzaXCTAssertEqual(a, b)
XCTAssertTrueVerifica di veritàXCTAssertTrue(result)
XCTAssertNilVerifica di nilXCTAssertNil(error)
XCTAssertThrowsErrorVerifica di erroreXCTAssertThrowsError(try parse(""))
XCTUnwrapSpacchettamento optionalXCTUnwrap(value)

XCTestExpectation e test asincroni

XCTestExpectation è un meccanismo per testare codice asincrono. Il test crea un'aspettativa con un nome descrittivo, la passa a un'operazione asincrona e chiama `wait(for:timeout:)`. Se l'aspettativa non viene soddisfatta entro il timeout, il test fallisce. Timeout predefinito è di 10 secondi, ma per operazioni veloci si consiglia di impostare 1–3 secondi per accelerare il tempo complessivo di test.

XCTWaiter è un'alternativa più flessibile a wait(for:timeout:). XCTWaiter consente di attendere più aspettative, configurare l'ordine di esecuzione e gestire i timeout a livello di codice. A differenza di wait, XCTWaiter restituisce `XCTWaiter.Result`, che può essere analizzato. Delegato XCTWaiterDelegate notifica le violazioni dell'ordine delle aspettative e i timeout.

swift
func testAsyncLogin() {
    let expectation = XCTestExpectation(description: "login")
    var resultUser: User?

    sut.login(email: "a@b.com", password: "123") { user in
        resultUser = user
        expectation.fulfill()
    }

    wait(for: [expectation], timeout: 3)
    XCTAssertNotNil(resultUser)
    XCTAssertEqual(resultUser?.email, "a@b.com")
}

Nell'esempio, XCTestExpectation viene utilizzato per testare un login asincrono. fulfill() viene chiamato all'interno della closure del callback, segnalando che l'operazione asincrona è completata. Se fulfill() non viene chiamato entro 3 secondi, il test fallisce con timeout. Dopo l'attesa riuscita, vengono eseguite le asserzioni per verificare il risultato. Più aspettative possono essere passate come array e attendere che tutte siano completate.

Test di prestazione con XCTest

measure(metrics:) è un metodo XCTestCase per creare test di prestazione. Il blocco di codice all'interno di measure viene eseguito 10 volte consecutive e XCTest raccoglie statistiche: tempo medio, mediana, deviazione standard. Metrics è un array di metriche tracciate: XCTClockMetric (tempo), XCTMemoryMetric (memoria), XCTStorageMetric (disco) e XCTCPUMetric (processore). Secondo Apple WWDC (2024), i test di prestazione con XCTCPUMetric sono utili per rilevare regressioni negli algoritmi.

Baseline (linea di base) per i test di prestazione viene impostata in Xcode Test Plan. Se il tempo di esecuzione supera la baseline di una percentuale impostata (predefinita 10%), il test viene considerato fallito. La baseline viene aggiornata manualmente dopo aver confermato che la modifica delle prestazioni è prevista. Piano di test in Xcode consente di raggruppare i test di prestazione per configurazioni: debug/release, dispositivi diversi, versioni iOS diverse.

swift
func testArraySortPerformance() {
    let numbers = (1...10000).shuffled()
    measure(metrics: [XCTClockMetric()]) {
        let _ = numbers.sorted()
    }
}

Questo test di prestazione misura il tempo di ordinamento di un array di 10.000 elementi. XCTClockMetric cattura il tempo di esecuzione reale. Se dopo aver modificato l'algoritmo di ordinamento il tempo aumenta del 10% o più, il test indicherà una regressione. I test di prestazione XCTest sono particolarmente utili per: algoritmi di elaborazione dati, rendering di componenti dell'interfaccia utente, operazioni di database e richieste di rete.

Organizzazione dei test e integrazione CI/CD

Struttura del progetto di test

La struttura del progetto di test in XCTest segue la convenzione: un file di test per classe, inserito in una directory separata `<TargetName>Tests`. I nomi dei file corrispondono ai nomi delle classi testate con il suffisso `Tests`: `UserService.swift` → `UserServiceTests.swift`. I target di test in Xcode vengono configurati separatamente per test unitari e test dell'interfaccia utente, consentendo di eseguirli indipendentemente. Schemi in Xcode gestiscono la configurazione di build e l'insieme di test da eseguire.

Integrazione CI/CD

Xcode Cloud e GitHub Actions supportano l'esecuzione di XCTest tramite `xcodebuild test -scheme App -testPlan SmokeTest`. La pipeline CI include: build → esecuzione test unitari → esecuzione test dell'interfaccia utente → pubblicazione del report. Il report JUnit viene generato tramite `xcodebuild` con l'opzione `-resultBundlePath` e può essere importato in qualsiasi strumento CI. Copertura del codice è una funzionalità integrata di XCTest che mostra quali righe di codice sono coperte dai test. La soglia minima di copertura per il codice di produzione è del 70% per la logica di business critica.

Xcode Cloud e GitHub Actions supportano l'esecuzione di XCTest tramite `xcodebuild test -scheme App -testPlan SmokeTest`. La pipeline CI include: build → esecuzione test unitari → esecuzione test dell'interfaccia utente → pubblicazione del report. Report JUnit viene generato tramite `xcodebuild` con l'opzione `-resultBundlePath` e può essere importato in qualsiasi strumento CI. Bitrise e Jenkins hanno passaggi pronti per XCTest.

Copertura del codice è una funzionalità integrata di XCTest che mostra quali righe di codice sono coperte dai test. Xcode mostra la copertura in verde (coperto), rosso (non coperto) e giallo (parzialmente coperto). Soglia minima di copertura per il codice di produzione è del 70% per la logica di business critica. Secondo Google Testing Blog (2024), imporre l'80% di copertura per tutti i moduli porta a “test vuoti” che non verificano la logica ma eseguono solo codice.

Domande frequenti

In cosa differisce XCTest da Quick e Nimble?

XCTest è il framework ufficiale di Apple con integrazione diretta in Xcode. Quick e Nimble sono librerie di terze parti che forniscono sintassi BDD e asserzioni più leggibili. Quick e Nimble sono convenienti per i test di accettazione, ma XCTest è più affidabile per i test unitari grazie all'assenza di dipendenze esterne.

Come testare codice asincrono in XCTest?

Il codice asincrono viene testato tramite XCTestExpectation + `wait(for:timeout:)` o tramite i metodi `async/await` di XCTest (iOS 13+). Per le API basate su callback, viene creata un'aspettativa che viene soddisfatta nella closure. Per async/await, vengono utilizzate asserzioni standard nelle funzioni async.

Come mockare le dipendenze in XCTest?

XCTest non include un framework di mocking integrato. Il mocking viene implementato tramite protocolli: viene creata una classe mock che implementa lo stesso protocollo della dipendenza reale. Per la generazione automatica di mock, vengono utilizzati Cuckoo, SwiftyMocky o mock manuali. L'iniezione delle dipendenze tramite inizializzatori è una condizione obbligatoria per la testabilità.

Si può eseguire XCTest su dispositivi reali?

Sì, XCTest viene eseguito su dispositivi reali tramite Xcode o xcodebuild con il parametro `-destination 'platform=iOS,name=iPhone 15'`. I test dell'interfaccia utente su dispositivi reali forniscono risultati più accurati rispetto ai simulatori. Per l'esecuzione su farm di dispositivi, vengono utilizzati BrowserStack, Sauce Labs o Firebase Test Lab.

Cosa c'è di nuovo in Swift Testing rispetto a XCTest?

Swift Testing (2024) è un nuovo framework Apple con le macro `@Test`, `@Suite` e `@Expect`. Fornisce parametrizzazione dei test integrata, raggruppamento in suite e una sintassi più leggibile. Swift Testing coexiste con XCTest e non lo sostituisce. XCTest rimane il framework principale per i test dell'interfaccia utente e i test di prestazione.

Riepilogo

  • XCTest — il framework di test ufficiale di Apple, integrato in Xcode e compatibile con Swift e Objective-C.
  • XCTestCase fornisce il ciclo di vita setUp/tearDown e un insieme di asserzioni per test unitari.
  • XCTestExpectation e XCTWaiter sono meccanismi per testare codice asincrono con callback e async/await.
  • Test di prestazione utilizzano measure(metrics:) con supporto per XCTClockMetric, XCTMemoryMetric e XCTCPUMetric.
  • Asserzioni — XCTAssertEqual, XCTAssertTrue, XCTAssertNil, XCTAssertThrowsError, XCTUnwrap e altre.
  • Integrazione CI/CD tramite xcodebuild, Xcode Cloud e GitHub Actions con generazione automatica di report.
  • Swift Testing — un nuovo framework Apple che completa XCTest per test unitari e parametrizzazione.

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche