بدء استخدام اختبارات حلقة الألعاب لأجهزة iOS

باستخدام اختبارات Game Loop، يمكنك كتابة اختبارات متوافقة مع محرك الألعاب، ثم تشغيلها في Test Lab على الأجهزة التي تختارها. بهذه الطريقة، لن تحتاج إلى القلق بشأن كتابة الرموز البرمجية لواجهات مستخدم أو أُطر اختبار مختلفة. يحاكي اختبار "حلقة الألعاب" إجراءات لاعب حقيقي، وعند تشغيله على Test Lab، يوفّر طريقة سريعة وقابلة للتوسّع للتحقّق من أنّ لعبتك تعمل بشكل جيد لدى المستخدمين.

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

ما هو اختبار حلقة الألعاب؟

الحلقة هي عملية تشغيل كاملة أو جزئية للاختبار على تطبيق الألعاب. ويمكنك إجراء اختبار "حلقة الألعاب" محليًا على محاكي أو على مجموعة من الأجهزة في Test Lab. يمكن استخدام اختبارات "حلقات الألعاب" في ما يلي:

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

  • شغِّل لعبتك بأعلى إعدادات الجودة لمعرفة الأجهزة التي يمكنها تشغيلها.

  • إجراء اختبار فني، مثل تجميع برامج تظليل متعددة وتنفيذها والتأكّد من أنّ الناتج هو كما هو متوقّع

الخطوة 1: تسجيل مخطّط عنوان URL المخصّص لتطبيق Test Lab

أولاً، عليك تسجيل مخطط عنوان URL المخصّص الخاص بـ Firebase Test Lab في تطبيقك:

  1. في Xcode، اختَر هدف مشروع.

  2. انقر على علامة التبويب المعلومات، ثم أضِف نوع عنوان URL جديدًا.

  3. في الحقل مخططات عناوين URL، أدخِل firebase-game-loop. يمكنك أيضًا تسجيل نظام عناوين URL المخصّص من خلال إضافته إلى ملف الإعداد Info.plist الخاص بمشروعك في أي مكان ضمن العلامة <dict>:

    <key>CFBundleURLTypes</key>
     <array>
         <dict>
             <key>CFBundleURLName</key>
             <string></string>
             <key>CFBundleTypeRole</key>
             <string>Editor</string>
             <key>CFBundleURLSchemes</key>
             <array>
                 <string>firebase-game-loop</string>
             </array>
         </dict>
     </array>
    

تم الآن إعداد تطبيقك لتنفيذ اختبار باستخدام Test Lab.

الخطوة 2 (اختيارية): ضبط تطبيقك لتشغيل حلقات متعدّدة

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

في مفوّض تطبيقك، يمكنك إلغاء طريقة application(_:open:options:):

SwiftObjective-C
func application(_app: UIApplication,
                 open url: URL
                 options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    let components = URLComponents(url: url, resolvingAgainstBaseURL: true)!
    if components.scheme == "firebase-game-loop" {
        // ...Enter Game Loop Test logic to override application(_:open:options:).
    }
    return true
}
- (BOOL)application:(UIApplication *)app
            openURL:(NSURL *)url
            options:(NSDictionary &lt;UIApplicationOpenURLOptionsKey, id&gt; *)options {
  if ([url.scheme isEqualToString:(@"firebase-game-loop")]) {
      // ...Enter Game Loop Test logic to override application(_:open:options:).
  }
}

عند تنفيذ عدة حلقات في الاختبار، يتم تمرير الحلقة الحالية كمعلَمة إلى عنوان URL المستخدَم لتشغيل التطبيق. ويمكنك أيضًا الحصول على رقم الحلقة الحالية من خلال تحليل العنصر URLComponents المستخدَم لجلب مخطط عنوان URL المخصّص:

SwiftObjective-C
if components.scheme == "firebase-game-loop" {
    // Iterate over all parameters and find the one with the key "scenario".
    let scenarioNum = Int(components.queryItems!.first(where: { $0.name == "scenario" })!.value!)!
    // ...Write logic specific to the current loop (scenarioNum).
}
if ([url.scheme isEqualToString:(@"firebase-game-loop")]) {
    // Launch the app as part of a game loop.
    NSURLComponents *components = [NSURLComponents componentsWithURL:url
                                             resolvingAgainstBaseURL:YES];
    for (NSURLQueryItem *item in [components queryItems]) {
        if ([item.name isEqualToString:@"scenario"]) {
            NSInteger scenarioNum = [item.value integerValue];
            // ...Write logic specific to the current loop (scenarioNum).
        }
    }
}

الخطوة 3: إنشاء اختبار وتشغيله

