Sådan tester du private metoder i Swift

Download dit gratis eksemplar af
The Missing Manual
for Swift Development

The Guide I Wish I Wish I Had When I Started Out

Deltag i 20,000+ Developers Learning About Swift Development

Download Your Free Copy

Unit testing a Swift project is quite different from unit testing a project written in Objective-C. For dem, der er vant til fleksibiliteten i Objective-C-køringstiden, kan det føles som om, at dine hænder er bundet bag din ryg.

Access Control

Selv om access control er en meget velkommen tilføjelse med mange fordele, kan det komplicere unit testing, især hvis du er ny i unit testing. Du ved sikkert, at du kan anvende testable-attributten på en import-angivelse i et testmål for at få adgang til enheder, der er erklæret som interne.

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

Selv om dette er en praktisk tilføjelse, giver det dig ikke adgang til private enheder i et testmål. Dette bringer os til dagens spørgsmål. Hvordan unit-tester man private entiteter?

Fejligt spørgsmål

Det korte svar på dette spørgsmål er enkelt. Du kan ikke få adgang til private entiteter fra et andet modul, og det gælder også for testmål. Helt enkelt og simpelt. Det er det, som adgangskontrol er til for.

Men det er ikke svaret på spørgsmålet. Hvis du spørger, hvordan man unit-tester private entiteter, så stiller du det forkerte spørgsmål. Men hvorfor er det?

None of Your Business

Hvorfor erklærer man en enhed for privat? Hvad er din motivation for at gøre det? Tag et kig på følgende eksempel:

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

Jeg vil gerne enhedsteste AccountViewViewModel-strukturen. Som du kan se, eksponerer AccountViewViewModel struct’en to interne beregnede egenskaber, og den definerer også en privat metode. Den beregnede expiresAtAsString-egenskab aflaster noget af sit arbejde til den private parse(date:)-metode. Det er ligetil at teste de interne beregnede egenskaber.

// 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 hvordan tester vi den private metode? Vi kan ikke få adgang til den private metode fra testmålet. Men hvorfor skal vi enhedsteste den private metode? Vi har markeret den som privat af en grund. Ikke sandt? Og det bringer os til svaret på det spørgsmål, vi startede med. Vi tester ikke private metoder.

Enhedstest af den offentlige grænseflade

Ved enhedstest af den offentlige grænseflade for AccountViewViewModel struct tester vi automatisk eller implicit enhedstest af struktets private grænseflade. Du har til opgave at sørge for, at den offentlige grænseflade bliver grundigt testet. Det betyder, at du skal sørge for, at alle kodeveje i AccountViewViewModel struct er dækket af enhedstests. Med andre ord skal pakken af enhedstests resultere i en fuldstændig kodedækning. Det omfatter offentlige, interne og private enheder.

Hvis vi aktiverer kodedækning i Xcode og kører enhedstestene for AccountViewViewModel struct, kan vi se, at nogle kodeveje ikke udføres.

Kodedækning i Xcode

Dette fortæller os, at enhedstestene er ufuldstændige. Vi kan ignorere kodevejen for den fatale fejl. Jeg unit-tester aldrig kodestier, der resulterer i en fatal fejl, men det afhænger i høj grad af, hvordan du bruger fatal fejl i dine projekter.

Vi kan øge kodedækningen for AccountViewViewModel struct ved at tilføje endnu en unit-test.

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

Kodedækning i Xcode

Implementering og specifikation

Det er vigtigt at forstå, at vi tester specifikationen af AccountViewViewModel struct’en. Vi tester ikke dens implementering. Selv om det kan lyde ens, er det faktisk meget forskelligt. Vi tester funktionaliteten af AccountViewViewModel struct. Vi er ikke interesserede i, hvordan den udfører sin magi under kølerhjelmen.

Den vigtigste pointe i denne artikel er, at private entiteter ikke behøver at blive enhedstestet. Unit testing er en form for black-box testing. Det betyder, at vi ikke tester implementeringen af AccountViewViewModel struct’en, vi tester dens specifikation.

Det betyder dog ikke, at vi ikke er interesseret i implementeringen. Vi skal sikre os, at pakken af enhedstests dækker alle kodeveje for den enhed, vi tester. Kodedækningsrapporter er uvurderlige til at opnå dette.

Download dit gratis eksemplar af
The Missing Manual
for Swift Development

The Guide I Wish I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download dit gratis eksemplar

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.