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

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

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

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

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

איך זה עובד

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

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

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

הדרישות להגדרת 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. יוצרים מופע של 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. מריצים את השיטה fulfill של ה-API ומקבלים את האובייקט AppActionsFulfillmentResult.

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

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

  1. להצהיר על סוג האספקה של AppActionsFulfillmentResult. הוא צריך להיות FulfillmentType.INTENT או FulfillmentType.UNFULFILLED כדי לבדוק איך האפליקציה פועלת במקרה של בקשות BII בלתי צפויות.
  2. יש 2 סוגים של השלמת הזמנות: השלמות הזמנות מסוג INTENT והשלמות הזמנות מסוג DEEPLINK.
    • בדרך כלל, המפתח יכול להבחין בין INTENT לבין DEEPLINK עמידה בדרישות על ידי עיון בתג Intent ב-shortcuts.xml שהם מממשים באמצעות הפעלת הספרייה.
    • אם יש תג של תבנית URL מתחת לתג Intent, המשמעות היא שה-DEEPLINK ממלא את הכוונה הזו.
    • אם ה-method 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).
  • AATL לא פועל כשהבדיקות הן במודולים אחרים מלבד אפליקציית ברירת המחדל של מודל טרנספורמר.
  • AATL תואם רק ל-Android 7.0 'Nougat' (רמת API‏ 24) ואילך.