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