應用程式動作測試程式庫

應用程式動作測試程式庫 (AATL) 提供多項功能,可讓開發人員透過程式輔助測試應用程式動作執行要求,以自動化方式處理通常會使用實際語音查詢或應用程式動作測試工具進行的測試。

此程式庫可確保 shortcut.xml 設定正確無誤,並成功叫用上述 Android 意圖。應用程式動作測試程式庫提供一項機制,可將特定 Google 助理意圖和參數轉換為 Android 深層連結或 Android 意圖,藉此測試應用程式執行這些意圖和參數的能力,並可用來斷言和將 Android 活動例項化。

測試形式為 Android 環境中的 Robolectric 單元測試或檢測設備測試。這可讓開發人員模擬實際的應用程式行為,全面測試應用程式。如要測試 BII、自訂意圖或深層連結執行要求,您可以使用任何檢測設備測試架構,例如 UI Automator、Espresso、JUnit4、Appium、Detox、Calabash。

如果應用程式支援多種語言,開發人員可以在不同的語言代碼下,驗證應用程式功能是否正常運作。

運作方式

如要在應用程式測試環境中整合應用程式動作測試程式庫,開發人員應在應用程式的 app 模組中,建立或更新現有的 Robolectric 或檢測設備測試。

測試程式碼包含下列部分:

  • 在常見的設定方法或個別測試案例中,將程式庫例項初始化。
  • 每項個別測試都會呼叫程式庫例項的 fulfill 方法,藉此產生意圖建立結果。
  • 開發人員會接著斷言深層連結或觸發應用程式執行要求,並針對應用程式狀態執行自訂驗證。

設定需求條件

如要使用測試程式庫,您必須先調整一些應用程式設定,才能將測試新增至應用程式。

設定

如要使用應用程式動作測試程式庫,請確認應用程式採用以下設定:

  • 安裝 Android Gradle 外掛程式 (AGP)
  • app 模組的 res/xml 資料夾中加入 shortcuts.xml 檔案
  • 確認 AndroidManifest.xml 在下列任一項目底下包含 <meta-data android:name="android.app.shortcuts" android:resource=”@xml/shortcuts” />
    • <application> 標記
    • 啟動器 <activity> 標記
  • <capability> 元素放入 shortcuts.xml<shortcuts> 元素中

新增應用程式動作測試程式庫依附元件

  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 結果,修改啟動活動的方式。以下是 Espresso 使用 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 結構定義參數 (可從 ApplicationProviderInstrumentationRegistry 取得),對 API 例項執行例項化程序:

    • 單一模組應用程式架構:

    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. 執行 API 的 fulfill 方法,並取得 AppActionsFulfillmentResult 物件。

執行斷言

以下為斷言應用程式動作測試資料庫的建議做法:

  1. 斷言 AppActionsFulfillmentResult 的執行要求類型。所斷言的類型必須是 FulfillmentType.INTENTFulfillmentType.UNFULFILLED,才能測試應用程式在發生非預期 BII 要求時的行為。
  2. 執行要求分為 2 種:INTENTDEEPLINK 執行要求。
    • 一般來說,如要辨別 INTENTDEEPLINK 執行要求,開發人員只需在執行要求透過觸發程式庫執行的 shortcuts.xml 中查看意圖標記即可。
    • 如果意圖標記下方有 url-template 標記,就表示執行意圖的是 DEEPLINK
    • 如果結果意圖的 getData() 方法傳回非空值的物件,也表示執行要求類型為 DEEPLINK。同樣地,如果 getData 傳回 null,即表示執行要求類型為 INTENT
  3. 若是 INTENT 的情況,請將 AppActionsFulfillmentResult 轉換類型至 AppActionsIntentFulfillmentResult,然後透過呼叫 getIntent 方法擷取 Android 意圖,並執行下列其中一項操作:
    • 斷言 Android 意圖的個別欄位。
    • 斷言可透過 intent.getData.getHost 方法存取的意圖 URI。
  4. 若是 DEEPLINK 的情況,請將 AppActionsFulfillmentResult 轉換類型至 AppActionsIntentFulfillmentResult (與上述 INTENT 情況相同),然後透過呼叫 getIntent 擷取 Android 意圖,並斷言深層連結網址 (可透過 intent.getData.getHost 存取)。
  5. 對於 INTENTDEEPLINK,都可以使用產生的意圖,針對選定的 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);
    
  

以下範例是針對西班牙文 (ES) 語言代碼設定 AATL 測試:

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");
      }
      
    

疑難排解

如果整合測試出現預期外的失敗情形,您可以在 Android Studio Logcat 視窗中尋找 AATL 記錄訊息,取得警示或錯誤層級訊息。您也可以提高記錄層級,從程式庫擷取更多輸出內容。

限制

以下是應用程式動作測試程式庫目前的限制:

  • AATL 不會測試自然語言理解 (NLU) 或語音轉文字 (STT) 功能。
  • 如果測試位於預設應用程式模組以外的模組中,AATL 會無法運作。
  • AATL 只與 Android 7.0「Nougat」(API 級別 24) 以上版本相容。