Il test di integrazione verifica la correttezza dell’interazione tra i componenti di un’applicazione mobile — moduli, servizi, database e API esterne. A differenza dei test unitari che isolano ogni componente, i test di integrazione rilevano errori nei punti di giunzione: incompatibilità dei formati dei dati, errori nella trasmissione dei parametri ed elaborazione scorretta delle risposte del server. Secondo Martin Fowler, 2018, i test di integrazione coprono fino al 40% dei difetti critici sfuggiti ai test unitari e forniscono fiducia nella stabilità del sistema prima del rilascio.
Punti chiave
I test di integrazione sono una fase di verifica del software in cui si valuta la correttezza dell’interazione tra singoli moduli o sottosistemi di un’applicazione. Mentre i test unitari verificano ogni componente in modo isolato, i test di integrazione riuniscono questi componenti e verificano come funzionano insieme. Gli scenari tipici includono il trasferimento di dati tra il livello di rete e il repository, la scrittura in un database tramite ORM e l’elaborazione delle risposte da API di terze parti.
Nel contesto dello sviluppo mobile, i test di integrazione coprono le interazioni tra il livello UI, la logica di business e le fonti di dati. Ad esempio, un test può verificare che dopo aver cliccato sul pulsante “Login”, l’applicazione invii una richiesta al server, riceva un token e lo salvi nell’archivio locale. Tale verifica conferma che la catena di componenti funziona senza errori.
Secondo il World Quality Report 2023, le aziende che applicano regolarmente i test di integrazione riducono il numero di incidenti di produzione del 35% rispetto ai progetti che si affidano solo ai test unitari. Ciò rende i controlli di integrazione un elemento obbligatorio della strategia di garanzia della qualità nello sviluppo commerciale.
Le applicazioni mobili sono costituite da molti componenti interconnessi: richieste di rete, database locali, notifiche push, servizi di sistema e SDK di terze parti. Ciascuno di questi componenti viene sviluppato separatamente, ma in fase di esecuzione scambiano dati in tempo reale. I test di integrazione rilevano difetti che non possono essere trovati con la verifica isolata dei moduli.
Tra i problemi tipici scoperti dai test di integrazione figurano la mancata corrispondenza dei tipi di dati tra l’API e il modello dell’applicazione, errori di serializzazione JSON, gestione errata dei timeout di rete e errori durante l’accesso concorrente al database tramite Room o Core Data. Senza verifiche di integrazione, tali difetti raggiungono la produzione e si manifestano solo con utenti reali.
La ricerca del Google Testing Blog (2021) mostra che il costo per correggere un difetto scoperto durante i test di integrazione è 5 volte inferiore rispetto a dopo il rilascio. Ciò accade perché nelle fasi iniziali lo sviluppatore ha il contesto completo dell’errore e può correggerlo senza un ciclo urgente di hotfix. Investire tempo nella scrittura di test di integrazione si ripaga attraverso la riduzione dei costi di manutenzione e l’aumento della fiducia degli utenti.
Esistono tre approcci principali per organizzare i test di integrazione: Big Bang, Bottom-Up e Top-Down. La scelta della strategia dipende dalle dimensioni del progetto, dall’architettura dell’applicazione e dalla disponibilità dei componenti al momento della scrittura dei test. Ogni approccio ha i suoi vantaggi e limiti che è importante considerare quando si pianifica la copertura dei test.
Big Bang — approccio in cui tutti i componenti del sistema vengono collegati simultaneamente, dopodiché viene eseguita una sessione di test generale. Questo metodo è semplice da implementare: non è necessario scrivere stub o emulare singoli moduli. Tuttavia, quando viene rilevato un errore, è difficile determinare quale componente lo abbia causato. Big Bang è giustificato in progetti piccoli con architettura semplice dove il numero di moduli non supera cinque.
Bottom-Up — strategia in cui i test di integrazione iniziano con i componenti di basso livello: database, livello di rete, servizi di sistema. Dopo aver verificato ogni livello, i test collegano gradualmente i moduli di livello superiore — repository, classi Use Case e ViewModel. Il vantaggio principale è la rilevazione precoce di difetti negli strati fondamentali dell’applicazione, riducendo il rischio di errori a cascata nelle fasi successive dello sviluppo.
Top-Down — approccio in cui i test iniziano con i componenti di alto livello — schermate UI e navigazione, mentre i moduli di livello inferiore vengono simulati utilizzando stub o mock. Ciò consente di verificare gli scenari utente prima che il lato server o il database siano completamente implementati. Top-Down è particolarmente utile durante lo sviluppo parallelo delle parti client e server quando il backend non è ancora pronto per l’integrazione reale.
Per i test di integrazione delle applicazioni mobili viene utilizzata una serie di strumenti specializzati, suddivisi in tre categorie: librerie per l’emulazione del server, framework per lavorare con i database e strumenti di verifica dei servizi di sistema. La scelta dello strumento specifico dipende dalla piattaforma — Android o iOS — e dallo stack tecnologico del progetto.
Vediamo esempi pratici di test di integrazione per Android e iOS. Per la piattaforma Android, utilizziamo MockWebServer insieme a JUnit, per iOS — XCTest con la libreria OHHTTPStubs. Entrambi gli esempi verificano lo scenario di ricezione dei dati da un’API e il loro salvataggio in un repository locale.
Questo test verifica che una richiesta Retrofit al server emulato restituisca JSON corretto e che il repository converta la risposta in un modello di dominio. MockWebServer intercetta la richiesta e restituisce il JSON specificato, dopodiché il test confronta il risultato atteso con quello effettivo.
class UserRepositoryTest {
private val mockServer = MockWebServer()
@Before
fun setup() {
mockServer.start()
}
@Test
fun fetchUser_returnsCorrectData() {
val json = "{ \`"id\`": 1, \`"name\`": \`"Alice\`" }"
mockServer.enqueue(MockResponse()
.setBody(json)
.setResponseCode(200))
val repository = UserRepository(
createRetrofit(mockServer.url("/").toString()))
val user = repository.fetchUser(1)
assertEquals(1, user.id)
assertEquals("Alice", user.name)
}
@After
fun tearDown() {
mockServer.shutdown()
}
}
Per iOS, un test simile utilizza OHHTTPStubs per intercettare le richieste URL. La libreria sostituisce la risposta del server a livello del framework di sistema URL Loading System, consentendo di testare qualsiasi libreria di rete — URLSession, Alamofire o Moya.
import XCTest
import OHHTTPStubs
import OHHTTPStubsSwift
class UserRepositoryTests: XCTestCase {
func testFetchUser_returnsCorrectData() {
stub(condition: isPath("/users/1")) { _ in
return HTTPStubsResponse(
jsonObject: ["id": 1, "name": "Alice"],
statusCode: 200,
headers: nil
)
}
let repository = UserRepository()
let expectation = expectation(description: "fetch user")
repository.fetchUser(id: 1) { user in
XCTAssertEqual(user.id, 1)
XCTAssertEqual(user.name, "Alice")
expectation.fulfill()
}
waitForExpectations(timeout: 2.0)
}
}
Test di integrazione efficaci richiedono il rispetto di una serie di pratiche che aumentano la stabilità dei test e riducono i costi di manutenzione. Isolate le dipendenze esterne: utilizzate database in memoria invece di istanze di produzione ed emulate API di terze parti utilizzando librerie stub. Ciò elimina gli errori non deterministici causati dalla disponibilità della rete o dallo stato dei servizi esterni.
Mantenete l’indipendenza dei test: ogni test di integrazione dovrebbe funzionare in modo isolato, senza dipendere dai risultati di altri test. Utilizzate le annotazioni @Before e @After in JUnit o setUp e tearDown in XCTest per preparare e pulire l’ambiente di test. Ciò previene l’influenza reciproca tra i test e semplifica la diagnosi degli errori.
Coprite i casi limite: i test di integrazione dovrebbero verificare non solo gli scenari di successo (happy path) ma anche la gestione degli errori — timeout, codici HTTP 4xx e 5xx, risposte vuote, JSON malformato. Secondo il Google Testing Blog (2022), il 60% degli incidenti di produzione è correlato a una gestione errata dei casi limite che non erano coperti dai test.
Domande frequenti
I test unitari verificano una singola classe o funzione in modo isolato, sostituendo le dipendenze con stub. I test di integrazione verificano l’interazione di più componenti reali — ad esempio, una connessione di rete e un database contemporaneamente.
L’esecuzione dei test di integrazione richiede solitamente da 2 a 15 minuti a seconda del numero di test e della complessità dell’ambiente. Per progetti grandi, si consiglia di suddividere i test in job paralleli in un sistema CI per ridurre il tempo totale di verifica prima del merge.
In primo luogo, i test di integrazione vengono scritti per il livello di rete, il database e i servizi di sistema — notifiche, fotocamera, geolocalizzazione. Le richieste API al backend e le operazioni di archiviazione locale forniscono il ROI più elevato, poiché questi componenti sono più spesso fonte di regressioni.
Per un singolo schermo, sono sufficienti i test unitari del ViewModel e i test UI. I test di integrazione per un singolo schermo sono giustificati solo se lo schermo interagisce con più fonti di dati — ad esempio, combina risposte da due API diverse o scrive dati contemporaneamente nella rete e nel database locale.
I test di integrazione dovrebbero essere eseguiti a ogni pull request nel pipeline CI e prima dei rilasci principali. Si consiglia inoltre di eseguire l’intero set di test di integrazione di notte (nightly build) per rilevare difetti correlati a modifiche nelle dipendenze o nell’ambiente di test.
Riepilogo
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.
Leggi anche