A Splash of EarlGrey - UI تختبر تطبيق Toptal Talent

نشرت: 2022-03-11

من أهم الأشياء التي يمكنك القيام بها كمختبِر لجعل عملك أكثر كفاءة وسرعة هو أتمتة التطبيق الذي تختبره. الاعتماد فقط على الاختبارات اليدوية ليس ممكنًا لأنك ستحتاج إلى تشغيل مجموعة كاملة من الاختبارات كل يوم ، وأحيانًا عدة مرات في اليوم ، واختبار كل تغيير يتم دفعه إلى رمز التطبيق.

ستصف هذه المقالة رحلة فريقنا لتحديد EarlGrey 1.0 من Google كأداة عملت بشكل أفضل بالنسبة لنا في سياق أتمتة تطبيق iOS Toptal Talent. حقيقة أننا نستخدمها لا تعني أن EarlGrey هي أفضل أداة اختبار للجميع - إنها فقط الأداة التي تناسب احتياجاتنا.

لماذا انتقلنا إلى EarlGrey

على مر السنين ، أنشأ فريقنا تطبيقات جوال مختلفة على كل من iOS و Android. في البداية ، فكرنا في استخدام أداة اختبار واجهة المستخدم عبر الأنظمة الأساسية والتي من شأنها أن تسمح لنا بكتابة مجموعة واحدة من الاختبارات وتنفيذها على أنظمة تشغيل محمولة مختلفة. أولاً ، ذهبنا مع Appium ، الخيار المفتوح المصدر الأكثر شيوعًا المتاح.

ولكن مع مرور الوقت ، أصبحت قيود Appium أكثر وضوحا. في حالتنا ، كان العائقان الرئيسيان في Appium هما:

  • تسبب استقرار الإطار المشكوك فيه في العديد من رقائق الاختبار.
  • أعاقت عملية التحديث البطيئة نسبيًا عملنا.

للتخفيف من عيب Appium الأول ، قمنا بكتابة جميع أنواع التعديلات والقرصنة البرمجية لجعل الاختبارات أكثر استقرارًا. ومع ذلك ، لم يكن هناك شيء يمكننا القيام به لمعالجة الثانية. في كل مرة يتم فيها إصدار إصدار جديد من iOS أو Android ، استغرق Appium وقتًا طويلاً للحاق بالركب. وفي كثير من الأحيان ، بسبب وجود العديد من الأخطاء ، كان التحديث الأولي غير قابل للاستخدام. نتيجة لذلك ، اضطررنا في كثير من الأحيان إلى الاستمرار في تنفيذ اختباراتنا على إصدار نظام أساسي قديم أو إيقاف تشغيلها تمامًا حتى يتم توفير تحديث Appium يعمل.

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

لذلك ، انتقلنا إلى Espresso لمشروع Android وإلى EarlGrey 1.0 لتطوير iOS. بعد فوات الأوان ، يمكننا القول الآن أن هذا كان قرارًا جيدًا. كان الوقت "الضائع" بسبب الحاجة إلى كتابة مجموعتين مختلفتين من الاختبارات والحفاظ عليهما ، واحدة لكل منصة ، أكثر من تعويضه من خلال عدم الحاجة إلى التحقيق في العديد من الاختبارات غير المستقرة وعدم وجود أي توقف في تحديثات الإصدار.

هيكل المشروع المحلي

ستحتاج إلى تضمين إطار العمل في نفس مشروع Xcode مثل التطبيق الذي تقوم بتطويره. لذلك أنشأنا مجلدًا في الدليل الجذر لاستضافة اختبارات واجهة المستخدم. يعد إنشاء ملف EarlGrey.swift إلزاميًا عند تثبيت إطار عمل الاختبار ومحتوياته محددة مسبقًا.

تطبيق Toptal Talent: هيكل المشروع المحلي

