Hoe unittests je private methoden in Swift

Download uw gratis exemplaar van
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Med aan 20,000+ ontwikkelaars die leren over Swift-ontwikkeling

Download uw gratis exemplaar

Unit testen van een Swift-project is heel anders dan unit testen van een project geschreven in Objective-C. Voor degenen die gewend zijn aan de flexibiliteit van de Objective-C runtime, kan het voelen alsof je handen achter je rug gebonden zijn.

Toegangscontrole

Terwijl toegangscontrole een zeer welkome toevoeging is met veel voordelen, kan het unit testen bemoeilijken, vooral als je nieuw bent met unit testen. U weet waarschijnlijk dat u het testable attribuut kunt toepassen op een import statement in een test target om toegang te krijgen tot entiteiten die als intern zijn gedeclareerd.

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

Hoewel dit een handige toevoeging is, geeft het u geen toegang tot private entiteiten in een test target. Dit brengt ons bij de vraag van de dag. Hoe test je private entiteiten?

Foute vraag

Het korte antwoord op deze vraag is simpel. Je kunt geen private entiteiten benaderen vanuit een andere module en dit geldt ook voor test targets. Heel eenvoudig. Dat is waar toegangscontrole voor is.

Maar dat is niet het antwoord op de vraag. Als je vraagt hoe je private entiteiten kunt testen, dan stel je de verkeerde vraag. Maar waarom is dat?

None of Your Business

Waarom verklaar je een entiteit prive? Wat is je motivatie om dat te doen? Kijk eens naar het volgende voorbeeld.

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

Ik wil de AccountViewViewModel structuur unit-testen. Zoals je kunt zien, stelt de AccountViewViewModel struct twee interne berekende eigenschappen bloot en het definieert ook een privé methode. De expiresAtAsString computed property offloads een deel van zijn werk naar de parse(date:) private method. Het testen van de interne berekende eigenschappen is eenvoudig.

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

Maar hoe testen we de privé-methode? We kunnen de private methode niet benaderen vanuit het testdoel. Maar waarom zouden we de private methode testen? We hebben het gemarkeerd als prive voor een reden. Toch? En dat brengt ons bij het antwoord op de vraag waar we mee begonnen. We testen geen private methodes.

Unit Testing the Public Interface

Door de public interface van de AccountViewViewModel struct te unit-testen, testen we automatisch of impliciet ook de private interface van de struct. Je hebt de taak om ervoor te zorgen dat de publieke interface grondig getest wordt. Dit betekent dat je ervoor moet zorgen dat elk code pad van de AccountViewViewModel struct wordt gedekt door unit tests. Met andere woorden, de suite van unit tests moet resulteren in een complete code coverage. Dat omvat publieke, interne en private entiteiten.

Als we code coverage in Xcode inschakelen en we voeren de unit tests van de AccountViewViewModel struct uit, kunnen we zien dat sommige codepaden niet worden uitgevoerd.

Code Coverage in Xcode

Dit vertelt ons dat de unit tests onvolledig zijn. We kunnen het codepad voor de fatale fout negeren. Ik test nooit codepaden die resulteren in een fatale fout, maar dat hangt grotendeels af van hoe u fatale fouten in uw projecten gebruikt.

We kunnen de codedekking voor de AccountViewViewModel struct vergroten door nog een unit test toe te voegen.

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

Code Coverage in Xcode

Implementatie en specificatie

Het is belangrijk om te begrijpen dat we de specificatie van de AccountViewViewModel struct testen. We testen niet de implementatie ervan. Hoewel dit vergelijkbaar klinkt, is het eigenlijk heel anders. We testen de functionaliteit van de AccountViewViewModel struct. We zijn niet geïnteresseerd in hoe het zijn magie onder de motorkap doet.

De belangrijkste conclusie van dit artikel is dat private entiteiten niet unit-getest hoeven te worden. Unit testen is een vorm van black-box testen. Dit betekent dat we niet de implementatie van de AccountViewViewModel struct testen, maar de specificatie.

Dit betekent echter niet dat we niet geïnteresseerd zijn in de implementatie. We moeten ervoor zorgen dat de reeks unit tests elk codepad dekt van de entiteit die we testen. Code coverage rapporten zijn van onschatbare waarde om dit te bereiken.

Download uw gratis exemplaar van
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Meded 20.000+ ontwikkelaars die leren over Swift Development

Download uw gratis exemplaar

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.