Come testare unitariamente i metodi privati in Swift

Scarica la tua copia gratuita di
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Sviluppatori che imparano lo sviluppo Swift

Scarica la tua copia gratuita

Il test unitario di un progetto Swift è molto diverso dal test unitario di un progetto scritto in Objective-C. Per coloro che sono abituati alla flessibilità del runtime Objective-C, può sembrare che le mani siano legate dietro la schiena.

Controllo dell’accesso

Mentre il controllo dell’accesso è un’aggiunta molto gradita con molti benefici, può complicare i test unitari, specialmente se sei nuovo ai test unitari. Probabilmente sapete che potete applicare l’attributo testable ad una dichiarazione di importazione in un obiettivo di test per ottenere l’accesso alle entità che sono dichiarate come interne.

import XCTest@testable import Notesclass NotesTests: XCTestCase { ...}

Mentre questa è una comoda aggiunta, non vi dà accesso alle entità private in un obiettivo di test. Questo ci porta alla domanda del giorno. Come fai a testare le entità private?

Domanda sbagliata

La risposta breve a questa domanda è semplice. Non puoi accedere alle entità private da un altro modulo e questo vale anche per gli obiettivi di test. Chiaro e semplice. A questo serve il controllo degli accessi.

Ma questa non è la risposta alla domanda. Se chiedete come testare unitariamente le entità private, allora state facendo la domanda sbagliata. Ma perché?

Non sono affari tuoi

Perché dichiari un’entità privata? Qual è la vostra motivazione per farlo? Dai un’occhiata al seguente esempio.

import Foundationstruct AccountViewViewModel { // MARK: - Properties let account: Account // MARK: - Public Interface var subscriptionAsString: String { switch account.subscription { case .monthly: return "Monthly Subscription" case .yearly: return "Yearly Subscription" case .trial: return "Trial Subscription" } } var expiresAtAsString: String { // Parse Date let date = parse(date: account.expiresAt) // Initialize Date Formatter let dateFormatter = DateFormatter() // Configure Date Formatter dateFormatter.dateFormat = "YYYY, MMMM" // Convert Date to String return dateFormatter.string(from: date) } // MARK: - Private Interface private func parse(date dateAsString: String) -> Date { let dateFormat: String if dateAsString.contains("/") { dateFormat = "YYYY'/'MM'/'dd" } else { dateFormat = "YYYYMMdd" } // Initialize Date Formatter let dateFormatter = DateFormatter() // Configure Date Formatter dateFormatter.dateFormat = dateFormat if let date = dateFormatter.date(from: dateAsString) { print(date) return date } else { fatalError("Incompatible Date Format") } }}

Vorrei testare unitariamente la struttura AccountViewViewModel. Come potete vedere, la struttura AccountViewViewModel espone due proprietà interne calcolate e definisce anche un metodo privato. La proprietà calcolata expiresAtAsString scarica parte del suo lavoro al metodo privato parse(date:). Testare le proprietà interne calcolate è semplice.

// MARK: - Tests for Subscription as Stringfunc testSubscriptionAsString_Monthly() { let account = Account(expiresAt: "20161225", subscription: .monthly) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.subscriptionAsString, "Monthly Subscription")}func testSubscriptionAsString_Yearly() { let account = Account(expiresAt: "20161225", subscription: .yearly) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.subscriptionAsString, "Yearly Subscription")}func testSubscriptionAsString_Trial() { let account = Account(expiresAt: "20161225", subscription: .trial) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.subscriptionAsString, "Trial Subscription")}// MARK: - Tests for Expires at as Stringfunc testExpiresAtAsString_20161225() { let account = Account(expiresAt: "20161225", subscription: .trial) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.expiresAtAsString, "2016, December")}

Ma come testiamo il metodo privato? Non possiamo accedere al metodo privato dall’obiettivo del test. Ma perché dovremmo testare il metodo privato? Lo abbiamo marcato come privato per un motivo. Giusto? E questo ci porta alla risposta alla domanda di partenza. Non testiamo i metodi privati.

Testiamo unitariamente l’interfaccia pubblica

Testando unitariamente l’interfaccia pubblica della AccountViewViewModel struct testiamo automaticamente o implicitamente unitariamente l’interfaccia privata della struct. Avete il compito di assicurarvi che l’interfaccia pubblica sia testata a fondo. Questo significa che devi assicurarti che ogni percorso di codice della struct AccountViewViewModel sia coperto da test unitari. In altre parole, la suite di test unitari dovrebbe risultare in una copertura completa del codice. Questo include entità pubbliche, interne e private.

Se abilitiamo la copertura del codice in Xcode ed eseguiamo i test unitari della AccountViewViewModel struct, possiamo vedere che alcuni percorsi del codice non vengono eseguiti.

Code Coverage in Xcode

Questo ci dice che i test unitari sono incompleti. Possiamo ignorare il percorso del codice per l’errore fatale. Non ho mai testato i percorsi del codice che risultano in un errore fatale, ma questo dipende in gran parte da come usate gli errori fatali nei vostri progetti.

Possiamo aumentare la copertura del codice per la struct AccountViewViewModel aggiungendo un altro test unitario.

func testExpiresAtAsString_20161225WithForwardSlashes() { let account = Account(expiresAt: "2016/12/25", subscription: .trial) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.expiresAtAsString, "2016, December")}

Copertura del codice in Xcode

Implementazione e specifica

È importante capire che stiamo testando la specifica della struct AccountViewViewModel. Non stiamo testando la sua implementazione. Anche se questo può sembrare simile, in realtà è molto diverso. Stiamo testando la funzionalità della struct AccountViewViewModel. Non siamo interessati a come fa la sua magia sotto il cofano.

Il punto chiave di questo articolo è che le entità private non hanno bisogno di essere testate unitariamente. Il test unitario è una forma di test black-box. Questo significa che non testiamo l’implementazione della struct AccountViewViewModel, testiamo la sua specifica.

Questo non significa che non siamo interessati all’implementazione, però. Dobbiamo assicurarci che la suite di test unitari copra ogni percorso del codice dell’entità che stiamo testando. I report di copertura del codice sono inestimabili per realizzare questo.

Scarica la tua copia gratuita di
Il manuale mancante
per lo sviluppo Swift

La guida che avrei voluto avere quando ho iniziato

Unisciti agli oltre 20.000 sviluppatori che imparano lo sviluppo Swift

Scarica la tua copia gratuita

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.