Thư viện kiểm thử Hành động trong ứng dụng

Thư viện kiểm thử Hành động trong ứng dụng (App Actions Test Library – AATL) cung cấp các tính năng cho phép nhà phát triển kiểm thử khả năng của Hành động trong ứng dụng (App Action) theo phương thức lập trình. Đây là cách kiểm thử tự động hoá mà thông thường được triển khai qua các truy vấn thoại thực tế hoặc công cụ kiểm thử Hành động trong ứng dụng.

Thư viện này giúp đảm bảo rằng cấu hình shortcut.xml là chính xác và lệnh gọi ý định trên Android được mô tả đã thành công. Thư viện kiểm thử Hành động trong ứng dụng cung cấp cơ chế kiểm thử khả năng thực hiện một vài tham số và ý định của Trợ lý Google của ứng dụng, bằng cách chuyển đổi các đối tượng này thành một đường liên kết sâu trên Android hoặc ý định Android. Bạn có thể xác nhận và sử dụng các đường liên kết sâu hoặc ý định này để tạo một thực thể hoạt động Android.

Hoạt động kiểm thử được thực hiện dưới dạng đơn vị Robolectric hoặc kiểm thử đo lường trong môi trường Android. Điều này cho phép nhà phát triển kiểm thử ứng dụng một cách toàn diện, nhờ quy trình mô phỏng hành vi thực tế của ứng dụng. Để kiểm thử BII, ý định tuỳ chỉnh hoặc phương thức thực hiện liên kết sâu, bạn có thể sử dụng bất kỳ khung kiểm thử đo lường nào (UI Automator, Espresso, JUnit4, Appium, Detox, Calabash).

Nếu ứng dụng có nhiều ngôn ngữ thì nhà phát triển có thể xác thực rằng chức năng của ứng dụng đang hoạt động chính xác trong nhiều ngôn ngữ.

Cách hoạt động

Để tích hợp Thư viện kiểm thử Hành động trong ứng dụng vào môi trường thử nghiệm của ứng dụng, nhà phát triển nên tạo mới hoặc cập nhật các chương trình kiểm thử đo lường hoặc Robolectric hiện có trên mô-đun app của ứng dụng.

Đoạn mã kiểm thử gồm các phần sau:

  • Khởi động thực thể thư viện, trong phương thức thiết lập chung hoặc trong các trường hợp kiểm thử riêng lẻ.
  • Mỗi lần kiểm thử riêng lẻ sẽ gọi phương thức fulfill của thực thể thư viện để đưa ra kết quả tạo ý định.
  • Sau đó, nhà phát triển sẽ xác nhận đường liên kết sâu hoặc kích hoạt phương thức thực hiện ứng dụng và chạy tính năng xác thực tuỳ chỉnh ở trạng thái ứng dụng.

Các yêu cầu về thiết lập

Để sử dụng thư viện kiểm thử, bạn cần cung cấp một số cấu hình ứng dụng ban đầu trước khi thêm nội dung kiểm thử vào ứng dụng.

Cấu hình

Để sử dụng Thư viện kiểm thử Hành động trong ứng dụng, hãy đảm bảo ứng dụng của bạn được định cấu hình như sau:

  • Cài đặt Trình bổ trợ Android cho Gradle (AGP)
  • Đưa tệp shortcuts.xml vào thư mục res/xml trong mô-đun app.
  • Đảm bảo AndroidManifest.xml bao gồm <meta-data android:name="android.app.shortcuts" android:resource=”@xml/shortcuts” /> thuộc:
    • thẻ <application>
    • thẻ trình chạy <activity>
  • Đặt phần tử <capability> bên trong phần tử <shortcuts> trong shortcuts.xml

Thêm phần phụ thuộc Thư viện kiểm thử Hành động trong ứng dụng

  1. Thêm kho lưu trữ của Google vào danh sách kho lưu trữ dự án trong settings.gradle:

        allprojects {
            repositories {
                …
                google()
            }
        }
    
  2. Trong tệp build.gradle của mô-đun ứng dụng, hãy thêm các phần phụ thuộc AATL:

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

    Hãy nhớ sử dụng số phiên bản của thư viện mà bạn đã tải xuống.

