Wie man private Methoden in Swift testet

Laden Sie Ihr kostenloses Exemplar von
The Missing Manual
für Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Entwickler, die die Swift-Entwicklung erlernen

Laden Sie Ihr kostenloses Exemplar herunter

Das Unit-Testing eines Swift-Projekts unterscheidet sich deutlich von dem eines in Objective-C geschriebenen Projekts. Für diejenigen, die an die Flexibilität der Objective-C-Laufzeitumgebung gewöhnt sind, kann es sich so anfühlen, als wären ihnen die Hände auf dem Rücken gebunden.

Zugriffskontrolle

Während die Zugriffskontrolle eine sehr willkommene Ergänzung mit vielen Vorteilen ist, kann sie Unit-Tests verkomplizieren, besonders wenn Sie neu im Unit-Testing sind. Sie wissen wahrscheinlich, dass Sie das testable-Attribut auf eine Import-Anweisung in einem Testziel anwenden können, um Zugriff auf Entitäten zu erhalten, die als intern deklariert sind.

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

Dies ist zwar eine praktische Ergänzung, gibt Ihnen aber keinen Zugriff auf private Entitäten in einem Testziel. Das bringt uns zu der Frage des Tages. Wie testet man private Entitäten?

Falsche Frage

Die kurze Antwort auf diese Frage ist einfach. Sie können nicht auf private Entitäten von einem anderen Modul aus zugreifen und das gilt auch für Testziele. Schlicht und einfach. Dafür ist die Zugriffskontrolle da.

Aber das ist nicht die Antwort auf die Frage. Wenn Sie fragen, wie man private Entitäten testet, dann stellen Sie die falsche Frage. Aber warum ist das so?

Das geht Sie nichts an

Warum deklarieren Sie eine Entität als privat? Was ist Ihre Motivation, dies zu tun? Schauen Sie sich das folgende Beispiel an.

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

Ich möchte die AccountViewViewModel-Struktur einem Unit-Test unterziehen. Wie Sie sehen können, stellt die Struktur AccountViewViewModel zwei interne berechnete Eigenschaften zur Verfügung und definiert außerdem eine private Methode. Die berechnete Eigenschaft expiresAtAsString überträgt einen Teil ihrer Arbeit auf die private Methode parse(date:). Das Testen der internen berechneten Eigenschaften ist einfach.

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

Aber wie testen wir die private Methode? Wir können vom Testziel aus nicht auf die private Methode zugreifen. Aber warum sollten wir die private Methode in der Einheit testen? Wir haben sie nicht ohne Grund als privat gekennzeichnet. Richtig? Und das bringt uns zur Antwort auf die Frage, mit der wir begonnen haben. Wir testen keine privaten Methoden.

Unit-Test der öffentlichen Schnittstelle

Indem wir die öffentliche Schnittstelle der AccountViewViewModelStruktur testen, testen wir automatisch oder implizit auch die private Schnittstelle der Struktur. Sie haben die Aufgabe, dafür zu sorgen, dass die öffentliche Schnittstelle gründlich getestet wird. Das bedeutet, dass Sie sicherstellen müssen, dass jeder Codepfad der AccountViewViewModel-Struktur durch Unit-Tests abgedeckt ist. Mit anderen Worten, die Suite von Unit-Tests sollte eine vollständige Codeabdeckung ergeben. Das schließt öffentliche, interne und private Entitäten ein.

Wenn wir die Codeabdeckung in Xcode aktivieren und die Unit-Tests der AccountViewViewModel struct ausführen, können wir sehen, dass einige Codepfade nicht ausgeführt werden.

Codeabdeckung in Xcode

Das sagt uns, dass die Unit-Tests unvollständig sind. Wir können den Codepfad für den fatalen Fehler ignorieren. Ich führe nie Unit-Tests für Codepfade durch, die zu einem schwerwiegenden Fehler führen, aber das hängt weitgehend davon ab, wie Sie schwerwiegende Fehler in Ihren Projekten verwenden.

Wir können die Codeabdeckung für die AccountViewViewModel-Struktur erhöhen, indem wir einen weiteren Unit-Test hinzufügen.

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

Codeabdeckung in Xcode

Implementierung und Spezifikation

Es ist wichtig zu verstehen, dass wir die Spezifikation der AccountViewViewModel-Struktur testen. Wir testen nicht seine Implementierung. Das mag zwar ähnlich klingen, ist aber tatsächlich sehr unterschiedlich. Wir testen die Funktionalität der AccountViewViewModel-Struktur. Wir sind nicht daran interessiert, wie es seine Magie unter der Haube ausübt.

Die wichtigste Erkenntnis aus diesem Artikel ist, dass private Entitäten nicht unitgetestet werden müssen. Unit-Tests sind eine Form von Black-Box-Tests. Das bedeutet, dass wir nicht die Implementierung der AccountViewViewModel-Struktur testen, sondern ihre Spezifikation.

Das bedeutet jedoch nicht, dass wir nicht an der Implementierung interessiert sind. Wir müssen sicherstellen, dass die Unit-Testsuite jeden Codepfad der zu testenden Entität abdeckt. Code Coverage Reports sind von unschätzbarem Wert, um dies zu erreichen.

Laden Sie Ihr kostenloses Exemplar von
The Missing Manual
für Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.