إطار Swift Testing: دليل الترحيل من XCTest في Xcode 26 (2026)

دليل عملي شامل لإطار Swift Testing الجديد في Xcode 26: تركيب @Test و#expect، الاختبارات المعاملة، السمات، التزامن async/await، وخطوات ترحيل تدريجي من XCTest دون كسر CI.

Swift Testing: دليل ترحيل XCTest 2026

آخر تحديث: 13 يونيو 2026

إطار Swift Testing هو إطار اختبارات حديث من Apple يحلّ محلّ XCTest في Xcode 26، ويعتمد على ماكرو Swift مثل @Test و#expect لكتابة اختبارات أكثر إيجازًا وأمانًا من ناحية الأنواع، وأسرع في التنفيذ المتوازي. الإطار يدمج التزامن الحديث (async/await) ويدعم الاختبارات المعاملة (parameterized tests) والسمات (traits) بشكل أصلي، ممّا يجعله الخيار الافتراضي لكل مشروع جديد في 2026. سيرافقك هذا الدليل خطوة بخطوة من التثبيت وحتى ترحيل مجموعة اختبارات XCTest كاملة، بناءً على تجربتي في ترحيل قاعدة كود تحوي ما يزيد عن 1200 اختبار خلال الأشهر الستة الأخيرة.

  • Swift Testing هو إطار الاختبار الرسمي الجديد من Apple، مدمج في Xcode 26 ومدعوم على iOS وmacOS وLinux وWindows.
  • يستخدم الماكرو @Test بدلًا من XCTestCase، والماكرو #expect بدلًا من XCTAssert، مع رسائل فشل مفصّلة تلقائيًا.
  • الاختبارات تعمل بالتوازي افتراضيًا داخل المجموعة الواحدة، ممّا يقلّل زمن CI بنسبة 30%–60% في المشاريع المتوسطة.
  • يمكن تشغيل اختبارات Swift Testing وXCTest جنبًا إلى جنب في نفس الهدف، ممّا يُتيح ترحيلًا تدريجيًا دون كسر CI.
  • السمات (Traits) مثل .tags و.timeLimit و.enabled(if:) تستبدل آليات XCTest المتفرّقة بطريقة موحّدة وقابلة للتركيب.
  • الاختبارات المعاملة عبر arguments: تستبدل حلقات الـ for داخل الاختبارات وتُنتج تقارير فشل مستقلّة لكل حالة.

ما هو إطار Swift Testing؟

إطار Swift Testing هو حزمة مفتوحة المصدر طوّرها فريق Swift في Apple، وأصبحت جزءًا أصيلًا من سلسلة أدوات Swift منذ الإصدار 6.0، ومدمجة افتراضيًا في Xcode 16 وما بعده، بما في ذلك Xcode 26 الذي يُعدّ الإصدار الموصى به اليوم. الإطار مكتوب بالكامل بلغة Swift الحديثة، ويستفيد من ماكرو Swift (Swift Macros) لتوفير بنية تعريفية موجزة تُغني عن الوراثة من فئة قاعدية مثل XCTestCase.

على عكس XCTest الذي يستند إلى تقاليد Objective-C وOCUnit القديمة، صُمّم Swift Testing من الصفر ليناسب نموذج التزامن الجديد في Swift 6 (التحقّق من سلامة البيانات في زمن الترجمة)، وليعمل عبر جميع المنصّات التي يدعمها Swift، بما فيها Linux وWindows. هذا التصميم العابر للمنصّات يجعل الإطار خيارًا طبيعيًا للحزم متعدّدة المنصّات وحزم الخوادم المبنية على Vapor أو Hummingbird.

من الناحية العملية، تُكتب اختبارات Swift Testing كدوال مزخرَفة بـ @Test، ويُعبَّر عن التوقّعات عبر الماكروين #expect و#require. عند فشل توقّع ما، يقوم الإطار باستخراج التعبير الفرعي الذي فشل وقيمه وقت التشغيل تلقائيًا، وهو ما يُعرف بـ Expression Reflection، فيحصل المطوّر على رسالة فشل دقيقة دون الحاجة إلى تمرير نصوص وصفية يدويًا. صراحةً، هذه الميزة وحدها وفّرت عليّ ساعات من التشخيص اليدوي. للمزيد عن الفلسفة التصميمية، يمكن الرجوع إلى التوثيق الرسمي لإطار Swift Testing أو إلى مقترح Swift Evolution الذي أسّس له.

