How to Unit Test Private Methods in Swift

Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy

Testowanie jednostkowe projektu Swift jest zupełnie inne niż testowanie jednostkowe projektu napisanego w Objective-C. Dla tych, którzy są przyzwyczajeni do elastyczności runtime’u Objective-C, może się to czuć tak, jakby twoje ręce były związane za plecami.

Kontrola dostępu

Choć kontrola dostępu jest bardzo mile widzianym dodatkiem z wieloma korzyściami, może skomplikować testowanie jednostkowe, zwłaszcza jeśli jesteś nowy w testowaniu jednostkowym. Prawdopodobnie wiesz, że możesz zastosować atrybut testable do instrukcji importu w celu testowym, aby uzyskać dostęp do encji, które są zadeklarowane jako wewnętrzne.

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

Choć jest to wygodny dodatek, nie daje ci dostępu do prywatnych encji w celu testowym. To prowadzi nas do pytania dnia. Jak testować jednostkowo prywatne encje?

Złe pytanie

Krótka odpowiedź na to pytanie jest prosta. Nie możesz uzyskać dostępu do prywatnych encji z innego modułu i dotyczy to również celów testowych. Zwykłe i proste. To jest to, do czego służy kontrola dostępu.

Ale to nie jest odpowiedź na pytanie. Jeśli pytasz, jak testować jednostkowo podmioty prywatne, to zadajesz złe pytanie. Ale dlaczego tak jest?

None of Your Business

Dlaczego deklarujesz encję jako prywatną? Jaka jest twoja motywacja, aby to zrobić? Przyjrzyj się następującemu przykładowi.

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

Chciałbym przetestować jednostkowo strukturę AccountViewViewModel. Jak widać, struktura AccountViewViewModel eksponuje dwie wewnętrzne właściwości obliczeniowe, a także definiuje prywatną metodę. Obliczeniowa właściwość expiresAtAsString odciąża część swojej pracy do prywatnej metody parse(date:). Testowanie wewnętrznych właściwości obliczeniowych jest proste.

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

Ale jak przetestować metodę prywatną? Nie mamy dostępu do prywatnej metody z celu testowego. Ale dlaczego mielibyśmy testować jednostkowo metodę prywatną? Oznaczyliśmy ją jako prywatną z jakiegoś powodu. Prawda? I to prowadzi nas do odpowiedzi na pytanie, od którego zaczęliśmy. Nie testujemy metod prywatnych.

Testowanie jednostkowe interfejsu publicznego

Testując jednostkowo interfejs publiczny struktury AccountViewViewModel automatycznie lub niejawnie testujemy jednostkowo interfejs prywatny struktury. Twoim zadaniem jest upewnienie się, że interfejs publiczny jest dokładnie przetestowany. Oznacza to, że musisz się upewnić, że każda ścieżka kodu AccountViewViewModel struct jest objęta testami jednostkowymi. Innymi słowy, zestaw testów jednostkowych powinien skutkować kompletnym pokryciem kodu. Obejmuje to publiczne, wewnętrzne i prywatne encje.

Jeśli włączymy pokrycie kodu w Xcode i uruchomimy testy jednostkowe struktury AccountViewViewModel, możemy zobaczyć, że niektóre ścieżki kodu nie są wykonywane.

Code Coverage in Xcode

To mówi nam, że testy jednostkowe są niekompletne. Możemy zignorować ścieżkę kodu dla błędu śmiertelnego. Nigdy nie testuję jednostkowo ścieżek kodu, które powodują błąd krytyczny, ale to w dużej mierze zależy od tego, jak używasz błędów krytycznych w swoich projektach.

Możemy zwiększyć pokrycie kodu dla AccountViewViewModel struct poprzez dodanie jeszcze jednego testu jednostkowego.

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

Pokrycie kodu w Xcode

Implementacja i specyfikacja

Ważne jest, aby zrozumieć, że testujemy specyfikację struktury AccountViewViewModel. Nie testujemy jego implementacji. Chociaż może to brzmieć podobnie, w rzeczywistości jest to bardzo różne. Testujemy funkcjonalność struktury AccountViewViewModel. Nie jesteśmy zainteresowani tym, w jaki sposób wykonuje on swoją magię pod maską.

Kluczowym wnioskiem z tego artykułu jest to, że prywatne encje nie muszą być testowane jednostkowo. Testowanie jednostkowe jest formą testowania black-box. Oznacza to, że nie testujemy implementacji struktury AccountViewViewModel, testujemy jej specyfikację.

Nie oznacza to jednak, że nie jesteśmy zainteresowani implementacją. Musimy się upewnić, że zestaw testów jednostkowych obejmuje każdą ścieżkę kodu testowanej przez nas jednostki. Raporty pokrycia kodu są nieocenione, aby to osiągnąć.

Pobierz swoją darmową kopię
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy

.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.