The Missing Manual
for Swift Development
The Guide I Wish I Wish I Have When I Started Out
Join 20,000+ Developers Learning About Swift Development
Download Your Free Copy
Egy Swift projekt egységtesztelése egészen más, mint egy Objective-C-ben írt projekt egységtesztelése. Azok számára, akik az Objective-C futásidő rugalmasságához szoktak hozzá, úgy érezhetik, mintha a kezük meg lenne kötve a hátuk mögött.
A hozzáférési vezérlés
Míg a hozzáférési vezérlés egy nagyon üdvözlendő, sok előnnyel járó kiegészítés, bonyolíthatja az egységtesztelést, különösen, ha újak vagyunk az egységtesztelésben. Valószínűleg tudod, hogy a testable
attribútumot alkalmazhatod egy import utasításhoz egy tesztcélban, hogy hozzáférj a belsőnek deklarált entitásokhoz.
import XCTest@testable import Notesclass NotesTests: XCTestCase { ...}
Míg ez egy kényelmes kiegészítés, nem ad hozzáférést a tesztcélban lévő privát entitásokhoz. Ezzel elérkeztünk a nap kérdéséhez. Hogyan lehet a privát entitásokat egységtesztelni?
Téves kérdés
A kérdésre a rövid válasz egyszerű. A privát entitásokhoz nem férhetsz hozzá egy másik modulból, és ez a tesztcélokra is vonatkozik. Egyszerű és világos. Erre való a hozzáférés-szabályozás.
De nem ez a válasz a kérdésre. Ha azt kérdezed, hogyan lehet a privát entitásokat egységtesztelni, akkor rossz kérdést teszel fel. De miért is?
None of Your Business
Miért nyilvánítasz egy entitást privátnak? Mi a motivációd arra, hogy ezt tedd? Nézzük meg a következő példát:
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") } }}
A AccountViewViewModel
struktúrát szeretném egységtesztelni. Mint látható, a AccountViewViewModel
struktúra két belső számított tulajdonságot tár fel, és egy privát metódust is definiál. A expiresAtAsString
számított tulajdonság a munka egy részét a privát parse(date:)
metódusra terheli. A belső számított tulajdonságok tesztelése egyszerű.
// 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")}
De hogyan teszteljük a privát metódust? A privát metódushoz nem férhetünk hozzá a tesztcélból. De miért is kellene egységtesztelnünk a privát metódust? Nem véletlenül jelöltük privátnak. Ugye? És ezzel el is érkeztünk a válaszhoz a kérdésre, amivel kezdtük. Nem teszteljük a privát metódusokat.
A nyilvános interfész egységtesztelése
Azzal, hogy egységteszteljük a AccountViewViewModel
struktúra nyilvános interfészét, automatikusan vagy implicit módon egységteszteljük a struktúra privát interfészét. Az a feladatunk, hogy a nyilvános interfészt alaposan teszteljük. Ez azt jelenti, hogy a AccountViewViewModel
struktúra minden kódútját le kell fednie egységtesztekkel. Más szóval, az egységtesztek csomagjának teljes kódlefedettséget kell eredményeznie. Ez magában foglalja a nyilvános, a belső és a privát entitásokat.
Ha engedélyezzük a kódlefedettséget az Xcode-ban, és lefuttatjuk a AccountViewViewModel
struct egységtesztjeit, láthatjuk, hogy néhány kódútvonal nem kerül végrehajtásra.
Ez azt mutatja, hogy az egységtesztek hiányosak. A végzetes hiba miatt figyelmen kívül hagyhatjuk a kódútvonalat. Soha nem szoktam egységtesztelni a végzetes hibát eredményező kódútvonalakat, de ez nagyban függ attól, hogy Ön hogyan használja a végzetes hibákat a projektjeiben.
A AccountViewViewModel
struktúra kódlefedettségét növelhetjük egy további egységteszt hozzáadásával.
func testExpiresAtAsString_20161225WithForwardSlashes() { let account = Account(expiresAt: "2016/12/25", subscription: .trial) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.expiresAtAsString, "2016, December")}
Implementáció és specifikáció
Fontos megérteni, hogy a AccountViewViewModel
struktúra specifikációját teszteljük. Nem a megvalósítását teszteljük. Bár ez hasonlóan hangzik, valójában nagyon is különbözik. A AccountViewViewModel
struktúra funkcionalitását teszteljük. Nem érdekel bennünket, hogy hogyan varázsol a motorháztető alatt.
A cikk legfontosabb tanulsága, hogy a privát entitásokat nem kell egységtesztelni. A unit tesztelés a fekete dobozos tesztelés egy formája. Ez azt jelenti, hogy nem a AccountViewViewModel
struktúra implementációját teszteljük, hanem a specifikációját.
Ez azonban nem jelenti azt, hogy nem érdekel minket az implementáció. Biztosítanunk kell, hogy az egységtesztek csomagja lefedje a tesztelt entitás minden kódútját. A kódlefedettségi jelentések felbecsülhetetlen értéket képviselnek ennek elérésében.
The Missing Manual
for Swift Development
The Guide I Wish I Wish I Have When I Started Out
Join 20,000+ Developers Learning About Swift Development
Download Your Free Copy