ספריית הבדיקות של 'פעולות באפליקציה'

ספריית הבדיקות של 'פעולות באפליקציה' (AATL) מספקת למפתחים כדי לבדוק את האפשרות לביצוע פעולות באפליקציה באופן פרוגרמטי, ביצוע אוטומציה של בדיקות מתבצעות בדרך כלל באמצעות שאילתות קוליות אמיתיות או באמצעות כלי הבדיקה של פעולות באפליקציה.

הספרייה עוזרת לוודא שההגדרה של shortcut.xml נכונה ההפעלה המתוארת של Intent של Android מצליחה. ספריית הבדיקות של 'פעולות באפליקציה' שמספק מנגנון לבדיקת היכולת של האפליקציה למלא את הבקשה, כוונת רכישה ופרמטרים של Assistant, על ידי המרה שלהם לקישור עומק ל-Android או Intent Android, שאפשר להצהיר עליו ולהשתמש בו כדי ליצור מכשיר Android פעילות.

הבדיקה מתבצעת בצורה של יחידות רובולקטריות או בדיקות אינסטרומנטליות בסביבת Android. כך מפתחים יכולים לבדוק באופן מקיף באמצעות אמולציה של התנהגות האפליקציה בפועל. לבדיקת BII, התאמה אישית ב-Intents, או מילוי של קישורי עומק, כל מסגרת בדיקה אינסטרומנטלית יכולה להיות בשימוש (UI Automator, Espresso, JUnit4, Appium, Detox, Calabash).

אם האפליקציה רב-לשונית, המפתחים יכולים לוודא הפונקציונליות של האפליקציה פועלת באופן תקין באזורים שונים.

איך זה עובד

כדי לשלב את ספריית הבדיקות של 'פעולות באפליקציה' בסביבת הבדיקה של האפליקציה, המפתחים צריכים ליצור או לעדכן בדיקות Robolectric או מכשירים אינסטרומנטליות קיימות ב-app של האפליקציה.

קוד הבדיקה מכיל את החלקים הבאים:

  • אתחול של מופע הספרייה, בשיטת ההגדרה הנפוצה או בתרחישי בדיקה נפרדים.
  • כל בדיקה בודדת מפעילה את ה-method fulfill של מכונת הספרייה כדי כדי ליצור את התוצאה של יצירת הכוונה.
  • לאחר מכן המפתח מצהיר על קישור עומק או מפעיל את מילוי ההזמנות של האפליקציה, ומריץ אימות מותאם אישית על מצב האפליקציה.

הדרישות להגדרת YouTube Music

כדי להשתמש בספריית הבדיקות, צריך לבצע הגדרות ראשוניות באפליקציה לפני שמוסיפים את הבדיקות לאפליקציה.

הגדרות אישיות

כדי להשתמש בספריית הבדיקות של 'פעולות באפליקציה', צריך לוודא שהאפליקציה מוגדרת כך:

  • התקנת הפלאגין של 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…")
          }
      
    

    אם משתמשים ב-Espresso, צריך לשנות את האופן שבו מפעילים את הפעילות על סמך תוצאות 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. לדוגמה, הערך exercisePlan.forExercise.name תואם למסמכי העזרה של הפרמטר ב-GET_EXERCISE_PLAN.

  3. מופע של Instantiate API עם הפרמטר Android Context (מתקבל מ- 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. מפעילים את ה-method fulfill ב-API ומשיגים אובייקט AppActionsFulfillmentResult.

ביצוע טענות נכוֹנוּת (assertions)

הדרך המומלצת להצהיר בעלות על ספריית הבדיקות של 'פעולות באפליקציה' היא:

  1. בודקים את סוג המילוי של AppActionsFulfillmentResult. כדי לבדוק איך האפליקציה מתנהגת במקרה של בקשות BII בלתי צפויות, הערך צריך להיות FulfillmentType.INTENT או FulfillmentType.UNFULFILLED.
  2. יש 2 טעמים של מילוי הזמנות: INTENT ו-DEEPLINK.
    • בדרך כלל, המפתח יכול להבחין בין INTENT לבין DEEPLINK עמידה בדרישות על ידי עיון בתג Intent ב-shortcuts.xml שהם מממשים באמצעות הפעלת הספרייה.
    • אם יש תג של תבנית URL מתחת לתג Intent, המשמעות היא שה-DEEPLINK ממלא את הכוונה הזו.
    • אם השיטה getData() של ה-intent בתוצאה מחזירה אובייקט שאינו null, זה גם מציין שהבקשה DEEPLINK בוצעה. באופן דומה, אם getData מחזירה את הערך null, המשמעות היא שמדובר במילוי בקשה מסוג INTENT.
  3. באותיות רישיות במסגרת INTENT, מקלידים את הערך AppActionsFulfillmentResult בתור AppActionsIntentFulfillmentResult, אחזר את ה-Intent של Android על ידי התקשרות getIntent מבצעים אחת מהפעולות הבאות:
    • הצהרת בעלות על שדות ספציפיים ב-Android Intent.
    • הצהרת בעלות על ה-URI של Intent שהגישה אליו מתבצעת דרך ה-method Intent.getData.getHost.
  4. באותיות רישיות במסגרת DEEPLINK, מקלידים את הערך AppActionsFulfillmentResult בתור AppActionsIntentFulfillmentResult (זהה לתרחיש INTENT שלמעלה), לאחזר את ה-Intent של Android על ידי קריאה ל-method 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.GET_EXERCISE_PLAN"
          val intentParams = ImmutableMap.of("exercisePlan.forExercise.name", "Running")

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

          val intentResult = result as AppActionsFulfillmentIntentResult

          assertThat(intentResult.getIntent().getData().toString())
            .isEqualTo("myexercise://browse?plan=running_weekly")
        }
      }
      
    

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.GET_EXERCISE_PLAN";
        ImmutableMap<String, String> intentParams = ImmutableMap.of("exercisePlan.forExercise.name", "Running");

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

        AppActionsFulfillmentIntentResult intentResult = (AppActionsFulfillmentIntentResult) result;

        assertThat(intentResult.getIntent().getData().toString())
          .isEqualTo("myexercise://browse?plan=running_weekly");
      }
      
    

פתרון בעיות

אם בדיקת השילוב נכשלת באופן בלתי צפוי, אפשר לחפש הודעות ביומן AATL בחלון ה-Logcat ב-Android Studio כדי לקבל את הודעת האזהרה או רמת השגיאה. אפשר גם להגדיל את הרישום ביומן רמה כדי להפיק יותר פלט בספרייה.

מגבלות

אלו המגבלות הנוכחיות של ספריית הבדיקות של 'פעולות באפליקציה':

  • AATL לא בודק את הבנת שפה טבעית (NLU) או המרת דיבור לטקסט (STT) (STT).
  • AATL לא פועל כשהבדיקות הן במודולים אחרים מלבד אפליקציית ברירת המחדל של מודל טרנספורמר.
  • AATL תואם רק ל-Android 7.0 'Nougat' (רמת API‏ 24) ואילך.