EarlGreyBase هو الفصل الرئيسي لجميع فصول الاختبار. يحتوي على أساليب tearDown و setUp العامة ، الممتدة من XCTestCase . في setUp ، نحمّل بذرة ستُستخدم عمومًا في معظم الاختبارات (المزيد عن Stubbing لاحقًا) وقمنا أيضًا بتعيين بعض علامات التكوين التي لاحظناها تزيد من ثبات الاختبارات:

 // Turn off EarlGrey's network requests tracking since we don't use it and it can block tests execution GREYConfiguration.sharedInstance().setValue([".*"], forConfigKey: kGREYConfigKeyURLBlacklistRegex) GREYConfiguration.sharedInstance().setValue(false, forConfigKey: kGREYConfigKeyAnalyticsEnabled)

نستخدم نمط تصميم كائن الصفحة - تحتوي كل شاشة في التطبيق على فئة مقابلة يتم فيها تحديد جميع عناصر واجهة المستخدم وتفاعلاتها المحتملة. يسمى هذا الفصل بـ "الصفحة". يتم تجميع طرق الاختبار حسب الميزات الموجودة في ملفات وفئات منفصلة من الصفحات.

لإعطائك فكرة أفضل عن كيفية عرض كل شيء ، هذا ما تبدو عليه شاشتا تسجيل الدخول ونسيان كلمة المرور في تطبيقنا وكيف يتم تمثيلهما بواسطة كائنات الصفحة.

هذا هو مظهر شاشتي تسجيل الدخول ونسيان كلمة المرور في تطبيقنا.

لاحقًا في المقالة ، سنقدم محتويات التعليمات البرمجية لكائن صفحة تسجيل الدخول.

طرق المنفعة المخصصة

الطريقة التي يزامن بها EarlGrey إجراءات الاختبار مع التطبيق ليست دائمًا مثالية. على سبيل المثال ، قد يحاول النقر فوق زر لم يتم تحميله بعد في التسلسل الهرمي لواجهة المستخدم ، مما يتسبب في فشل الاختبار. لتجنب هذه المشكلة ، أنشأنا طرقًا مخصصة للانتظار حتى تظهر العناصر في الحالة المطلوبة قبل أن نتفاعل معها.

وفيما يلي بعض الأمثلة على ذلك:

 static func asyncWaitForVisibility(on element: GREYInteraction) { // By default, EarlGrey blocks test execution while // the app is animating or doing anything in the background. //https://github.com/google/EarlGrey/blob/master/docs/api.md#synchronization GREYConfiguration.sharedInstance().setValue(false, forConfigKey: kGREYConfigKeySynchronizationEnabled) element.assert(grey_sufficientlyVisible()) GREYConfiguration.sharedInstance().setValue(true, forConfigKey: kGREYConfigKeySynchronizationEnabled) } static func waitElementVisibility(for element: GREYInteraction, timeout: Double = 15.0) -> Bool { GREYCondition(name: "Wait for element to appear", block: { var error: NSError? element.assert(grey_notNil(), error: &error) return error == nil }).wait(withTimeout: timeout, pollInterval: 0.5) if !elementVisible(element) { XCTFail("Element didn't appear") } return true }

هناك شيء آخر لا يفعله EarlGrey من تلقاء نفسه وهو تمرير الشاشة حتى يصبح العنصر المطلوب مرئيًا. إليك كيف يمكننا القيام بذلك:

 static func elementVisible(_ element: GREYInteraction) -> Bool { var error: NSError? element.assert(grey_notVisible(), error: &error) if error != nil { return true } else { return false } } static func scrollUntilElementVisible(_ scrollDirection: GREYDirection, _ speed: String, _ searchedElement: GREYInteraction, _ actionElement: GREYInteraction) -> Bool { var swipes = 0 while !elementVisible(searchedElement) && swipes < 10 { if speed == "slow" { actionElement.perform(grey_swipeSlowInDirection(scrollDirection)) } else { actionElement.perform(grey_swipeFastInDirection(scrollDirection)) } swipes += 1 } if swipes >= 10 { return false } else { return true } }

هناك طرق مساعدة أخرى مفقودة من واجهة برمجة تطبيقات EarlGrey والتي حددناها وهي عد العناصر وقراءة القيم النصية. رمز هذه الأدوات متاح على GitHub: هنا وهنا.

Stubbing API Calls

للتأكد من أننا نتجنب نتائج الاختبار الخاطئة الناتجة عن مشكلات الخادم الخلفي ، نستخدم مكتبة OHHTTPStubs لمحاكاة مكالمات الخادم. التوثيق على صفحتهم الرئيسية واضح ومباشر ، لكننا سنقدم كيفية إيقاف الردود في تطبيقنا ، الذي يستخدم واجهة برمجة تطبيقات GraphQL.

 class StubsHelper { static let testURL = URL(string: "https://[our backend server]")! static func setupOHTTPStub(for request: StubbedRequest, delayed: Bool = false) { stub(condition: isHost(testURL.host!) && hasJsonBody(request.bodyDict())) { _ in let fix = appFixture(forRequest: request) if delayed { return fix.requestTime(0.1, responseTime: 7.0) } else { return fix } } } static let stubbedEmail = "[email protected]" static let stubbedPassword = "password" enum StubbedRequest { case login func bodyDict() -> [String: Any] { switch self { case .login: return EmailPasswordSignInMutation( email: stubbedTalentLogin, password: stubbedTalentPassword ).makeBodyIdentifier() } } func statusCode() -> Int32 { return 200 } func jsonFileName() -> String { let fileName: String switch self { case .login: fileName = "login" } return "\(fileName).json" } } private extension GraphQLOperation { func makeBodyIdentifier() -> [String: Any] { let body: GraphQLMap = [ "query": queryDocument, "variables": variables, "operationName": operationName ] // Normalize values like enums here, otherwise body comparison will fail guard let normalizedBody = body.jsonValue as? [String: Any] else { fatalError() } return normalizedBody } }

يتم تحميل كعب الروتين عن طريق استدعاء طريقة setupOHTTPStub :

 StubsHelper.setupOHTTPStub(for: .login)

نضع كل شيء معًا

سيوضح هذا القسم كيف نستخدم جميع المبادئ الموضحة أعلاه لكتابة اختبار تسجيل دخول فعلي شامل.

 import EarlGrey final class LoginPage { func login() -> HomePage { fillLoginForm() loginButton().perform(grey_tap()) return HomePage() } func fillLoginForm() { ElementsHelper.waitElementVisibility(emailField()) emailField().perform(grey_replaceText(StubsHelper.stubbedTalentLogin)) passwordField().perform(grey_tap()) passwordField().perform(grey_replaceText(StubsHelper.stubbedTalentPassword)) } func clearAllInputs() { if ElementsHelper.elementVisible(passwordField()) { passwordField().perform(grey_tap()) passwordField().perform(grey_replaceText("")) } emailField().perform(grey_tap()) emailField().perform(grey_replaceText("")) } } private extension LoginPage { func emailField(file: StaticString = #file, line: UInt = #line) -> GREYInteraction { return EarlGrey.selectElement(with: grey_accessibilityLabel("Email"), file: file, line: line) } func passwordField(file: StaticString = #file, line: UInt = #line) -> GREYInteraction { return EarlGrey.selectElement( with: grey_allOf([ grey_accessibilityLabel("Password"), grey_sufficientlyVisible(), grey_userInteractionEnabled() ]), file: file, line: line ) } func loginButton(file: StaticString = #file, line: UInt = #line) -> GREYInteraction { return EarlGrey.selectElement(with: grey_accessibilityID("login_button"), file: file, line: line) } } class BBucketTests: EarlGreyBase { func testLogin() { StubsHelper.setupOHTTPStub(for: .login) LoginPage().clearAllInputs() let homePage = LoginPage().login() GREYAssertTrue( homePage.assertVisible(), reason: "Home screen not displayed after successful login" ) } }

إجراء الاختبارات في CI

نحن نستخدم Jenkins كنظام تكامل مستمر لدينا ، ونجري اختبارات واجهة المستخدم لكل التزام في كل طلب سحب.

نستخدم fastlane scan المسار السريع لإجراء الاختبارات في CI وإنشاء التقارير. من المفيد إرفاق لقطات شاشة بهذه التقارير للاختبارات الفاشلة. لسوء الحظ ، لا يوفر scan هذه الوظيفة ، لذلك كان علينا تخصيصها.

في وظيفة tearDown() ، نكتشف ما إذا كان الاختبار قد فشل وقمنا بحفظ لقطة شاشة لمحاكي iOS إذا حدث ذلك.

 import EarlGrey import XCTest import UIScreenCapture override func tearDown() { if testRun!.failureCount > 0 { // name is a property of the XCTest instance // https://developer.apple.com/documentation/xctest/xctest/1500990-name takeScreenshotAndSave(as: name) } super.tearDown() } func takeScreenshotAndSave(as testCaseName: String) { let imageData = UIScreenCapture.takeSnapshotGetJPEG() let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) let filePath = "\(paths[0])/\(testCaseName).jpg" do { try imageData?.write(to: URL.init(fileURLWithPath: filePath)) } catch { XCTFail("Screenshot not written.") } }

يتم حفظ لقطات الشاشة في مجلد Simulator ، وستحتاج إلى جلبها من هناك لإرفاقها كأدوات بناء. نحن نستخدم Rake لإدارة نصوص CI الخاصة بنا. هذه هي الطريقة التي نجمع بها أدوات الاختبار:

 def gather_test_artifacts(booted_sim_id, destination_folder) app_container_on_sim = `xcrun simctl get_app_container #{booted_sim_id} [your bundle id] data`.strip FileUtils.cp_r "#{app_container_on_sim}/Documents", destination_folder end

الماخذ الرئيسية

إذا كنت تبحث عن طريقة سريعة وموثوقة لأتمتة اختبارات iOS الخاصة بك ، فابحث عن EarlGrey. تم تطويره وصيانته بواسطة Google (هل أحتاج إلى قول المزيد؟) ، وفي كثير من النواحي ، فهو يتفوق على الأدوات الأخرى المتاحة اليوم.

ستحتاج إلى تعديل الإطار قليلاً لإعداد طرق الأداة لتعزيز استقرار الاختبار. للقيام بذلك ، يمكنك البدء بأمثلة لدينا لطرق الأدوات المخصصة.

نوصي بإجراء اختبار على البيانات التي تم إيقافها للتأكد من أن اختباراتك لن تفشل لأن الخادم الخلفي لا يحتوي على جميع بيانات الاختبار التي تتوقعها. استخدم OHHTTPStubs أو خادم ويب محلي مماثل لإنجاز المهمة.

عند إجراء اختباراتك في CI ، تأكد من توفير لقطات شاشة للحالات الفاشلة لتسهيل تصحيح الأخطاء.

قد تتساءل عن سبب عدم انتقالنا إلى EarlGrey 2.0 حتى الآن ، وإليك شرح سريع. تم إصدار الإصدار الجديد العام الماضي ويعد ببعض التحسينات على الإصدار v1.0. لسوء الحظ ، عندما اعتمدنا EarlGrey ، لم يكن الإصدار 2.0 مستقرًا بشكل خاص. لذلك لم ننتقل إلى الإصدار 2.0 حتى الآن. ومع ذلك ، ينتظر فريقنا بفارغ الصبر إصلاحًا للخلل في الإصدار الجديد حتى نتمكن من ترحيل بنيتنا التحتية في المستقبل.

الموارد على الانترنت

دليل EarlGrey's Getting Started على صفحة GitHub الرئيسية هو المكان الذي تريد أن تبدأ منه إذا كنت تفكر في إطار عمل الاختبار لمشروعك. ستجد هناك دليل تثبيت سهل الاستخدام ، ووثائق واجهة برمجة التطبيقات للأداة ، وورقة غش سهلة الاستخدام تسرد جميع أساليب إطار العمل بطريقة سهلة الاستخدام أثناء كتابة الاختبارات.

للحصول على معلومات إضافية حول كتابة الاختبارات الآلية لنظام iOS ، يمكنك أيضًا التحقق من إحدى منشورات المدونة السابقة.