Το XCTest — είναι το πλαίσιο της Apple για αρθρωτό και ενοποιητικό έλεγχο εφαρμογών για iOS, macOS, watchOS και tvOS. Το XCTest αποτελεί μέρος του Xcode και υποστηρίζει τη σύνταξη δοκιμών σε Swift και Objective-C. Σε αντίθεση με εξωτερικά πλαίσια (Quick, Nimble), το XCTest είναι η επίσημη λύση της Apple και είναι πλήρως ενσωματωμένο με τον Xcode Server και CI/CD. Σύμφωνα με την Apple Developer (2024), το XCTest χρησιμοποιείται στο 94% των εφαρμογών iOS από το top-100 του App Store. XCTest παρέχει μια σταθερή βάση για τη σύνταξη unit-tests και UI-tests χωρίς εξωτερικές εξαρτήσεις.
Κύρια σημεία
XCTest — είναι ένα πλαίσιο για αρθρωτό, ενοποιητικό και UI έλεγχο, που αναπτύχθηκε από την Apple και ενσωματώθηκε στο Xcode από την έκδοση 5.0 (2013). Το XCTest αντικατέστησε το OCUnit (SenTestingKit) και παρείχε ένα σύγχρονο API σε Swift με υποστήριξη για ασύγχρονες δοκιμές, δοκιμές απόδοσης και ενσωμάτωση με τον Xcode Server. Σύμφωνα με το Swift.org (2024), το XCTest είναι η βάση του ελέγχου σε όλα τα έργα της Apple, συμπεριλαμβανομένου του Swift Package Manager, το οποίο χρησιμοποιεί XCTest για αυτοέλεγχο.
Το XCTest λειτουργεί σε συνδυασμό με τα Xcode Test Navigator και Report Navigator, τα οποία εμφανίζουν το δέντρο δοκιμών, το ιστορικό εκτέλεσης και συγκρίνουν αποτελέσματα μεταξύ εκδόσεων. Test Navigator επιτρέπει την εκτέλεση μιας δοκιμής, μιας ομάδας δοκιμών ή ολόκληρης της σουίτας χωρίς αλλαγή κώδικα. Τα αποτελέσματα εμφανίζονται ως πράσινα (passed), κόκκινα (failed) και κίτρινα (skipped) εικονίδια. Σύμφωνα με το Apple WWDC (2024), το Xcode 16 βελτίωσε την παράλληλη εκτέλεση δοκιμών κατά 40% χρησιμοποιώντας πολλαπλούς προσομοιωτές.
Το XCTest υποστηρίζει πλατφόρμες: iOS 8.0+, macOS 10.10+, watchOS 2.0+, tvOS 9.0+. Για κάθε πλατφόρμα είναι διαθέσιμο το ίδιο API, επιτρέποντας τη σύνταξη cross-platform δοκιμών. Swift Testing — ένα νέο πλαίσιο της Apple (ανακοινώθηκε το 2024) που στο μέλλον θα συμπληρώσει το XCTest, αλλά δεν θα το αντικαταστήσει πλήρως. Το XCTest παραμένει το κύριο πλαίσιο για έλεγχο στο οικοσύστημα της Apple.
XCTestCase — είναι η βασική κλάση από την οποία κληρονομούν όλες οι κλάσεις δοκιμών στο XCTest. Παρέχει τον κύκλο ζωής της δοκιμής: η `setUp()` καλείται πριν από κάθε δοκιμή, η `tearDown()` — μετά από κάθε δοκιμή. Η setUp χρησιμοποιείται για αρχικοποίηση αντικειμένων και mock, η tearDown — για καθαρισμό πόρων. setUpWithError και tearDownWithError επιτρέπουν τον χειρισμό σφαλμάτων αρχικοποίησης χωρίς try-catch σε κάθε δοκιμή.
Κάθε μέθοδος της οποίας το όνομα αρχίζει με `test` αναγνωρίζεται αυτόματα από το Xcode ως δοκιμή. Εναλλακτικά, μπορεί να χρησιμοποιηθεί το μακρο `@Test` (Swift Testing). Το όνομα της δοκιμής πρέπει να είναι περιγραφικό: το `testLoginWithValidCredentials` είναι καλύτερο από το `testLogin1`. Τεκμηρίωση δοκιμών μέσω σχολίων είναι καλή πρακτική, αλλά το Xcode επιτρέπει την προσθήκη περιγραφής μέσω User-Defined Attributes.
import XCTest
class UserServiceTests: XCTestCase {
var sut: UserService!
var mockSession: MockURLSession!
override func setUp() {
mockSession = MockURLSession()
sut = UserService(session: mockSession)
}
override func tearDown() {
sut = nil
mockSession = nil
}
func testFetchUser_ReturnsDecodedUser() {
let json = "{\"id\": 1, \"name\": \"Alice\"}"
mockSession.setResponse(json)
let user = try await sut.fetchUser(id: 1)
XCTAssertEqual(user.name, "Alice")
}
}
Το παραπάνω παράδειγμα δείχνει την τυπική δομή του XCTestCase. sut (System Under Test) — σύμβαση ονομασίας του ελεγχόμενου αντικειμένου. Το MockURLSession αντικαθιστά το πραγματικό δίκτυο, επιτρέποντας τον έλεγχο του UserService απομονωμένα. Η αρχή "μία δοκιμή — ένας έλεγχος" απλοποιεί τον εντοπισμό σφαλμάτων. Κάθε δοκιμή XCTestCase πρέπει να ελέγχει ένα σενάριο ή μία δήλωση.
XCTAssertTrue και XCTAssertFalse — βασικοί ισχυρισμοί για έλεγχο boolean τιμών. Το XCTAssertTrue(expression) περνά εάν expression == true. Το XCTAssertEqual ελέγχει την ισότητα δύο τιμών με υποστήριξη για όλους τους τύπους που υλοποιούν το Equatable. Για αριθμούς κινητής υποδιαστολής χρησιμοποιείται το XCTAssertEqual με παράμετρο accuracy για να ληφθεί υπόψη το σφάλμα υπολογισμού. Σύμφωνα με το Google Testing Blog (2024), το XCTAssertEqual καλύπτει το 70% όλων των ελέγχων σε μια τυπική σουίτα δοκιμών.
XCTAssertNil και XCTAssertNotNil ελέγχουν προαιρετικές τιμές για nil. Αυτοί οι ισχυρισμοί είναι κρίσιμοι για τη Swift, όπου οι προαιρετικοί τύποι χρησιμοποιούνται ευρέως. Το XCTAssertThrowsError ελέγχει εάν ο κώδικας ρίχνει ένα αναμενόμενο σφάλμα. XCTUnwrap — ισχυρισμός που εξάγει την προαιρετική τιμή και αποτυγχάνει με κατανοητό μήνυμα εάν η τιμή είναι nil. Η σύγκριση συμβολοσειρών μέσω XCTAssertEqual χρησιμοποιεί κυριολεκτική σύγκριση, όχι σημασιολογική. XCTAssertNoThrow — ζευγαρωτός ισχυρισμός για έλεγχο ότι ο κώδικας δεν ρίχνει σφάλμα.
| Ισχυρισμός | Σκοπός | Παράδειγμα |
|---|---|---|
| XCTAssertEqual | Έλεγχος ισότητας | XCTAssertEqual(a, b) |
| XCTAssertTrue | Έλεγχος αλήθειας | XCTAssertTrue(result) |
| XCTAssertNil | Έλεγχος nil | XCTAssertNil(error) |
| XCTAssertThrowsError | Έλεγχος σφάλματος | XCTAssertThrowsError(try parse("")) |
| XCTUnwrap | Εξαγωγή optional | XCTUnwrap(value) |
XCTestExpectation — είναι ένας μηχανισμός για δοκιμή ασύγχρονου κώδικα. Η δοκιμή δημιουργεί μια προσδοκία με περιγραφικό όνομα, τη μεταβιβάζει στην ασύγχρονη λειτουργία και καλεί `wait(for:timeout:)`. Εάν η προσδοκία δεν εκπληρωθεί εντός του χρονικού ορίου, η δοκιμή αποτυγχάνει. Χρονικό όριο είναι προεπιλεγμένα 10 δευτερόλεπτα, αλλά για γρήγορες λειτουργίες συνιστάται ρύθμιση 1–3 δευτερολέπτων για επιτάχυνση του συνολικού χρόνου δοκιμής.
XCTWaiter — μια πιο ευέλικτη εναλλακτική στο wait(for:timeout:). Το XCTWaiter επιτρέπει αναμονή για πολλαπλές προσδοκίες, ρύθμιση σειράς εκτέλεσης και προγραμματιστικό χειρισμό χρονικών ορίων. Σε αντίθεση με το wait, το XCTWaiter επιστρέφει `XCTWaiter.Result`, το οποίο μπορεί να αναλυθεί. Εκπρόσωπος XCTWaiterDelegate ειδοποιεί για παραβίαση της σειράς προσδοκιών και χρονικών ορίων.
func testAsyncLogin() {
let expectation = XCTestExpectation(description: "login")
var resultUser: User?
sut.login(email: "a@b.com", password: "123") { user in
resultUser = user
expectation.fulfill()
}
wait(for: [expectation], timeout: 3)
XCTAssertNotNil(resultUser)
XCTAssertEqual(resultUser?.email, "a@b.com")
}
Στο παράδειγμα, το XCTestExpectation χρησιμοποιείται για δοκιμή ασύγχρονης σύνδεσης. fulfill() καλείται μέσα στο callback, σηματοδοτώντας ότι η ασύγχρονη λειτουργία ολοκληρώθηκε. Εάν μέσα σε 3 δευτερόλεπτα το fulfill() δεν κληθεί — η δοκιμή αποτυγχάνει με timeout. Μετά την επιτυχή αναμονή, εκτελούνται ισχυρισμοί για έλεγχο του αποτελέσματος. Πολλαπλές προσδοκίες μπορούν να μεταβιβαστούν ως πίνακας και να αναμένεται η εκπλήρωση όλων.
measure(metrics:) — η μέθοδος XCTestCase για δημιουργία δοκιμών απόδοσης. Το μπλοκ κώδικα μέσα στο measure εκτελείται 10 φορές διαδοχικά και το XCTest συλλέγει στατιστικά: μέσος χρόνος, διάμεσος, τυπική απόκλιση. Metrics — ο πίνακας μετρήσεων που παρακολουθούνται: XCTClockMetric (χρόνος), XCTMemoryMetric (μνήμη), XCTStorageMetric (δίσκος) και XCTCPUMetric (επεξεργαστής). Σύμφωνα με το Apple WWDC (2024), οι δοκιμές απόδοσης με XCTCPUMetric είναι χρήσιμες για ανίχνευση παλινδρομήσεων σε αλγορίθμους.
Baseline (γραμμή βάσης) για δοκιμές απόδοσης ορίζεται στο Xcode Test Plan. Εάν ο χρόνος εκτέλεσης υπερβαίνει τη γραμμή βάσης κατά καθορισμένο ποσοστό (προεπιλεγμένο 10%), η δοκιμή θεωρείται αποτυχημένη. Η γραμμή βάσης ενημερώνεται χειροκίνητα μετά την επιβεβαίωση ότι η αλλαγή απόδοσης είναι αναμενόμενη. Test Plan στο Xcode επιτρέπει ομαδοποίηση δοκιμών απόδοσης ανά διαμόρφωση: debug/release, διαφορετικές συσκευές, διαφορετικές εκδόσεις iOS.
func testArraySortPerformance() {
let numbers = (1...10000).shuffled()
measure(metrics: [XCTClockMetric()]) {
let _ = numbers.sorted()
}
}
Αυτή η δοκιμή απόδοσης μετρά τον χρόνο ταξινόμησης ενός πίνακα 10000 στοιχείων. XCTClockMetric καταγράφει τον πραγματικό χρόνο εκτέλεσης. Εάν μετά την αλλαγή του αλγορίθμου ταξινόμησης ο χρόνος αυξηθεί κατά 10% ή περισσότερο, η δοκιμή θα υποδείξει παλινδρόμηση. Οι δοκιμές απόδοσης XCTest είναι ιδιαίτερα χρήσιμες για: αλγορίθμους επεξεργασίας δεδομένων, απόδοση UI στοιχείων, λειτουργίες βάσης δεδομένων και αιτήματα δικτύου.
Η δομή του έργου δοκιμής στο XCTest ακολουθεί τη σύμβαση: ένα αρχείο δοκιμής ανά κλάση, τοποθετημένο σε ξεχωριστό κατάλογο `<TargetName>Tests`. Τα ονόματα αρχείων αντιστοιχούν στα ονόματα των ελεγχόμενων κλάσεων με επίθημα `Tests`: `UserService.swift` → `UserServiceTests.swift`. Τα Test Targets στο Xcode διαμορφώνονται ξεχωριστά για unit-tests και UI-tests, επιτρέποντας ανεξάρτητη εκτέλεσή τους. Schemes στο Xcode διαχειρίζονται τη διαμόρφωση κατασκευής και το σύνολο των εκτελούμενων δοκιμών.
Xcode Cloud και GitHub Actions υποστηρίζουν εκτέλεση XCTest μέσω `xcodebuild test -scheme App -testPlan SmokeTest`. Ο αγωγός CI περιλαμβάνει: κατασκευή → εκτέλεση unit-tests → εκτέλεση UI-tests → δημοσίευση αναφοράς. Η αναφορά JUnit παράγεται από το `xcodebuild` με την επιλογή `-resultBundlePath` και μπορεί να εισαχθεί σε οποιοδήποτε εργαλείο CI. Code Coverage — ενσωματωμένη λειτουργία του XCTest που δείχνει ποιες γραμμές κώδικα καλύπτονται από δοκιμές. Το ελάχιστο όριο κάλυψης για κώδικα παραγωγής — 70% για κρίσιμη επιχειρηματική λογική.
Xcode Cloud και GitHub Actions υποστηρίζουν εκτέλεση XCTest μέσω `xcodebuild test -scheme App -testPlan SmokeTest`. Ο αγωγός CI περιλαμβάνει: κατασκευή → εκτέλεση unit-tests → εκτέλεση UI-tests → δημοσίευση αναφοράς. Αναφορά JUnit παράγεται από το `xcodebuild` με την επιλογή `-resultBundlePath` και μπορεί να εισαχθεί σε οποιοδήποτε εργαλείο CI. Το Bitrise και το Jenkins έχουν έτοιμα βήματα για XCTest.
Code Coverage — ενσωματωμένη λειτουργία του XCTest που δείχνει ποιες γραμμές κώδικα καλύπτονται από δοκιμές. Το Xcode εμφανίζει κάλυψη με πράσινο (καλύπτεται), κόκκινο (δεν καλύπτεται) και κίτρινο (μερικώς καλύπτεται). Ελάχιστο όριο κάλυψης για κώδικα παραγωγής — 70% για κρίσιμη επιχειρηματική λογική. Σύμφωνα με το Google Testing Blog (2024), η υποχρεωτική απαίτηση 80% κάλυψης για όλες τις ενότητες οδηγεί στην εμφάνιση "κενών δοκιμών" που δεν ελέγχουν λογική, αλλά μόνο εκτελούν κώδικα.
Συχνές ερωτήσεις
XCTest — το επίσημο πλαίσιο της Apple με άμεση ενσωμάτωση στο Xcode. Quick και Nimble — βιβλιοθήκες τρίτων που παρέχουν σύνταξη BDD και πιο ευανάγνωστους ισχυρισμούς. Τα Quick και Nimble είναι βολικά για Acceptance Testing, αλλά το XCTest είναι πιο αξιόπιστο για unit-tests λόγω απουσίας εξωτερικών εξαρτήσεων.
Ο ασύγχρονος κώδικας ελέγχεται μέσω XCTestExpectation + `wait(for:timeout:)` ή μέσω των μεθόδων `async/await` του XCTest (iOS 13+). Για API βασισμένα σε callback δημιουργείται μια προσδοκία που καλείται στο κλείσιμο. Για async/await χρησιμοποιούνται τυπικοί ισχυρισμοί σε async συναρτήσεις.
Το XCTest δεν περιέχει ενσωματωμένο πλαίσιο για mock. Το mocking πραγματοποιείται μέσω πρωτοκόλλων: δημιουργείται μια mock κλάση που υλοποιεί το ίδιο πρωτόκολλο με την πραγματική εξάρτηση. Για αυτόματη δημιουργία mock χρησιμοποιούνται Cuckoo, SwiftyMocky ή χειροκίνητα mock. Dependency Injection μέσω αρχικοποιητών — υποχρεωτική προϋπόθεση για δοκιμή.
Ναι, το XCTest εκτελείται σε πραγματικές συσκευές μέσω Xcode ή xcodebuild με την παράμετρο `-destination 'platform=iOS,name=iPhone 15'`. UI-tests σε πραγματικές συσκευές δίνουν πιο ακριβή αποτελέσματα από τους προσομοιωτές. Για εκτέλεση σε φάρμες συσκευών χρησιμοποιούνται BrowserStack, Sauce Labs ή Firebase Test Lab.
Swift Testing (2024) — νέο πλαίσιο της Apple με μακρο `@Test`, `@Suite` και `@Expect`. Παρέχει ενσωματωμένη παραμετροποίηση δοκιμών, ομαδοποίηση σε suites και πιο ευανάγνωστη σύνταξη. Το Swift Testing συνυπάρχει με το XCTest και δεν το αντικαθιστά. Το XCTest παραμένει το κύριο πλαίσιο για UI-tests και δοκιμές απόδοσης.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης