تركيب الميزات واختبارها
اجمع المختزلات واختبرها بصورة شاملة.
تركيب الميزات واختبارها درس مجاني في Swift Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Swift Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Swift Academy 4 دروس في المجموع.
تركيب الميزات
تُبنى التطبيقات الحقيقية من ميزات صغيرة كثيرة. يتيح TCA للميزة الأب تضمين ميزات فرعية، بحيث تبقى كل ميزة مركّزة، ثم يجمعها في بنية أكبر. والأداتان المستخدمتان لذلك هما Scope وتضمين حالة الميزات الفرعية وإجراءاتها.
التركيب هو ما منح The Composable Architecture اسمها.
import ComposableArchitecture
// A parent feature embeds child State and Action,
// then uses Scope to run the child's reducer.تضمين حالة الميزة الفرعية
تحتفظ State الخاصة بالميزة الأب بحالة الميزة الفرعية كخاصية. ويحتوي تعداد Action الخاص بالميزة الأب على حالة تغلّف إجراءات الميزة الفرعية. يعكس هذا التداخل طريقة تداخل واجهة المستخدم.
هنا تحتفظ ميزة أب بميزة فرعية لعداد.
import ComposableArchitecture
@Reducer
struct AppFeature {
@ObservableState
struct State: Equatable {
var counter = CounterFeature.State()
}
enum Action {
case counter(CounterFeature.Action)
}
var body: some ReducerOf<Self> { EmptyReducer() }
}تحديد نطاق الميزة الفرعية
تشغّل Scope الـ reducer الخاص بالميزة الفرعية على الجزء من حالة الميزة الأب وإجرائها الذي يخصّها. تمرّر إليها مسار مفتاح للحالة ومسار حالة للإجراء، ثم الـ reducer الخاص بالميزة الفرعية.
بهذا تُوصَل الميزة الفرعية بحلقة التحديث الخاصة بالميزة الأب.
import ComposableArchitecture
var body: some ReducerOf<Self> {
Scope(state: \.counter, action: \.counter) {
CounterFeature()
}
Reduce { state, action in
// parent-specific logic here
return .none
}
}دمج عدة reducers
يمكن لـ body الخاص بالميزة أن يسرد عدة reducers؛ إذ يشغّلها TCA بالترتيب مع كل إجراء. يتيح لك ذلك وضع واحد أو أكثر من Scope إلى جانب Reduce الخاص بالميزة الأب.
هنا تتعايش ميزتان فرعيتان مع منطق الميزة الأب.
import ComposableArchitecture
var body: some ReducerOf<Self> {
Scope(state: \.profile, action: \.profile) { ProfileFeature() }
Scope(state: \.settings, action: \.settings) { SettingsFeature() }
Reduce { state, action in
return .none
}
}تحديد نطاق Store لواجهات الميزات الفرعية
على جانب الواجهة، تحتاج واجهة الميزة الفرعية إلى Store مخصّص لها. تنشئ الميزة الأب هذا الـ Store باستخدام store.scope، مع الربط بحالة الميزة الفرعية وإجرائها.
هنا تمرّر واجهة الميزة الأب Store ذا نطاق محدد إلى واجهة العداد.
import ComposableArchitecture
import SwiftUI
struct AppView: View {
let store: StoreOf<AppFeature>
var body: some View {
CounterView(
store: store.scope(state: \.counter, action: \.counter)
)
}
}التعرّف على TestStore
يتيح لك TestStore التأكد بدقة من سلوك الميزة. ترسل الإجراءات وتصف كيفية تغيّر الحالة؛ وإذا اختلف السلوك الفعلي، يفشل الاختبار. ويكشف هذا التحقق الشامل التراجعات الدقيقة.
تنشئه مثل Store عادي، بتمرير الحالة الابتدائية والـ reducer.
import ComposableArchitecture
import XCTest
func testIncrement() async {
let store = await TestStore(initialState: CounterFeature.State()) {
CounterFeature()
}
}التأكد من تغيّرات الحالة
عندما ترسل إجراءً إلى TestStore، تمرّر إغلاقًا يصف التغيير المتوقع. ينجح الاختبار فقط إذا تغيّرت الحالة بالطريقة نفسها تمامًا.
هنا يجب أن تؤدي الزيادة إلى رفع العدد من 0 إلى 1.
import ComposableArchitecture
import XCTest
func testIncrement() async {
let store = await TestStore(initialState: CounterFeature.State()) {
CounterFeature()
}
await store.send(.incrementTapped) {
$0.count = 1
}
}الشمولية
يكون TestStore شاملًا افتراضيًا: يجب أن تتعامل مع كل تغيّر في الحالة وكل إجراء يعيده تأثير. تؤدي التغيّرات غير المعالَجة إلى فشل الاختبار، مما يحافظ على دقة تأكيداتك.
يمكنك تخفيف ذلك باستخدام store.exhaustivity = .off في الاختبارات المركّزة.
import ComposableArchitecture
// Exhaustive (default): every change must be asserted.
// Relax when you only care about part of the behavior:
store.exhaustivity = .offاستقبال إجراءات التأثيرات
عندما يعيد تأثير إجراءً، يجب أن يستقبله الاختبار باستخدام receive، وأن يتأكد من الحالة الناتجة. يثبت ذلك عمل التدفق غير المتزامن من البداية إلى النهاية.
هنا يؤدي إعادة التحميل إلى استجابة ينتظرها الاختبار ويتحقق منها.
import ComposableArchitecture
import XCTest
func testReload() async {
let store = await TestStore(initialState: NumberFeature.State()) {
NumberFeature()
}
await store.send(.reload)
await store.receive(\.response) {
$0.value = 42
}
}تجاوز الاعتماديات في الاختبارات
لجعل التأثيرات حتمية، تجاوز الاعتماديات عند إنشاء TestStore. قدّم عميلًا وهميًا أو ساعة اختبار حتى تكون النتائج متوقعة.
هنا يُستبدل عميل الأرقام بعميل يعيد دائمًا القيمة 42.
import ComposableArchitecture
import XCTest
func testReload() async {
let store = await TestStore(initialState: NumberFeature.State()) {
NumberFeature()
} withDependencies: {
$0.numberClient.fetch = { 42 }
}
await store.send(.reload)
await store.receive(\.response) { $0.value = 42 }
}اختبار الميزات المركّبة
بما أن التركيب يضمّن الحالات والإجراءات داخل بعضها، تختبر الميزة الأب بإرسال إجراءات الميزة الفرعية مغلّفة داخل حالة الميزة الأب. وتنطبق تأكيدات الشمول نفسها، مما يتحقق من الشجرة بأكملها.
هنا تستقبل ميزة أب إجراء زيادة العداد عبر نطاقها.
import ComposableArchitecture
import XCTest
func testChildInParent() async {
let store = await TestStore(initialState: AppFeature.State()) {
AppFeature()
}
await store.send(.counter(.incrementTapped)) {
$0.counter.count = 1
}
}مراجعة سريعة: سلوك TestStore
تذكّر ما الذي يتطلبه TestStore الافتراضي من تأكيداتك.
مراجعة: التركيب والاختبار
لقد أكملت مجموعة أدوات TCA:
- يشغّل
Scopereducer فرعيًا على جزء من حالة الميزة الأب وإجرائها، ويمكن دمج عدة reducers فيbodyواحد. - يمرّر
store.scopeإلى واجهة الميزة الفرعية Store مخصّصًا لها. - يتحقق
TestStoreمن تغيّرات الحالة بدقة عند استخدامكsendلإرسال الإجراءات. - يكون شاملًا افتراضيًا، ويستخدم
receiveللإجراءات التي تقودها التأثيرات. - تجاوز الاعتماديات باستخدام
withDependenciesلإجراء اختبارات حتمية.
يمكنك الآن بناء الميزات وتركيبها واختبارها بدقة باستخدام The Composable Architecture.
import ComposableArchitecture
// Recap: Scope to compose, TestStore to verify.
// Exhaustive assertions + injected fakes = confidence.تعلم Swift مع معلم ذكاء اصطناعي — مجانًا
اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.
- الدورات
- 122
- الدروس
- 409
الأسئلة الشائعة
هل درس «تركيب الميزات واختبارها» مجاني؟
نعم — نص درس «تركيب الميزات واختبارها» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Swift Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Swift Academy 4 دروس في المجموع.
ماذا ستتعلم في «تركيب الميزات واختبارها»؟
اجمع المختزلات واختبرها بصورة شاملة. تتمرن على Swift Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Swift Academy؟
لا تُشترط خبرة سابقة. Swift Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «تركيب الميزات واختبارها»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Swift Academy هذا؟
نعم. كل درس في Swift Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- الحالة والإجراء والمختزل
- المخزن وتكامل SwiftUI
- التأثيرات والتبعيات
- تركيب الميزات واختبارها