مكتبة اختبار إجراءات التطبيق

توفّر مكتبة اختبار "إجراءات التطبيقات" (AATL) إمكانات لتمكين المطوّرين من اختبار تنفيذ "إجراءات التطبيقات" آليًا، ما يؤدي إلى برمجة الاختبار الذي يتم عادةً باستخدام طلبات صوتية فعلية أو أداة اختبار "إجراءات التطبيقات".

تساعد المكتبة في التأكّد من أنّ إعدادات shortcut.xml صحيحة ونجاح عملية استدعاء أهداف Android الموضّحة الموضّحة. توفّر "مكتبة اختبار الإجراءات في التطبيقات" آلية لاختبار قدرة تطبيقك على تحقيق أهداف "مساعد Google" ومعاييره المحدّدة، من خلال تحويلها إلى رابط لصفحة في تطبيق Android أو إلى هدف Android يمكن تأكيده واستخدامه لإنشاء مثيل لأحد أنشطة Android.

يتم إجراء الاختبار في شكل وحدة Robolectric أو اختبارات مدمَجة في بيئة Android. ويتيح ذلك للمطورين اختبار تطبيقاتهم بشكل شامل من خلال محاكاة سلوك التطبيق الفعلي. لاختبار BII أو نية الشراء المخصّصة أو تنفيذ الروابط لصفحات معيّنة، يمكن استخدام أي إطار اختبار مبرمج (UI Automator وEspresso وJUnit4 وAppium وDetox وCalabash).

إذا كان التطبيق متعدد اللغات، يمكن للمطوّرين التحقّق من عمل وظائف التطبيق بشكل صحيح في لغات مختلفة.

طريقة العمل

لدمج مكتبة اختبار إجراءات التطبيق في بيئة اختبار التطبيق، على المطوّرين إنشاء اختبارات Robolectric أو اختبارات جديدة أو تعديل الاختبارات الحالية في وحدة app للتطبيق.

يحتوي الرمز التجريبي على الأجزاء التالية:

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

متطلّبات الإعداد

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

الإعدادات

لاستخدام "مكتبة اختبار الإجراءات في التطبيقات"، تأكّد من ضبط تطبيقك على النحو التالي:

  • تثبيت المكوّن الإضافي Android Gradle الإضافي (AGP)
  • ضمِّن ملف shortcuts.xml في المجلد res/xml في الوحدة app.
  • تأكَّد من أنّ AndroidManifest.xml تتضمّن <meta-data android:name="android.app.shortcuts" android:resource=”@xml/shortcuts” /> ضمن أي مما يلي:
    • العلامة <application>
    • علامة <activity> لمشغِّل التطبيقات
  • يمكنك وضع العنصر <capability> داخل العنصر <shortcuts> في shortcuts.xml.

إضافة العناصر الاعتمادية لمكتبة اختبار الإجراءات في التطبيقات

  1. إضافة مستودع Google إلى قائمة مستودعات المشاريع في settings.gradle:

        allprojects {
            repositories {
                …
                google()
            }
        }
    
  2. في ملف build.gradle في وحدة التطبيق، أضِف تبعيات AATL:

        androidTestImplementation 'com.google.assistant.appactions:testing:1.0.0'
    

    احرص على استخدام رقم إصدار المكتبة التي نزّلتها.

إنشاء اختبارات دمج

  1. إنشاء اختبارات جديدة ضمن app/src/androidTest. بالنسبة لاختبارات Robolectric، يمكنك إنشاؤها ضمن app/src/test:

    Kotlin

      
        import android.content.Context
        import android.content.Intent
        import android.widget.TextView
        import androidx.test.core.app.ApplicationProvider
        import androidx.test.core.app.ActivityScenario
        import com.google.assistant.appactions.testing.aatl.AppActionsTestManager
        import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentIntentResult
        import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentResult
        import com.google.assistant.appactions.testing.aatl.fulfillment.FulfillmentType
        import com.google.common.collect.ImmutableMap
        import org.junit.Assert.assertEquals
        import org.junit.Before
        import org.junit.runner.RunWith
        import org.junit.Test
        import org.robolectric.RobolectricTestRunner
        …
        @Test
        fun IntentTestExample() {
          val intentParams = mapOf("feature" to "settings")
          val intentName = "actions.intent.OPEN_APP_FEATURE"
          val result = aatl.fulfill(intentName, intentParams)
    
          assertEquals(FulfillmentType.INTENT, result.getFulfillmentType())
    
          val intentResult = result as AppActionsFulfillmentIntentResult
          val intent = intentResult.intent
    
          // Developer can choose to assert different relevant properties of the returned intent, such as the action, activity, package, scheme and so on
          assertEquals("youtube", intent.scheme)
          assertEquals("settings", intent.getStringExtra("featureParam"))
          assertEquals("actions.intent.OPEN_APP_FEATURE", intent.action)
          assertEquals("com.google.android.youtube/.MainActivity",
              intent.component.flattenToShortString())
          assertEquals("com.google.myapp", intent.package)
    
          // Developers can choose to use returned Android Intent to launch and assess the activity. Below are examples for how it will look like for Robolectric and Espresso tests.
          // Please note that the below part is just a possible example of how Android tests are validating Activity functionality correctness for given Android Intent.
    
          // Robolectric example:
          val activity = Robolectric.buildActivity(MainActivity::class.java,
            intentResult.intent).create().resume().get()
    
          val title: TextView = activity.findViewById(R.id.startActivityTitle)
          assertEquals(title?.text?.toString(), "Launching…")
        }
      
    

    Java

      
        import android.content.Context;
        import android.content.Intent;
        import android.widget.TextView;
        import androidx.test.core.app.ApplicationProvider;
        import androidx.test.core.app.ActivityScenario;
        import com.google.assistant.appactions.testing.aatl.AppActionsTestManager;
        import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentIntentResult;
        import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentResult;
        import com.google.assistant.appactions.testing.aatl.fulfillment.FulfillmentType;
        import com.google.common.collect.ImmutableMap;
        import org.junit.Assert.assertEquals;
        import org.junit.Before;
        import org.junit.runner.RunWith;
        import org.junit.Test;
        import org.robolectric.RobolectricTestRunner;
        ...
        @Test
          public void IntentTestExample() throws Exception {
            Map<String, String> intentParams = ImmutableMap.of("feature", "settings");
            String intentName = "actions.intent.OPEN_APP_FEATURE";
            AppActionsFulfillmentResult result = aatl.fulfill(intentName, intentParams);
    
            assertEquals(FulfillmentType.INTENT, result.getFulfillmentType());
    
            AppActionsFulfillmentIntentResult intentResult = (AppActionsFulfillmentIntentResult) result;
    
            Intent intent = intentResult.getIntent();
    
            // Developer can choose to assert different relevant properties of the returned intent, such as the action, activity, package, or scheme
            assertEquals("settings", intent.getStringExtra("featureParam"));
            assertEquals("actions.intent.OPEN_APP_FEATURE", intent.getAction());
            assertEquals("com.google.android.youtube/.MainActivity", intent.getComponent().flattenToShortString());
            assertEquals("com.google.myapp", intent.getPackage());
    
            // Developers can choose to use returned Android Intent to launch and assess the   activity. Below are examples for how it will look like for Robolectric and  Espresso tests.
            // Please note that the below part is just a possible example of how Android tests are validating Activity functionality correctness for given Android Intent.
    
            // Robolectric example:
            MainActivity activity = Robolectric.buildActivity(MainActivity.class,intentResult.intent).create().resume().get();
    
            TextView title: TextView = activity.findViewById(R.id.startActivityTitle)
            assertEquals(title?.getText()?.toString(), "Launching…")
          }
      
    

    إذا كنت تستخدم الإسبريسو، ستحتاج إلى تعديل طريقة إطلاق النشاط بناءً على نتائج AATL. في ما يلي مثال لقهوة إسبرسو باستخدام طريقة ActivityScenario:

    Kotlin

        
        ActivityScenario.launch<MainActivity>(intentResult.intent);
        Espresso.onView(ViewMatchers.withId(R.id.startActivityTitle))
          .check(ViewAssertions.matches(ViewMatchers.withText("Launching…")))
        
    

    Java

        
          ActivityScenario.launch<MainActivity>(intentResult.intent);
          Espresso.onView(ViewMatchers.withId(R.id.startActivityTitle))
            .check(ViewAssertions.matches(ViewMatchers.withText("Launching…")))
        
    
  2. أن يتطابق الاسم والخصائص الرئيسية في تعيينات المعلَمات مع المعلَمات من BII. على سبيل المثال، تتطابق السمة order.orderedItem.name مع الوثائق الخاصة بالمَعلمة في GET_ORDER.

  3. إنشاء مثيل واجهة برمجة التطبيقات باستخدام مَعلمة سياق Android (التي تم الحصول عليها من ApplicationProvider أو InstrumentationRegistry):

    • بنية التطبيق التي تستخدم وحدة واحدة:

    Kotlin

        
          private lateinit var aatl: AppActionsTestManager
          @Before
          fun init() {
            val appContext = ApplicationProvider.getApplicationContext()
            aatl = AppActionsTestManager(appContext)
          }
        
      

    Java

        
          private AppActionsTestManager aatl;
    
          @Before
          public void init() {
            Context appContext = ApplicationProvider.getApplicationContext();
            aatl = new AppActionsTestManager(appContext);
          }
        
      
    • بنية التطبيق متعدّدة الوحدات:

    Kotlin

        
          private lateinit var aatl: AppActionsTestManager
    
          @Before
          fun init() {
            val appContext = ApplicationProvider.getApplicationContext()
            val lookupPackages = listOf("com.myapp.mainapp", "com.myapp.resources")
            aatl = AppActionsTestManager(appContext, lookupPackages)
          }
        
      

    Java

        
          private AppActionsTestManager aatl;
    
          @Before
          public void init() throws Exception {
    
            Context appContext = ApplicationProvider.getApplicationContext();
            List<String> lookupPackages = Arrays.asList("com.myapp.mainapp","com.myapp.resources");
            aatl = new AppActionsTestManager(appContext, Optional.of(lookupPackages));
          }
        
      
  4. نفِّذ طريقة fulfill لواجهة برمجة التطبيقات واحصل على الكائن AppActionsFulfillmentResult.

تنفيذ التأكيدات

الطريقة المُقترَحة لتأكيد مكتبة اختبار الإجراءات في التطبيقات هي:

  1. أكِّد نوع تنفيذ AppActionsFulfillmentResult. يجب أن يكون FulfillmentType.INTENT أو FulfillmentType.UNFULFILLED لاختبار سلوك التطبيق في حال حدوث طلبات غير متوقعة من BII.
  2. ثمة طريقتان لتنفيذ الطلب: INTENT وDEEPLINK.
    • يمكن عادةً لمطوّر البرامج التمييز بين عمليات تنفيذ INTENT وDEEPLINK من خلال الاطّلاع على علامة النية في shortcuts.xml التي يفي بها من خلال تفعيل المكتبة.
    • إذا كانت هناك علامة نموذج عنوان URL ضمن علامة النية، هذا يشير إلى أن DEEPLINK تحقق هذا الهدف.
    • إذا كانت طريقة getData() لغرض النتيجة تعرض كائنًا غير فارغ، يشير هذا أيضًا إلى تنفيذDEEPLINK. وبالمثل، إذا عرض getData null، يعني ذلك أنّه تنفيذ INTENT.
  3. في حالة INTENT، عليك استرجاع typecast AppActionsFulfillmentResult إلى AppActionsIntentFulfillmentResult من خلال استدعاء طريقة Android Intent من خلال استدعاء طريقة getIntent وتنفيذ أحد الإجراءات التالية:
    • التأكيد على حقول فردية في Android Intent
    • أكِّد معرّف الموارد المنتظم (URI) لعنصر يتم الوصول إليه من خلال طريقة intent.getData.getHost.
  4. بالنسبة إلى حالة DEEPLINK، عليك تحويل النوع من AppActionsFulfillmentResult إلى AppActionsIntentFulfillmentResult (كما هو الحال في سيناريو INTENT أعلاه)، واسترجاع هدف Android من خلال استدعاء طريقة getIntent وتأكيد عنوان URL لرابط لصفحة معيّنة (يتم الوصول إليه من خلال intent.getData.getHost).
  5. بالنسبة إلى كل من INTENT وDEEPLINK، يمكنك استخدام الغرض الناتج لإطلاق النشاط باستخدام إطار عمل اختبار Android الذي تم اختياره.

التوافق مع أسواق عالمية

إذا كان تطبيقك يحتوي على لغات متعددة، يمكنك ضبط إعدادات الاختبارات لتشغيل لغة محدّدة تخضع للاختبار. بدلاً من ذلك، يمكنك تغيير اللغة مباشرةً:

Kotlin

    
    import android.content.res.Configuration
    import java.util.Locale
    ...
    val newLocale = Locale("es")
    val conf = context.resources.configuration
    conf = Configuration(conf)
    conf.setLocale(newLocale)
    
  

Java

    
    Locale newLocale = new Locale("es");
    Configuration conf = context.getResources().getConfiguration();
    conf = new Configuration(conf);
    conf.setLocale(newLocale);
    
  