بعد تسجيل مخطط عنوان URL المخصّص لتطبيق Test Lab، يمكنك إجراء الاختبار في وحدة تحكّم Firebase أو باستخدام gcloud beta CLI. إذا لم يسبق لك إجراء ذلك، أنشئ ملف IPA لتطبيقك (ستحتاج إلى تحديد موقعه لاحقًا).

  1. في Xcode، اختَر ملفًا تعريفيًا للتزويد لإنشاء التطبيق المستهدَف.
  2. من القائمة المنسدلة التي تظهر، انقر على المنتج > الأرشيف. اختَر أحدث أرشيف، ثم انقر على توزيع التطبيق.
  3. في النافذة التي تظهر، انقر على التطوير > التالي.
  4. اختياري: للحصول على إصدار أسرع، أزِل العلامة من المربّع بجانب الخيار إعادة الإنشاء من رمز Bitcode، ثم انقر على التالي. لا تتطلّب ميزة "Test Lab" تصغير حجم تطبيقك أو إعادة إنشائه لتشغيل اختبار، لذا يمكنك إيقاف هذا الخيار بأمان.
  5. انقر على تصدير، ثم أدخِل الدليل الذي تريد تنزيل ملف IPA الخاص بتطبيقك فيه.

إجراء اختبار في وحدة تحكّم Firebase

  1. إذا لم يسبق لك ذلك، افتح وحدة تحكّم Firebase وأنشئ مشروعًا.

  2. في صفحة Test Lab بوحدة تحكّم Firebase، انقر على تشغيل اختبارك الأول > تشغيل حلقة لعبة على iOS.

  3. في قسم تحميل التطبيق، انقر على استعراض، ثم اختَر ملف IPA الخاص بتطبيقك (إذا لم يسبق لك ذلك، أنشئ ملف IPA لتطبيقك).

  4. اختياري: إذا أردت تشغيل عدة حلقات (تُعرف أيضًا باسم سيناريوهات) في الوقت نفسه أو اختيار حلقات معيّنة لتشغيلها، أدخِل أرقام الحلقات في حقل السيناريوهات.

    على سبيل المثال، عند إدخال "1-3, 5"، سيتم تشغيل الحلقات 1 و2 و3 و5.Test Lab تلقائيًا (إذا لم تُدخل أي شيء في الحقل السيناريوهات)، لا يتم تنفيذ سوى الحلقة 1 في Test Lab.

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

إجراء اختبار باستخدام gcloud beta CLI

  1. إذا لم يسبق لك إجراء ذلك، عليك إعداد بيئة gcloud SDK المحلية، ثم التأكّد من تثبيت مكوّن gcloud التجريبي.

  2. نفِّذ الأمر gcloud beta firebase test ios run واستخدِم العلامات التالية لضبط عملية التنفيذ:

علامات اختبارات "حلقة الألعاب"
--type

مطلوبة: تحدّد نوع اختبار iOS الذي تريد تنفيذه. يمكنك إدخال أنواع الاختبارات xctest (القيمة التلقائية) أو game-loop.

--app

مطلوب: المسار المطلق (Google Cloud Storage أو نظام الملفات) لملف IPA الخاص بتطبيقك. لا تكون هذه العلامة صالحة إلا عند إجراء اختبارات &quot;حلقة الألعاب&quot;.

--scenario-numbers

حلقات الألعاب (المعروفة أيضًا باسم السيناريوهات) التي تريد تشغيلها في تطبيقك. يمكنك إدخال حلقة واحدة أو قائمة بالحلقات أو نطاق من الحلقات. عدد مرات التكرار التلقائي هو 1.

على سبيل المثال، --scenario-numbers=1-3,5 يشغّل الحلقات 1 و2 و3 و5.

--device-model

الجهاز الفعلي الذي تريد إجراء الاختبار عليه (يمكنك الاطّلاع على الأجهزة المتاحة التي يمكنك استخدامها).

--timeout

الحدّ الأقصى للمدة التي تريد أن يستمرّ فيها اختبارك يمكنك إدخال عدد صحيح لتمثيل المدة بالثواني، أو عدد صحيح وتعداد لتمثيل المدة كوحدة زمنية أطول.

على سبيل المثال:

  • يؤدي الخيار --timeout=200 إلى إيقاف الاختبار عند تشغيله لمدة تصل إلى 200 ثانية.
  • يؤدي الخيار --timeout=1h إلى إنهاء الاختبار تلقائيًا بعد ساعة واحدة من تشغيله.

على سبيل المثال، ينفّذ الأمر التالي اختبار "حلقة الألعاب" الذي ينفّذ الحلقات 1 و4 و6 و7 و8 على هاتف iPhone 8 Plus:

gcloud beta firebase test ios run
 --type game-loop --app path/to/my/App.ipa --scenario-numbers 1,4,6-8
 --device-model=iphone8plus

لمزيد من المعلومات عن gcloud CLI، يُرجى الاطّلاع على المستندات المرجعية.

إجراء اختبار محلي

لتشغيل الاختبار على جهازك، حمِّل تطبيق الألعاب في محاكي ونفِّذ ما يلي:

xcrun simctl openurl SIMULATOR_UDID firebase-game-loop://
  • يمكنك العثور على المعرّف الفريد للجهاز (UDID) الخاص بالمحاكي من خلال تنفيذ الأمر instruments -s devices.

  • إذا كان هناك محاكي واحد فقط قيد التشغيل، أدخِل السلسلة الخاصة "booted" بدلاً من SIMULATOR_UDID.