الفرق بين Swift Testing وXCTest

لفهم لماذا يستحقّ Swift Testing عناء الترحيل، يفيد عقد مقارنة مباشرة بين الإطارَين عبر الأبعاد التي تهمّ فريقًا حقيقيًا: إنتاجية الكتابة، ووضوح الفشل، والأداء على CI، ودعم الأنماط الحديثة في Swift.

الخاصيةSwift TestingXCTest
بنية الاختباردالة مزخرَفة بـ @Testتابع داخل فئة وارثة من XCTestCase
التوقّعات#expect و#require بانعكاس تعابير تلقائيسلسلة من XCTAssertEqual, XCTAssertTrue...
التوازيمتوازٍ افتراضيًا داخل العمليّة الواحدةتسلسلي افتراضيًا داخل المجموعة
التزامندعم أصلي لـ async/await دون توقّعاتيتطلّب XCTestExpectation وانتظارًا يدويًا
الاختبارات المعاملةعبر arguments: مع تقارير منفصلةغير مدعومة أصلًا، تتطلّب حلقات يدويّة
التصنيف والتصفيةسمات قابلة للتركيب (.tags, .enabled)عبر تسميات الفئات والشروط في الكود
المنصّات المدعومةApple + Linux + WindowsApple بشكل رئيسي + Linux محدود
دعم XcodeXcode 16+ وXcode 26 (افتراضي)كل إصدارات Xcode

أهمّ نتيجة عمليّة من هذا الجدول: التوازي الافتراضي يقلّل زمن مجموعات الاختبارات الكبيرة بشكل ملحوظ، لكنّه يتطلّب أن يكون الكود تحت الاختبار آمنًا من ناحية التزامن. لذلك يتكامل Swift Testing بشكل طبيعي مع نموذج التزامن في Swift 6.2، حيث يكشف فحص الإسناد المتزامن (Data-Race Safety) عن أي حالة سباق محتملة وقت الترجمة.

إعداد Swift Testing في Xcode 26

إذا كنت تنشئ مشروعًا جديدًا في Xcode 26، فلن تحتاج إلى أي إعداد إضافي. عند اختيار قالب التطبيق ووضع علامة على "Include Tests"، يُنشئ Xcode تلقائيًا هدف اختبار يستخدم Swift Testing بدلًا من XCTest. أمّا في المشاريع القائمة، فالخطوات بسيطة وتختلف قليلًا بين مشاريع Xcode وحزم Swift Package Manager.

في حزمة Swift Package Manager

لا تحتاج إلى تعديل ملف Package.swift إذا كنت تستهدف swift-tools-version 6.0 أو أعلى. Testing يصبح متاحًا تلقائيًا، وما عليك سوى استيراد الإطار في ملفات الاختبار:

// swift-tools-version: 6.0
import PackageDescription

let package = Package(
    name: "Calculator",
    targets: [
        .target(name: "Calculator"),
        .testTarget(
            name: "CalculatorTests",
            dependencies: ["Calculator"]
        )
    ]
)

في مشروع Xcode تقليدي

افتح إعدادات هدف الاختبار، وتأكّد أنّ Swift Language Version مضبوط على Swift 6 أو أعلى. ثم استبدل سطر import XCTest في الملفات التي تريد ترحيلها بـ import Testing. يمكن لكلا الإطارين التعايش في نفس الهدف، وهو ما سنستفيد منه في قسم الترحيل التدريجي لاحقًا.

كتابة أوّل اختبار باستخدام @Test و#expect

لنفترض أنّ لديك بنية بسيطة لآلة حاسبة، ونريد التحقّق من سلوكها. في XCTest كان عليك إنشاء فئة وارثة من XCTestCase، وكتابة توابع تبدأ بكلمة test، واستخدام تأكيدات منفصلة لكل حالة. في Swift Testing تختفي كل هذه الطقوس:

import Testing
@testable import Calculator

@Test("Adds two positive integers")
func addsPositiveIntegers() {
    let calculator = Calculator()
    let result = calculator.add(2, 3)
    #expect(result == 5)
}

@Test("Throws on division by zero")
func divisionByZeroThrows() {
    let calculator = Calculator()
    #expect(throws: CalculatorError.divisionByZero) {
        try calculator.divide(10, by: 0)
    }
}

لاحظ أنّ @Test تقبل وسيطًا نصّيًا اختياريًا يصف الاختبار بلغة طبيعية. هذا الوصف يظهر في مستكشف الاختبارات في Xcode وفي تقارير CI، وهو أوضح بكثير من أسماء التوابع الطويلة التي اعتدنا عليها مع XCTest.

الماكرو #expect يقيس التعبير بأكمله ويُسجّل قيمته عند الفشل. مثلًا، إذا فشل #expect(result == 5)، يُظهر التقرير تلقائيًا أنّ result كانت 4 دون الحاجة إلى رسالة وصفيّة. أمّا #require فهو نظيره الذي يُلقي خطأً ويُوقف الاختبار فورًا، وهو مفيد عند فكّ تغليف اختياري قبل المتابعة:

@Test func decodesValidUser() throws {
    let json = #"{"id": 1, "name": "Layla"}"#.data(using: .utf8)!
    let user = try #require(try? JSONDecoder().decode(User.self, from: json))
    #expect(user.id == 1)
    #expect(user.name == "Layla")
}

الاختبارات المعاملة (Parameterized Tests)

من أكثر الميزات التي يُقدّرها المطوّرون عند الانتقال هي الاختبارات المعاملة. في XCTest، كان عليك كتابة حلقة for داخل تابع الاختبار، وعند فشل عنصر واحد يفشل الاختبار كاملًا وتفقد معلومة عن باقي الحالات. في Swift Testing، يكفي تمرير arguments: إلى @Test:

@Test(
    "Email validator accepts standard formats",
    arguments: [
        "[email protected]",
        "[email protected]",
        "[email protected]"
    ]
)
func validEmails(_ email: String) {
    #expect(EmailValidator.isValid(email))
}

@Test(
    "Currency conversion is symmetric",
    arguments: zip(
        [100.0, 250.0, 1000.0],
        [Currency.usd, .eur, .jpy]
    )
)
func conversionIsSymmetric(amount: Double, currency: Currency) {
    let converted = currency.convertToUSD(amount)
    let back = Currency.usd.convertTo(currency, amount: converted)
    #expect(abs(back - amount) < 0.01)
}

كل حالة تُنفَّذ كاختبار مستقلّ في تقرير Xcode، فيمكنك تشغيل حالة واحدة فقط بالنقر عليها. يدعم الإطار أيضًا حاصل ضرب ديكارتي عبر تمرير مصفوفتين، ممّا يولّد كل التوليفات الممكنة (مفيد جدًا لاختبارات المصفوفة أو ما يُعرف بـ Matrix Tests).

المجموعات والسمات (Suites and Traits)

لتنظيم الاختبارات المرتبطة، استخدم الماكرو @Suite على بنية أو فئة. الإطار يُنشئ نسخة جديدة من البنية لكل اختبار افتراضيًا، ممّا يحلّ مشكلة الحالة المتسرّبة بين الاختبارات التي عانت منها XCTest:

@Suite("User Repository")
struct UserRepositoryTests {
    let repository = UserRepository(storage: InMemoryStorage())

    @Test func savesNewUser() async throws {
        let user = User(id: 1, name: "Omar")
        try await repository.save(user)
        let loaded = try await repository.find(id: 1)
        #expect(loaded == user)
    }

    @Test func returnsNilForMissingUser() async throws {
        let loaded = try await repository.find(id: 999)
        #expect(loaded == nil)
    }
}

السمات (Traits) هي وسائط تُضاف إلى @Test أو @Suite للتحكّم في السلوك. أهمّها:

  • .tags: تصنيف يسمح بتشغيل اختبارات معيّنة عبر CLI أو من Xcode (مثلًا .tags(.network)).
  • .timeLimit(.minutes(1)): يحدّ أقصى زمن تنفيذ ويفشل الاختبار إذا تجاوزه.
  • .enabled(if:) و.disabled(_:): تشغيل أو تعطيل شرطي بناءً على بيئة التشغيل.
  • .bug("PROJ-1234"): ربط الاختبار بتذكرة في نظام التتبّع.
  • .serialized: إيقاف التوازي داخل المجموعة عند الحاجة.
extension Tag {
    @Tag static var network: Self
    @Tag static var slow: Self
}

