في
المقالة السابقة ، تعرفنا على هرم الاختبار وفوائد الاختبارات الآلية. لكن النظرية عادة ما تختلف عن الممارسة. اليوم نريد أن نتحدث عن تجربتنا في اختبار رمز
التطبيق الذي يستخدمه ملايين مستخدمي iOS. وأيضًا حول المسار الصعب الذي كان على فريقنا أن يذهب إليه لتحقيق كود ثابت.
الموقف هو هذا: لنفترض أن المطورين تمكنوا من إقناع أنفسهم وعملهم بالحاجة إلى تغطية قاعدة الكود باختبارات. بمرور الوقت ، أصبح المشروع أكثر من عشرة آلاف وحدة وأكثر من ألف اختبار واجهة المستخدم. تسببت قاعدة الاختبار الكبيرة هذه في العديد من المشكلات ، التي نريد حلها.
في الجزء الأول من المقالة ، سوف نتعرف على الصعوبات التي تنشأ عند العمل مع اختبارات وحدة نظيفة (غير متكاملة) ، في الجزء الثاني سننظر في اختبارات واجهة المستخدم. لمعرفة كيف نعمل على تحسين ثبات اختبارات التشغيل ، مرحبًا بك في Cat.
في عالم مثالي ، مع شفرة المصدر دون تغيير ، يجب أن تظهر اختبارات الوحدة دائمًا نفس النتيجة ، بغض النظر عن عدد مرات التشغيل وتسلسلها. ويجب ألا تمر اختبارات السقوط باستمرار عبر حاجز خادم التكامل المستمر (CI).
في الواقع ، قد يواجه المرء حقيقة أن نفس اختبار الوحدة سيظهر إما نتيجة إيجابية أو سلبية - وهذا يعني "وميض". يكمن سبب هذا السلوك في التنفيذ الضعيف لرمز الاختبار. علاوة على ذلك ، يمكن أن يجتاز اختبار CI هذا التشغيل بنجاح ، وسيبدأ لاحقًا في التراجع عن طلب السحب (PR) للأشخاص الآخرين. في موقف مماثل ، هناك رغبة في تعطيل هذا الاختبار أو لعب الروليت وتشغيل CI مرة أخرى. ومع ذلك ، فإن هذا النهج غير مثمر ، لأنه يقوض مصداقية الاختبارات ويحمل CI بعمل لا معنى له.
تم تسليط الضوء على هذه المشكلة هذا العام في مؤتمر Apple WWDC الدولي:
- تتحدث هذه الجلسة عن الاختبار الموازي وتحليل تغطية الشفرة المستهدفة الفردية مع الاختبارات وأيضًا حول ترتيب بدء الاختبارات.
- تحدثت Apple هنا عن اختبار طلبات الشبكة والاختراق واختبار الإخطارات وسرعة الاختبارات.
اختبارات الوحدة
لمكافحة الاختبارات الوامضة ، نستخدم تسلسل الإجراءات التالي:

