الوحدة 4 · إطلاق تطبيق حقيقي
الاختبار وTestFlight
16 دقراءة 4 داملأ الفراغات
الدرس في لمحة
احمِ منطق Pocket Budget بـ Swift Testing باستخدام @Test و#expect و#require والاختبارات ذات المعاملات، ثم أرشِف التطبيق وأرسل نسخة إلى المختبِرين عبر TestFlight.
- 1Swift Testing في خمس دقائق
- 2#require والاختبارات ذات المعاملات
- 3ماذا تختبر أولًا
- 4إطلاق نسخة تجريبية عبر TestFlight
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
بنيت Pocket Budget على مدى أربعة عشر درسًا. تخيّل الآن أنك ستغيّر CategoryBudget الشهر القادم لدعم ترحيل الرصيد بين الأشهر. كيف تعرف أن قاعدة تجاوز الحدّ ما زالت تعمل؟ يمكنك أن تنقر في التطبيق في كل مرة، أو أن تجعل الحاسوب يتحقّق منها في جزء من الثانية، مع كل تغيير، إلى الأبد. ثم، بعد أن يعمل، تحتاج إلى أشخاص حقيقيين يستخدمونه على هواتف حقيقية قبل أن تثق به في إطلاق على App Store.
Swift Testing في خمس دقائق
Swift Testing هو إطار الاختبار من Apple، المدمج في Xcode 16 وما بعده. حين تنشئ مشروعًا، اختر Swift Testing كنظام الاختبار وسيضيف Xcode هدف اختبار (test target)؛ ولمشروع موجود، اختر File › New › Target › Unit Testing Bundle ثم اختر Swift Testing. اختبر منطق الميزانية من درس معالجة الأخطاء:
import Testing
@testable import PocketBudget
struct CategoryBudgetTests {
@Test func recordingAnExpenseIncreasesSpent() throws {
var food = CategoryBudget(limit: 300)
try food.record(title: "Groceries", amount: 64.2)
#expect(food.spent == 64.2)
#expect(food.limit - food.spent == 235.8)
}
@Test("Going over the limit throws and leaves the budget unchanged")
func overLimitThrows() throws {
var food = CategoryBudget(limit: 50)
try food.record(title: "Lunch", amount: 12)
#expect(throws: BudgetError.overLimit(by: 7)) {
try food.record(title: "Dinner", amount: 45)
}
#expect(food.spent == 12)
}
}تعطي @testable import الاختبارات صلاحية الوصول إلى الأنواع الداخلية في تطبيقك. الاختبار هو أي دالة معلّمة بـ @Test؛ والنص الاختياري اسم مقروء يظهر في متصفّح الاختبارات. وتجميع الاختبارات في struct يجعله suite (مجموعة اختبارات)، ويحصل كل اختبار على instance جديد، فلا تستطيع الاختبارات تسريب state إلى بعضها.
يأخذ #expect تعبير Swift عاديًا. وحين يفشل، يتضمّن التقرير القيم الفعلية، مثل Expectation failed: (food.spent → 12) == 57، وهذا يخبرك في الغالب بما حدث قبل أن تفتح الـ debugger. أما #expect(throws:) فلا ينجح إلا إن رمى الـ closure ذلك الخطأ بالضبط. ولهذا يتوافق BudgetError مع Equatable منذ درس معالجة الأخطاء. شغّل كل شيء بـ Cmd-U، أو انقر على المعيّن بجانب اختبار واحد.
#require والاختبارات ذات المعاملات
@Test func parsesValidAmount() throws {
let amount = try #require(parseAmount("12.50"))
#expect(amount == 12.5)
}
@Test(arguments: ["", "abc", "0", "-3"])
func rejectsInvalidAmounts(text: String) {
#expect(parseAmount(text) == nil)
}#require هو #expect مع زر إيقاف: إن فشل الشرط، ينتهي الاختبار عند ذلك السطر. وإن أعطيته optional، يفكّه، فيستخدم باقي الاختبار قيمة Decimal حقيقية. استخدمه للشروط المسبقة؛ واستخدم #expect للادّعاءات الفعلية، فيُبلغ التشغيل الواحد عن كل ادّعاء مكسور، لا عن الأول فقط.
يشغّل @Test(arguments:) الاختبار نفسه مرة لكل مُدخَل ويُبلغ عن كل حالة على حدة. أربعة مدخلات سيئة، ودالة واحدة، والفشل يخبرك بالضبط أيّ مُدخَل كسر الكود.
ماذا تختبر أولًا
ابدأ حيث تؤذي الأخطاء المستخدمين وحيث تكون الاختبارات رخيصة: المنطق الخالص بلا واجهة. في Pocket Budget هذا يعني حسابات الميزانية، وتحليل المبالغ، وتحويل العملات، وانتقالات LoadState. تعمل هذه الاختبارات في أجزاء من الثانية ولا تتذبذب نتائجها أبدًا. وللكود الذي يحتاج SwiftData، أنشئ الـ container في الذاكرة من الدرس السابق داخل الاختبار، وعلّم الاختبار بـ @MainActor حين يلمس الـ context الرئيسي. اترك فحوصات الواجهة على مستوى البكسل لوقت لاحق؛ فالـ previews تكتشف معظم مشاكل التخطيط أسرع.
إطلاق نسخة تجريبية عبر TestFlight
TestFlight هو طريقتك لإيصال نسخة إلى أجهزة iPhone الخاصة بأشخاص آخرين قبل الإطلاق. تحتاج إلى عضوية مدفوعة في Apple Developer Program وسجلّ للتطبيق في App Store Connect بمعرّف الحزمة (bundle identifier) نفسه الذي في مشروعك. ثم:
- في إعدادات General للهدف، اضبط Version (ما يراه المستخدمون، مثل 1.0) ورقم Build (1، 2، 3…).
- اختر جهاز iOS عامًا (generic) كوجهة للتشغيل، ثم Product › Archive.
- في نافذة Organizer التي تُفتح، اختر الأرشيف، وانقر Distribute App واختر خيار App Store Connect. يوقّع Xcode النسخة ويرفعها.
- انتظر حتى يعالجها App Store Connect، وهذا يستغرق دقائق عادةً، وأجب عن سؤال الامتثال لقيود التصدير المتعلّق بالتشفير.
- أضف النسخة إلى مجموعة مختبِرين. المختبِرون الداخليون، وهم حتى 100 شخص في فريقك على App Store Connect، يستطيعون تثبيتها فورًا. أما المختبِرون الخارجيون، وهم حتى 10,000 شخص تدعوهم بالبريد الإلكتروني أو برابط عام، فيحصلون عليها بعد أن تجتاز النسخة Beta App Review.
يثبّت المختبِرون تطبيق TestFlight المجاني، ويقبلون الدعوة، ويثبّتون نسختك التجريبية. ويستطيعون إرسال ملاحظات مع لقطات شاشة مباشرة من التطبيق، وستجد تقاريرهم، مع سجلّات الأعطال، في App Store Connect. وتبقى كل نسخة قابلة للاختبار 90 يومًا.
هذه هي الدورة الكاملة التي يمرّ بها مطوّر iOS المحترف كل أسبوع: صمّم البيانات بالأنواع، وابنِ الشاشات بـ SwiftUI، واجلب البيانات واحفظها بأمان تحت فحوصات الـ concurrency في Swift 6، وأثبت المنطق بالاختبارات، وضع النسخ بين أيدي المختبِرين. ومن هنا، من الخطوات التالية الجيدة إضافة رسوم بيانية للإنفاق حسب التصنيف باستخدام Swift Charts، والمزامنة مع iCloud عبر SwiftData، وتجهيز صفحة تطبيقك على App Store. لديك الأساسات اللازمة لكل ذلك.
أهم النقاط
- اختبارات Swift Testing دوال عادية معلّمة بـ
@Test؛ و#expectيفحص شرطًا ويُبلغ عن القيم الفعلية حين يفشل. #requireيوقف الاختبار مبكرًا حين يفشل شرط مسبق، ويفكّ الـ optionals أيضًا فيعمل باقي الاختبار بقيم حقيقية.#expect(throws:)يتحقّق من أن الكود يرمي خطأً محدّدًا، و@Test(arguments:)يشغّل اختبارًا واحدًا على مدخلات كثيرة.- اختبر المنطق الخالص أولًا، مثل الميزانيات والتحليل والتنسيق، لأنه سريع ومستقر ويحمل القواعد التي تهمّ المستخدمين أكثر.
- يوزّع TestFlight النسخ على ما يصل إلى 100 مختبِر داخلي دون مراجعة، وعلى ما يصل إلى 10,000 مختبِر خارجي بعد Beta App Review.