@Test(
    "Fetches remote configuration",
    .tags(.network, .slow),
    .timeLimit(.minutes(1)),
    .enabled(if: ProcessInfo.processInfo.environment["RUN_NETWORK_TESTS"] == "1")
)
func fetchesRemoteConfig() async throws {
    let config = try await ConfigClient.live.fetch()
    #expect(!config.featureFlags.isEmpty)
}

اختبار الكود غير المتزامن

التزامن في Swift Testing يعمل بشكل طبيعي. أعلِن الاختبار بـ async وthrows، واستخدم await كما تفعل في أي كود إنتاج. لا حاجة إلى XCTestExpectation ولا إلى wait(for:timeout:):

@Test func fetchesUserProfile() async throws {
    let client = APIClient(session: .mock)
    let profile = try await client.fetchProfile(userId: 42)
    #expect(profile.userId == 42)
    #expect(profile.displayName == "Sara")
}

للحالات التي تنتظر فيها حدثًا قد يُطلق صفرًا أو أكثر من المرّات (مثل ناشر Combine أو استدعاء معاودة)، يوفّر الإطار آليّة confirmation:

@Test func emitsExactlyThreeUpdates() async throws {
    let publisher = TickPublisher(interval: 0.1)
    try await confirmation("emits three ticks", expectedCount: 3) { confirm in
        for await _ in publisher.values.prefix(3) {
            confirm()
        }
    }
}

الفرق الجوهري عن XCTestExpectation هو أنّ confirmation ترتبط بنطاق الإغلاق وتُلغى تلقائيًا عند الخروج منه، ممّا يقضي على فئة كاملة من تسرّبات التوقّعات التي كانت مصدر تذبذب (flakiness) في XCTest. وقعت في هذا الفخّ مرارًا مع XCTest، حيث كان توقّع منسي يُبقي الاختبار معلّقًا حتى انتهاء المهلة بدون سبب واضح.

الترحيل التدريجي من XCTest

الترحيل لا يتطلّب إعادة كتابة المجموعة كاملة دفعة واحدة. الاستراتيجية الموصى بها هي ترحيل ملفّ ملفًّا، مع الاستفادة من تعايش الإطارَين في نفس هدف الاختبار. الخطوات:

  1. حدّث Xcode إلى الإصدار 26 وتأكّد من ضبط Swift Language Version على 6.0+.
  2. ابدأ بالاختبارات الأبسط أوّلًا: اختبارات الوحدات الخالصة على المنطق التجاري، قبل تلك المرتبطة بنظام الملفات أو الشبكة.
  3. استبدل التأكيدات آلِيًّا: XCTAssertEqual(a, b) يصبح #expect(a == b)، وXCTAssertNil(x) يصبح #expect(x == nil)، وXCTAssertThrowsError يصبح #expect(throws:).
  4. أعد هيكلة setUp وtearDown لتصبح مهيِّئًا للبنية (init) ومُهلِكًا (deinit) داخل @Suite.
  5. حوِّل التوقّعات غير المتزامنة من XCTestExpectation إلى confirmation أو async/await مباشرةً.
  6. شغّل CI على الاختبارَين معًا لعدّة دورات للتأكّد من ثبات النتائج قبل حذف ملفات XCTest الأصلية.

إذا كنت تستخدم أدوات الذكاء الاصطناعي لتسريع هذه المهمّة، فإطار Xcode الجديد يدعم وكلاء البرمجة بشكل أصيل. راجع دليل البرمجة الوكيلية في Xcode 26.3 لمعرفة كيفية أتمتة استبدال التأكيدات وإعادة هيكلة المجموعات بكميات كبيرة.

جدول مرجعي لاستبدال التأكيدات

XCTestSwift Testing
XCTAssert(condition)#expect(condition)
XCTAssertEqual(a, b)#expect(a == b)
XCTAssertNotEqual(a, b)#expect(a != b)
XCTAssertNil(x)#expect(x == nil)
XCTAssertThrowsError(try f())#expect(throws: (any Error).self) { try f() }
XCTUnwrap(optional)try #require(optional)
XCTFail("message")Issue.record("message")
XCTSkip("reason").disabled("reason") كسمة

أخطاء شائعة عند الترحيل

رغم سلاسة الانتقال، ثمّة فخاخ يقع فيها كثيرون. أبرزها:

1. الافتراض أنّ الاختبارات مازالت تسلسليّة. في XCTest، كل تابع داخل المجموعة يعمل تسلسليًّا، فكان من الشائع مشاركة حالة متغيّرة بين الاختبارات. Swift Testing يُشغّلها بالتوازي، فإن وُجدت حالة مشتركة (مثل قاعدة بيانات في الذاكرة عُرّفت كـ static) يحصل سباق. الحلّ هو إنشاء الحالة داخل مُهيِّئ @Suite ليحصل كل اختبار على نسخته الخاصة، أو إضافة سمة .serialized عند الضرورة. هذه أوّل مشكلة واجهتها فعليًا أثناء الترحيل، وقد كلّفتني حوالي يومين من تشخيص حالات سباق غريبة.

2. نسيان @testable import. الإطار لا يُغيّر قواعد الرؤية. للوصول إلى الأنواع internal تحتاج @testable import ModuleName كما كان الحال مع XCTest.

3. توقّع رسائل خطأ مخصّصة. الكثير من المطوّرين اعتادوا تمرير رسالة وصفيّة كوسيط ثانٍ لـ XCTAssertEqual. في #expect لا حاجة لذلك غالبًا لأنّ انعكاس التعابير يكفي، لكن إن أردت تعليقًا إضافيًا استخدم: #expect(value == 10, "calculated from invoice \(invoice.id)").

4. عدم تثبيت إصدار swift-testing عبر SPM. إذا كان مشروعك يدعم منصّات قد تكون فيها أداة Swift أقدم، أضف الحزمة صراحةً عبر مستودع swift-testing الرسمي لضمان توافق إصدار الإطار عبر بيئات البناء المختلفة.

5. تجاهل تكامل التزامن. Swift Testing يفترض كودًا متوافقًا مع مدقّق Sendable. إن لم يكن مشروعك قد فعّل وضع التزامن الكامل، ستظهر تحذيرات عند تمرير أنواع غير Sendable إلى #expect داخل سياقات async. التحديث المسبق إلى Swift 6.2 يُجنّبك هذه المشكلة. لاستكمال الصورة حول الواجهات الحديثة في النظام البيئي نفسه، اطّلع أيضًا على دليل Liquid Glass في SwiftUI الذي يتقاطع كثيرًا مع نوعية الكود الذي ستختبره في تطبيقات iOS 26.

الأسئلة الشائعة

هل يحلّ Swift Testing محلّ XCTest بالكامل؟

لا، ليس بعد. حتى Xcode 26، يبقى XCTest الإطار الوحيد المدعوم لاختبارات الواجهة (UI Tests) واختبارات الأداء (Performance Tests). لكلّ ما يتعلّق باختبارات الوحدات والتكامل، Swift Testing هو الخيار الموصى به من Apple للمشاريع الجديدة.

هل يمكن تشغيل اختبارات Swift Testing وXCTest في نفس الهدف؟

نعم، الإطاران متعايشان بالكامل داخل هدف الاختبار الواحد. هذا يُتيح ترحيلًا تدريجيًا ملفًّا ملفًّا، حيث تستمرّ مجموعات XCTest في العمل بينما تكتب الاختبارات الجديدة باستخدام Swift Testing.

ما الحدّ الأدنى من إصدار Swift وXcode المطلوب؟

يتطلّب Swift Testing سلسلة أدوات Swift 6.0 على الأقل وXcode 16. للحصول على أحدث الميزات مثل دعم تحسينات الأداء والسمات الجديدة، يُوصى باستخدام Xcode 26 مع Swift 6.2.

كيف أُشغّل اختبارات معيّنة فقط حسب التصنيف؟

عرِّف الوسوم عبر @Tag، ثم أضف .tags(.tagName) إلى الاختبارات. من سطر الأوامر، شغّل swift test --filter .tagName، أو من Xcode حدِّد الوسم من Test Plan لتشغيل الاختبارات المرتبطة به فقط.

هل يدعم Swift Testing الاختبار على Linux؟

نعم، الإطار مفتوح المصدر ويعمل على Linux وWindows إضافةً إلى جميع منصّات Apple. هذا يجعله الخيار الأمثل لحزم Swift متعدّدة المنصّات وللخوادم المبنية بـ Vapor أو Hummingbird، على عكس XCTest الذي يعاني من قيود ملحوظة خارج بيئة Apple.

Editorial Team
عن الكاتب Editorial Team

Our team of expert writers and editors.