0. نقيم رمز اختبار الجودة وفقًا للمعايير الأساسية: العزلة ، صحة موكا ، إلخ. نحن نتبع القاعدة: من خلال الاختبار الوامض ، نقوم بتغيير رمز الاختبار ، وليس رمز الاختبار.
إذا لم يساعد هذا العنصر ، فتابع على النحو التالي:
1. نصلح ونعيد إنتاج الشروط التي يقع فيها الاختبار ؛
2. العثور على سبب سقوط.
3. تغيير رمز الاختبار أو رمز الاختبار ؛
4. انتقل إلى الخطوة الأولى وتحقق مما إذا كان سبب السقوط قد تم القضاء عليه.
لعب السقوط
الخيار الأبسط والأكثر وضوحًا هو إجراء اختبار للمشكلة على نفس إصدار iOS وعلى نفس الجهاز. وكقاعدة عامة ، يكون الاختبار ناجحًا في هذه الحالة ، ويبدو الفكر: "كل شيء يعمل من أجلي محليًا ، سأقوم بإعادة تشغيل التجميع على CI". هذا في الواقع لم يتم حل المشكلة ، ويستمر الاختبار في الوقوع مع شخص آخر.
لذلك ، في خطوة التحقق التالية ، تحتاج إلى تشغيل جميع اختبارات الوحدة للتطبيق محليًا لتحديد التأثير المحتمل للاختبار على اختبار آخر. ولكن حتى بعد هذا التحقق ، قد تكون نتيجة الاختبار إيجابية ، ولكن المشكلة لا تزال غير مكتشفة.
إذا كان تسلسل الاختبار بأكمله ناجحًا ولم يتم تسجيل الانخفاض المتوقع ، فيمكنك تكرار التشغيل عدة مرات.
للقيام بذلك ، في سطر الأوامر ، تحتاج إلى تشغيل حلقة مع xcodebuild:
#! /bin/sh x=0 while [ $x -le 100 ]; do xcodebuild -configuration Debug -scheme "TargetScheme" -workspace App.wcworkspace -sdk iphonesimulator -destination "platfrom=iOS Simulator, OS=11.3, name=iPhone 7" test >> "report.txt"; x=$(( $x +1 )); done
كقاعدة عامة ، هذا يكفي لإعادة إنتاج السقوط والانتقال إلى الخطوة التالية - تحديد سبب السقوط المسجل.
أسباب السقوط والحلول الممكنة
فكر في الأسباب الرئيسية لوميض اختبارات الوحدة التي قد تواجهها في عملك ، وأدوات التعرف عليها ، والحلول الممكنة.
هناك ثلاث مجموعات رئيسية من أسباب سقوط الاختبارات:
ضعف العزلنعني بعزل حالة خاصة من التغليف ، وهي: آلية اللغة التي تسمح بتقييد وصول بعض مكونات البرنامج إلى الآخرين.
تلعب عزلة البيئة دورًا مهمًا ، نظرًا لنقاء الاختبار ، لا ينبغي أن يؤثر أي شيء على الكيانات التي تم اختبارها. يجب إيلاء اهتمام خاص للاختبارات التي تهدف إلى التحقق من الكود. يستخدمون كيانات الحالة العامة مثل المتغيرات العالمية و Keychain و Network و CoreData و Singleton و NSUserDefaults وما إلى ذلك. في هذه المناطق ينشأ أكبر عدد من الأماكن المحتملة لظهور العزلة الضعيفة. لنفترض أنه عند إنشاء بيئة اختبار ، يتم تعيين حالة عمومية ، والتي يتم استخدامها ضمنيًا في رمز اختبار آخر. في هذه الحالة ، قد يبدأ الاختبار الذي يتحقق من الكود قيد الاختبار في "الوامض" - لأنه ، وفقًا لتسلسل الاختبارات ، قد ينشأ حالتان - عند تعيين الحالة العالمية وعندما لا يتم ضبطها. غالبًا ما تكون التبعيات الموضحة ضمنية ، لذلك قد تنسى بطريق الخطأ تعيين / إعادة ضبط هذه الحالات العالمية.
لجعل التبعيات واضحة للعيان ، يمكنك استخدام مبدأ حقن التبعية (DI) ، وهي: تمرير التبعية من خلال معلمات المُنشئ ، أو خاصية الكائن. هذا سيجعل من السهل استبدال تبعيات وهمية بدلاً من كائن حقيقي.
استدعاء غير متزامنيتم إجراء جميع اختبارات الوحدة بشكل متزامن. تنشأ صعوبة اختبار عدم التزامن لأن استدعاء طريقة الاختبار في الاختبار "يتجمد" تحسباً لاستكمال نطاق اختبار الوحدة. ستكون النتيجة انخفاض ثابت في الاختبار.
لاختبار مثل هذا الاختبار ، هناك عدة طرق:
- تشغيل NSRunLoop
- waitForExpectationsWithTimeout
يتطلب كلا الخيارين تحديد وسيطة مع مهلة. ومع ذلك ، لا يمكن ضمان أن الفاصل الزمني المحدد سيكون كافياً. على المستوى المحلي ، سيجتاز الاختبار الخاص بك ، ولكن في CI محملة بشكل كبير قد لا تكون هناك طاقة كافية وسيسقط - ومن ثم "وميض".
دعونا لدينا نوع من خدمة معالجة البيانات. نريد التحقق من أنه بعد تلقي استجابة من الخادم ، يقوم بنقل هذه البيانات لمزيد من المعالجة.
لإرسال الطلبات عبر الشبكة ، تستخدم الخدمة العميل للتعامل معها.
يمكن كتابة مثل هذا الاختبار بشكل غير متزامن باستخدام خادم وهمي لضمان استجابات الشبكة الثابتة.
@interface Service : NSObject @property (nonatomic, strong) id<APIClient> apiClient; @end @protocol APIClient <NSObject> - (void)getDataWithCompletion:(void (^)(id responseJSONData))completion; @end - (void)testRequestAsync {
لكن النسخة المتزامنة للاختبار ستكون أكثر ثباتًا وستسمح لك بالتخلص من العمل مع المهلات.
بالنسبة له نحن بحاجة إلى APIClient وهمية متزامن
@interface APIClientMock : NSObject <APIClient> @end @implementation - (void)getDataWithCompletion:(void (^)(id responseJSONData))completion { __auto_type fakeData = @{ @"key" : @"value" }; if (completion != nil) { completion(fakeData); } } @end
ثم سيبدو الاختبار أبسط وسيعمل بشكل أكثر استقرارًا
- (void)testRequestSync {
يمكن عزل العملية غير المتزامنة بتغليف كيان منفصل ، والذي يمكن اختباره بشكل مستقل. يحتاج باقي المنطق إلى اختباره بشكل متزامن. هذا النهج سوف تجنب معظم المزالق الناجمة عن عدم التزامن.
كخيار ، في حالة تحديث طبقة واجهة المستخدم من مؤشر ترابط الخلفية ، يمكنك التحقق مما إذا كنا في سلسلة الرسائل الرئيسية وما سيحدث إذا أجرينا مكالمة من الاختبار:
func performUIUpdate(using closure: @escaping () -> Void) {
للحصول على شرح مفصل ، راجع
مقال د .
ساندل .
اختبار رمز خارج عن إرادتكغالبًا ما ننسى الأشياء التالية:
- قد يعتمد تنفيذ الأساليب على تعريب التطبيق ،
- هناك طرق خاصة في SDK يمكن استدعاؤها بواسطة فئات الإطار ،
- قد يعتمد تنفيذ الأساليب على إصدار SDK

الحالات المذكورة أعلاه تقدم عدم اليقين عند كتابة الاختبارات وتشغيلها. لتجنب العواقب السلبية ، تحتاج إلى تشغيل الاختبارات على جميع اللغات ، وكذلك على إصدارات iOS التي يدعمها التطبيق الخاص بك. بشكل منفصل ، تجدر الإشارة إلى أنه ليست هناك حاجة لاختبار التعليمات البرمجية التي يتم إخفاء تنفيذها عنك.
مع هذا ، نريد إكمال الجزء الأول من المقالة حول الاختبار التلقائي لتطبيق Sberbank Online iOS ، المخصص لاختبار الوحدة.
في الجزء الثاني من المقال ، سنتحدث عن المشكلات التي حدثت أثناء كتابة 1500 اختبار لواجهة المستخدم ، بالإضافة إلى وصفات للتغلب عليها.
كُتب المقال مع
ريجنو - أنطون فلاسوف ، رئيس قسم التطوير ومطور iOS.