Tạo chương trình kiểm thử tích hợp

  1. Tạo chương trình kiểm thử mới trong app/src/androidTest. Đối với chương trình kiểm thử Robolectric, hãy tạo trong 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…")
          }
      
    

    Nếu đang sử dụng Espresso, bạn cần điều chỉnh cách chạy Hoạt động dựa trên kết quả AATL. Dưới đây là một ví dụ về Espresso dùng phương thức 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. Có tên và các thuộc tính chính trong sơ đồ ánh xạ tham số khớp với tham số trong BII. Ví dụ: order.orderedItem.name khớp với tài liệu cho tham số trong GET_ORDER.

  3. Tạo thực thể API bằng tham số Context của Android (lấy từ ApplicationProvider hoặc InstrumentationRegistry):

    • Cấu trúc ứng dụng mô-đun đơn:

    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);
          }
        
      
    • Cấu trúc ứng dụng nhiều mô-đun:

    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. Thực thi phương thức fulfill của API và lấy đối tượng AppActionsFulfillmentResult.

Xác nhận

Bạn nên xác nhận Thư viện kiểm thử Hành động trong ứng dụng theo cách dưới đây:

  1. Xác nhận kiểu phương thức thực hiện của AppActionsFulfillmentResult. Giá trị này phải là FulfillmentType.INTENT hoặc FulfillmentType.UNFULFILLED để kiểm thử cách ứng dụng hoạt động trong trường hợp có các yêu cầu BII bất thường.
  2. Có 2 phiên bản phương thức thực hiện: phương thức thực hiện INTENTDEEPLINK.
    • Thường thì nhà phát triển có thể phân biệt phương thức thực hiện INTENTDEEPLINK bằng cách xem thẻ ý định trong shortcuts.xml mà họ đang thực hiện bằng cách kích hoạt thư viện.
    • Nếu có một thẻ mẫu URL trong thẻ ý định, thì đây là dấu hiệu cho biết DEEPLINK thực hiện ý định này.
    • Nếu phương thức getData() của ý định kết quả trả về một đối tượng khác rỗng, thì điều này cũng cho biết đó là phương thức thực hiện DEEPLINK. Tương tự, nếu getData trả về null, thì đó là một phương thức thực hiện INTENT.
  3. Đối với trường hợp INTENT, hãy nhập AppActionsFulfillmentResult vào AppActionsIntentFulfillmentResult, tìm nạp Android Intent bằng cách gọi phương thức getIntent rồi thực hiện một trong những thao tác sau:
    • Xác nhận từng trường của Android Intent.
    • Xác nhận URI của một ý định được truy cập thông qua phương thức intent.getData.getHost.
  4. Đối với trường hợp DEEPLINK, hãy nhập AppActionsFulfillmentResult vào AppActionsIntentFulfillmentResult (tương tự như đối với INTENT ở trên), tìm nạp Android Intent bằng cách gọi phương thức getIntent và xác nhận URL liên kết sâu (truy cập thông qua intent.getData.getHost).
  5. Đối với cả INTENTDEEPLINK, bạn có thể sử dụng ý định kết quả để chạy hoạt động bằng khung kiểm thử Android đã chọn.

Quốc tế hoá

Nếu Ứng dụng của bạn có nhiều ngôn ngữ, bạn có thể định cấu hình kiểm thử để chạy kiểm thử trong một ngôn ngữ cụ thể. Ngoài ra, bạn có thể trực tiếp thay đổi ngôn ngữ:

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

Dưới đây là ví dụ về một chương trình kiểm thử AATL được định cấu hình thành tiếng Tây Ban Nha (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");
      }
      
    

Khắc phục sự cố

Nếu chương trình kiểm thử tích hợp của bạn thất bại ngoài dự kiến, bạn có thể tìm thông điệp nhật ký AATL trong cửa sổ logcat của Android Studio để xem thông báo cấp cảnh báo hoặc lỗi. Bạn cũng có thể tăng cấp độ ghi nhật ký để thu được nhiều kết quả đầu ra hơn từ thư viện.

Hạn chế

Dưới đây là những điểm hạn chế hiện tại của Thư viện kiểm thử Hành động trong ứng dụng:

  • AATL không kiểm tra các tính năng Hiểu ngôn ngữ tự nhiên (NLU) hay Chuyển lời nói thành văn bản (STT).
  • AATL không hoạt động khi quy trình kiểm thử nằm trong các mô-đun không phải là mô-đun ứng dụng mặc định.
  • AATL chỉ tương thích với Android 7.0 "Nougat" (API cấp 24) trở lên.