إذا كان الاختبار يتضمّن حلقات متعدّدة، يمكنك تحديد الحلقة التي تريد تشغيلها من خلال تمرير رقم الحلقة إلى العلامة scenario. يُرجى العِلم أنّه يمكنك تنفيذ حلقة واحدة فقط في كل مرة عند إجراء الاختبار محليًا. على سبيل المثال، إذا أردت تشغيل الحلقات 1 و2 و5، عليك تشغيل أمر منفصل لكل حلقة:

xcrun simctl openurl SIMULATOR_UDID firebase-game-loop://?scenario=1
xcrun simctl openurl SIMULATOR_UDID firebase-game-loop://?scenario=2
xcrun simctl openurl SIMULATOR_UDID firebase-game-loop://?scenario=5

إنهاء اختبار مبكرًا

يستمر اختبار "حلقة الألعاب" تلقائيًا إلى أن يصل إلى مهلة مدتها خمس دقائق، حتى بعد تنفيذ جميع الحلقات. عند انتهاء المهلة، ينتهي الاختبار ويتم إلغاء أي حلقات معلّقة. يمكنك تسريع اختبارك أو إنهاؤه مبكرًا من خلال استدعاء نظام عناوين URL المخصّص الخاص بـ Test Labfirebase-game-loop-complete في AppDelegate لتطبيقك. على سبيل المثال:

SwiftObjective-C
/// End the loop by calling our custom url scheme.
func finishLoop() {
    let url = URL(string: "firebase-game-loop-complete://")!
    UIApplication.shared.open(url)
}
- (void)finishLoop {
  UIApplication *app = [UIApplication sharedApplication];
  [app openURL:[NSURL URLWithString:@"firebase-game-loop-complete://"]
      options:@{}
completionHandler:^(BOOL success) {}];
}

تنهي اختبار "حلقة الألعاب" الحلقة الحالية وتنفّذ الحلقة التالية. عندما لا تكون هناك المزيد من الحلقات لتشغيلها، ينتهي الاختبار.

كتابة نتائج اختبار مخصّصة

يمكنك ضبط اختبار Game Loop لكتابة نتائج اختبار مخصّصة في نظام ملفات جهازك. بهذه الطريقة، عندما يبدأ الاختبار، Test Lab يخزّن ملفات النتائج في دليل GameLoopsResults على جهاز الاختبار (الذي يجب إنشاؤه بنفسك). عند انتهاء الاختبار، تنقل Test Lab جميع الملفات من الدليل GameLoopResults إلى حزمة مشروعك. يُرجى مراعاة ما يلي عند إعداد الاختبار:

  • يتم تحميل جميع ملفات النتائج بغض النظر عن نوع الملف أو حجمه أو عددها.

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

لإعداد الاختبار من أجل كتابة نتائج اختبار مخصّصة، اتّبِع الخطوات التالية:

  1. في دليل Documents الخاص بتطبيقك، أنشئ دليلاً باسم GameLoopResults.

  2. من أي مكان في رمز تطبيقك (مثل مفوّض التطبيق)، أضِف ما يلي:

    SwiftObjective-C
    /// Write to a results file.
    func writeResults() {
      let text = "Greetings from game loops!"
      let fileName = "results.txt"
      let fileManager = FileManager.default
      do {
    
      let docs = try fileManager.url(for: .documentDirectory,
                                     in: .userDomainMask,
                                     appropriateFor: nil,
                                     create: true)
      let resultsDir = docs.appendingPathComponent("GameLoopResults")
      try fileManager.createDirectory(
          at: resultsDir,
          withIntermediateDirectories: true,
          attributes: nil)
      let fileURL = resultsDir.appendingPathComponent(fileName)
      try text.write(to: fileURL, atomically: false, encoding: .utf8)
      } catch {
        // ...Handle error writing to file.
      }
    }
    
    /// Write to a results file.
    - (void)writeResults:(NSString *)message {
        // Locate and create the results directory (if it doesn't exist already).
        NSFileManager *manager = [NSFileManager defaultManager];
        NSURL* url = [[manager URLsForDirectory:NSDocumentDirectory
                                      inDomains:NSUserDomainMask] lastObject];
        NSURL* resultsDir = [url URLByAppendingPathComponent:@"GameLoopResults"
                                                 isDirectory:YES];
        [manager createDirectoryAtURL:resultsDir
          withIntermediateDirectories:NO
                           attributes:nil
                                error:nil];
    
        // Write the result message to a text file.
        NSURL* resultFile = [resultsDir URLByAppendingPathComponent:@"result.txt"];
        if ([manager fileExistsAtPath:[resultFile path]]) {
            // Append to the existing file
            NSFileHandle *handle = [NSFileHandle fileHandleForWritingToURL:resultFile
                                                                     error:nil];
            [handle seekToEndOfFile];
            [handle writeData:[message dataUsingEncoding:NSUTF8StringEncoding]];
            [handle closeFile];
        } else {
            // Create and write to the file.
            [message writeToURL:resultFile
                     atomically:NO
                       encoding:NSUTF8StringEncoding error:nil];
        }
    }