في ما يلي مثال على اختبار AATL تم إعداده للّغة الإسبانية (ES):

Kotlin

      
      import com.google.common.truth.Truth.assertThat
      import org.junit.Assert.assertEquals
      import android.content.Context
      import android.content.res.Configuration
      import androidx.test.platform.app.InstrumentationRegistry
      import com.google.assistant.appactions.testing.aatl.AppActionsTestManager
      import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentIntentResult
      import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentResult
      import com.google.assistant.appactions.testing.aatl.fulfillment.FulfillmentType
      import com.google.common.collect.ImmutableMap
      import java.util.Locale
      import org.junit.Before
      import org.junit.Test
      import org.junit.runner.RunWith
      import org.robolectric.RobolectricTestRunner

      @RunWith(RobolectricTestRunner::class)
      class ShortcutForDifferentLocaleTest {

        @Before
        fun setUp() {
          val context = InstrumentationRegistry.getInstrumentation().getContext()

          // change the device locale to 'es'
          val newLocale = Locale("es")
          val conf = context.resources.configuration
          conf = Configuration(conf)
          conf.setLocale(newLocale)

          val localizedContext = context.createConfigurationContext(conf)
        }

        @Test
        fun shortcutForDifferentLocale_succeeds() {
          val aatl = AppActionsTestManager(localizedContext)
          val intentName = "actions.intent.ORDER_MENU_ITEM"
          val intentParams = ImmutableMap.of("menuItem.name", "hamburguesa con queso")

          val result = aatl.fulfill(intentName, intentParams)
          assertThat(result.getFulfillmentType()).isEqualTo(FulfillmentType.INTENT)

          val intentResult = result as AppActionsFulfillmentIntentResult

          assertThat(intentResult.getIntent().getData().toString())
            .isEqualTo("myfoodapp://browse?food=food_hamburger")
        }
      }
      
    

Java

      
      import static com.google.common.truth.Truth.assertThat;
      import static org.junit.Assert.assertEquals;

      import android.content.Context;
      import android.content.res.Configuration;
      import androidx.test.platform.app.InstrumentationRegistry;
      import com.google.assistant.appactions.testing.aatl.AppActionsTestManager;
      import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentIntentResult;
      import com.google.assistant.appactions.testing.aatl.fulfillment.AppActionsFulfillmentResult;
      import com.google.assistant.appactions.testing.aatl.fulfillment.FulfillmentType;
      import com.google.common.collect.ImmutableMap;
      import java.util.Locale;
      import org.junit.Before;
      import org.junit.Test;
      import org.junit.runner.RunWith;
      import org.robolectric.RobolectricTestRunner;

      @Test
      public void shortcutForDifferentLocale_succeeds() throws Exception {
        Context context = InstrumentationRegistry.getInstrumentation().getContext();

        // change the device locale to 'es'
        Locale newLocale = new Locale("es");
        Configuration conf = context.getResources().getConfiguration();
        conf = new Configuration(conf);
        conf.setLocale(newLocale);

        Context localizedContext = context.createConfigurationContext(conf);

        AppActionsTestManager aatl = new AppActionsTestManager(localizedContext);
        String intentName = "actions.intent.ORDER_MENU_ITEM";
        ImmutableMap<String, String> intentParams = ImmutableMap.of("menuItem.name", "hamburguesa con queso");

        AppActionsFulfillmentResult result = aatl.fulfill(intentName, intentParams);
        assertThat(result.getFulfillmentType()).isEqualTo(FulfillmentType.INTENT);

        AppActionsFulfillmentIntentResult intentResult = (AppActionsFulfillmentIntentResult) result;

        assertThat(intentResult.getIntent().getData().toString())
          .isEqualTo("myfoodapp://browse?food=food_hamburger");
      }
      
    

تحديد المشاكل وحلّها

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

القيود

في ما يلي القيود الحالية على مكتبة اختبارات الإجراءات في التطبيقات :

  • لا يختبر AATL ميزات فهم اللغة الطبيعية (NLU) أو تحويل الكلام إلى نص (STT).
  • لا تعمل AATL عندما تكون الاختبارات في وحدات أخرى غير وحدة التطبيق الافتراضية.
  • تتوافق تقنية AATL فقط مع نظام التشغيل Android 7.0 "Nougat" (المستوى 24 من واجهة برمجة التطبيقات) والإصدارات الأحدث.