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