Hur man enhetstestar privata metoder i Swift

Ladda ner ditt gratisexemplar av
The Missing Manual
for Swift Development

Guiden jag önskar att jag hade haft när jag började

Delta med i 20,000+ Utvecklare som lär sig om Swift-utveckling

Ladda ner ditt gratisexemplar

Enhetstestning av ett Swift-projekt skiljer sig ganska mycket från enhetstestning av ett projekt skrivet i Objective-C. För dem som är vana vid flexibiliteten hos Objective-C-körtiden kan det kännas som om händerna är bundna bakom ryggen.

Access Control

Men även om accesskontroll är ett mycket välkommet tillägg med många fördelar kan det komplicera enhetstestning, särskilt om du är nybörjare inom enhetstestning. Du vet förmodligen att du kan tillämpa attributet testable på ett importmeddelande i ett testmål för att få tillgång till enheter som är deklarerade som interna.

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

Det här är ett bekvämt tillägg, men det ger dig inte tillgång till privata enheter i ett testmål. Detta för oss till dagens fråga. Hur gör man enhetstest av privata entiteter?

Fel fråga

Det korta svaret på denna fråga är enkelt. Du kan inte få tillgång till privata entiteter från en annan modul och detta gäller även för testmål. Det är enkelt och tydligt. Det är vad åtkomstkontroll är till för.

Men det är inte svaret på frågan. Om du frågar hur man enhetstestar privata enheter ställer du fel fråga. Men varför är det så?

None of Your Business

Varför deklarerar man en enhet som privat? Vad är din motivering för att göra det? Ta en titt på följande exempel:

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") } }}

Jag skulle vilja enhetstesta strukturen AccountViewViewModel. Som du kan se exponerar strukturen AccountViewViewModel två interna beräknade egenskaper och den definierar också en privat metod. Den beräknade egenskapen expiresAtAsString avlastar en del av sitt arbete till den privata metoden parse(date:). Det är enkelt att testa de interna beräknade egenskaperna.

// 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")}

Men hur testar vi den privata metoden? Vi kan inte komma åt den privata metoden från testmålet. Men varför ska vi enhetstesta den privata metoden? Vi markerade den som privat av en anledning. Eller hur? Och det för oss till svaret på den fråga vi började med. Vi testar inte privata metoder.

Enhetstestning av det offentliga gränssnittet

Vid enhetstestning av det offentliga gränssnittet för AccountViewViewModel struct testar vi automatiskt eller implicit enhetstestning av det privata gränssnittet för struct. Du har till uppgift att se till att det offentliga gränssnittet testas grundligt. Detta innebär att du måste se till att varje kodväg i AccountViewViewModel struct täcks av enhetstester. Med andra ord ska sviten av enhetstester resultera i fullständig kodtäckning. Det inkluderar offentliga, interna och privata enheter.

Om vi aktiverar kodtäckning i Xcode och kör enhetstesterna för AccountViewViewModel struct kan vi se att vissa kodvägar inte utförs.

Kodtäckning i Xcode

Detta talar om att enhetstesterna är ofullständiga. Vi kan ignorera kodvägen för det ödesdigra felet. Jag enhetstestar aldrig kodvägar som resulterar i ett dödligt fel, men det beror till stor del på hur du använder dödliga fel i dina projekt.

Vi kan öka kodtäckningen för AccountViewViewModel struct genom att lägga till ytterligare ett enhetstest.

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

Kodtäckning i Xcode

Implementering och specifikation

Det är viktigt att förstå att vi testar specifikationen av AccountViewViewModel struct. Vi testar inte dess implementering. Även om detta kan låta likadant är det faktiskt väldigt olika. Vi testar funktionaliteten hos AccountViewViewModel struct. Vi är inte intresserade av hur den gör sin magi under huven.

Den viktigaste behållningen av den här artikeln är att privata enheter inte behöver testas med enhetstest. Enhetstestning är en form av black-box-testning. Det betyder att vi inte testar implementationen av AccountViewViewModel struct, vi testar dess specifikation.

Detta betyder dock inte att vi inte är intresserade av implementationen. Vi måste se till att sviten av enhetstester täcker varje kodväg för den enhet vi testar. Kodtäckningsrapporter är ovärderliga för att uppnå detta.

Ladda ner ditt gratisexemplar av
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Häng med i 20 000+ utvecklare som lär sig om Swift-utveckling

Ladda ner ditt gratisexemplar

.

Lämna ett svar

Din e-postadress kommer inte publiceras.