XCTest — چارچوب اپل برای آزمایش ماژولایی و انسگرالی برنامههای iOS، macOS، watchOS و tvOS است. XCTest بخشی از Xcode است و از نوشتن تست به Swift و Objective-C پشتیبانی میکند. به عنوان مثال، چارچوبهای خارجی (Quick، Nimble)، XCTest راهحل رسمی اپل است و به طور کامل با Xcode Server و CI/CD یکپارچه شده است. بر اساس Apple Developer (2024)، XCTest در 94% برنامههای iOS از تاپ 100 App Store استفاده میشود. XCTest پایه پایداری برای نوشتن تستهای واحد و UI-تستها بدون وابستگیهای خارجی فراهم میکند.
نکات کلیدی
XCTest — چارچوبی برای آزمایش واحد، انسگرالی و UI است که توسط اپل توسعه یافته و از نسخه 5.0 (2013) در Xcode جایگزین شده است. 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 یکسانی در دسترس است که امکان نوشتن تستهای چند سطحی را فراهم میکند. Swift Testing — چارچوب جدید Apple (اعلام شده در 2024) که در آینده XCTest را تکمیل خواهد کرد، اما آن را کاملاً جایگزین نمیکند. XCTest چارچوب اصلی برای آزمایش در اکوسیستم اپل باقی میماند.
XCTestCase — کلاس پایهای است که تمامی کلاسهای تست در XCTest از آن ارث بر میبرند. آن چرخه زندگی تست را فراهم میکند: `setUp()` قبل از هر تست و `tearDown()` بعد از هر تست فراخوانی میشود. SetUp برای مقداردهی اولیه اشیا، tearDown برای پاکسازی منابع استفاده میشود. setUpWithError و tearDownWithError امکان رسیدگی به خطاهای مقداردهی بدون تری-کتچ در هر تست را فراهم میکنند.
هر روشی که نامش با `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 Text) — قرارداد نامگذاری شیء تحت تست است. MockURLSession شبکه واقعی را جایگزین میکند و امکان تست UserService را به صورت جداگانه فراهم میکند. اصل «یک تست — یک بررسی» اشکالزدایی را آسان میکند. هر تست XCTestCase باید یک سناریو یا یک ادعا را بررسی کند.
XCTAssertTrue و XCTAssertFalse — اثباتهای پایه برای بررسی مقادیر بولین. 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:)` را فراخوان میکند. اگر انتظار در طول مدت زمان تعیین شده برآورده نشود، تست شکست میخورد. مدت زمان پیشفرض ۱۰ ثانیه است، اما برای عملیاتهای سریع توصیه میشود ۱–۳ ثانیه تنظیم شود.
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 فراخوان میشود و از اینکه عملیات ناهمگام تکمیل شده خبر میدهد. اگر طی ۳ ثانیه fulfill() فراخوان نشود، تست با timeout شکست میخورد. پس از انتظار موفقیتآمیز، اثباتها برای بررسی نتیجه اجرا میشوند. چندین انتظار را میتوان به صورت ماتریس ارسال کرد و منتظر تکمیل همه شد.
measure(metrics:) — روش XCTestCase برای ایجاد تستهای کارایی است. بلوک کد داخل measure ۱۰ بار متوالی اجرا میشود و XCTest آمار جمعآوری میکند: زمان میانگین، میانه، انحراف معیار. Metrics — ماتریس متریکهایی است که ردگیابی میشوند: XCTClockMetric (زمان)، XCTMemoryMetric (حافظه)، XCTStorageMetric (دیسک) و XCTCPUMetric (پردازنده). بر اساس Apple WWDC (2024)، تستهای کارایی با XCTCPUMetric برای کشف رگرسیون در آلگوریتمها مفید هستند.
Baseline (خط پایه) برای تستهای کارایی در Xcode Test Plan تنظیم میشود. اگر زمان اجرا از baseline به میزان تعیین شده (پیشفرض ۱۰%) بیشتر شود، تست شکست خورده دانسته میشود. Baseline پس از تایید اینکه تغییر کارایی مورد انتظار است، دستی بهروز میشود. Test Plan در Xcode امکان گروهبندی تستهای کارایی را بر اساس پیکربندیها فراهم میکند.
func testArraySortPerformance() {
let numbers = (1...10000).shuffled()
measure(metrics: [XCTClockMetric()]) {
let _ = numbers.sorted()
}
}
این تست کارایی زمان مرتبسازی ماتریسی از ۱۰۰۰۰ عنصر را اندازهگیری میکند. XCTClockMetric زمان واقعی اجرا را ثبت میکند. اگر پس از تغییر آلگوریتم مرتبسازی زمان تا ۱۰% یا بیشتر افزایش یابد، تست رگرسیون را نشان خواهد داد. تستهای کارایی XCTest به ویژه برای: آلگوریتمهای پردازش داده، رندر کاربردهای UI، عملیات پایگاه داده و درخواستهای شبکه مفید هستند.
ساختار پروژه تست در XCTest از قرارداد زیر پیروی میکند: یک فایل تست به ازای یک کلاس، در یک دیرکتوری جداگانه `<TargetName>Tests`. نام فایلها با پسوند `Tests` به نام کلاسهای در حال تست مطابقت دارند. Test Targets در Xcode برای تستهای واحد و UI-تستها به طور جداگانه پیکربندی میشوند که امکان اجرای مستقل آنها را فراهم میکند. Schemes در Xcode پیکربندی ساخت و مجموعه تستهای اجرایی را مدیریت میکند.
Xcode Cloud و GitHub Actions از اجرای XCTest از طریق `xcodebuild test -scheme App -testPlan SmokeTest` پشتیبانی میکنند. پایپلاین CI شامل: ساخت → اجرای تستهای واحد → اجرای UI-تستها → انتشار گزارش. گزارش JUnit توسط `xcodebuild` با گزینه `-resultBundlePath` تولید میشود و قابل واردات به هر ابزار CI است. Code Coverage — ویژگی ساخته شده در XCTest که نشان میدهد کدام خطوط کد توسط تستها پوشش داده شدهاند. حداقل آستانه پوشش برای کد تولیدی — ۷۰% برای منطق کسب و کار حیاتی.
Xcode Cloud و GitHub Actions از اجرای XCTest از طریق `xcodebuild test -scheme App -testPlan SmokeTest` پشتیبانی میکنند. پایپلاین CI شامل: ساخت → اجرای تستهای واحد → اجرای UI-تستها → انتشار گزارش. گزارش JUnit توسط `xcodebuild` با گزینه `-resultBundlePath` تولید میشود و قابل واردات به هر ابزار CI است. Bitrise و Jenkins مراحل آمادهای برای XCTest دارند.
Code Coverage — ویژگی ساخته شده در XCTest که نشان میدهد کدام خطوط کد توسط تستها پوشش داده شدهاند. Xcode پوشش را به صورت سبز (پوشش داده)، قرمز (پوشش نداده) و زرد (قسمتی پوشش داده) نمایش میدهد. حداقل آستانه پوشش برای کد تولیدی — ۷۰% برای منطق کسب و کار حیاتی. بر اساس Google Testing Blog (2024)، اجباری کردن 80% پوشش برای همه ماژولها به ظهور تستهای خالی منجر میشود که منطق را بررسی نمیکنند.
سوالات متداول
XCTest — چارچوب رسمی اپل با یکپارچگی مستقیم در Xcode است. Quick و Nimble — کتابخانههای شخص ثالثی هستند که از ثبات بیشتر قابل خواندنی فراهم میکنند. Quick و Nimble برای Acceptance Testing راحت، اما XCTest به دلیل عدم وابستگی خارجی برای تستهای واحد مطمئنتر است.
کد ناهمگام از طریق XCTestExpectation + `wait(for:timeout:)` یا از طریق روشهای `async/await` XCTest (iOS 13+) تست میشود. برای callback-based API انتظاری ایجاد میشود. برای async/await از اثباتهای استاندارد در توابع async استفاده میشود.
XCTest چارچوب ساخته شده برای mock ندارد. Mock کردن از طریق پروتکلها انجام میشود. برای تولید خودکار mock از Cuckoo، SwiftyMocky یا mockهای دستی استفاده میشود. Dependency Injection از طریق اینیشیالیسازیها شرط ضروری برای تست است.
بله، XCTest روی دستگاههای واقعی از طریق Xcode یا xcodebuild با پارامتر `-destination 'platform=iOS,name=iPhone 15'` اجرا میشود. UI-تستها روی دستگاههای واقعی نتایج دقیقتری نسبت به شبیهسازها دارند. برای اجرا روی مزارع دستگاهها از BrowserStack، Sauce Labs یا Firebase Test Lab استفاده میشود.
Swift Testing (2024) — چارچوب جدید Apple با ماکروهای `@Test`، `@Suite` و `@Expect`. آن پارامتریزاسیون ساخته شده تستها، گروهبندی در suite-ها و سینتاکس خواناتر را فراهم میکند. Swift Testing در کنار XCTest قرار دارد و آن را جایگزین نمیکند. XCTest چارچوب اصلی برای UI-تستها و تستهای کارایی باقی میماند.
نتایج
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید