Espresso 測試框架 - 意圖



Android 意圖用於開啟新的 Activity,可以是內部的(例如從產品列表螢幕開啟產品詳情螢幕)或外部的(例如開啟撥號器進行撥打電話)。Espresso 測試框架會透明地處理內部意圖 Activity,使用者無需執行任何特定操作。但是,呼叫外部 Activity 確實是一個挑戰,因為它超出了我們的範圍,即被測應用程式。一旦使用者呼叫外部應用程式並退出被測應用程式,使用者以預定義的動作序列返回應用程式的可能性就會大大降低。因此,我們需要在測試應用程式之前假設使用者操作。Espresso 提供了兩種處理這種情況的選項,如下所示:

intended

這允許使用者確保從被測應用程式中打開了正確的意圖。

intending

這允許使用者模擬外部 Activity,例如從相機拍攝照片、從聯絡人列表撥打電話等,並使用預定義的值集返回應用程式(例如,使用預定義的影像而不是實際影像)。

設定

Espresso 透過外掛庫支援意圖選項,並且需要在應用程式的 Gradle 檔案中配置該庫。配置選項如下所示:

dependencies {
   // ...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}

intended()

Espresso 意圖外掛提供特殊的匹配器來檢查呼叫的意圖是否為預期意圖。提供的匹配器及其用途如下所示:

hasAction

它接受意圖操作並返回一個匹配器,該匹配器匹配指定的意圖。

hasData

它接受資料並返回一個匹配器,該匹配器匹配呼叫意圖時提供的資料。

toPackage

它接受意圖包名並返回一個匹配器,該匹配器匹配呼叫的意圖的包名。

現在,讓我們建立一個新的應用程式並使用intended()測試該應用程式的外部 Activity,以瞭解該概念。

  • 啟動 Android Studio。

  • 建立一個新的專案,如前所述,並將其命名為 IntentSampleApp。

  • 使用重構 → 遷移到 AndroidX選項選單將應用程式遷移到 AndroidX 框架。

  • 建立一個文字框、一個開啟聯絡人列表的按鈕和另一個透過更改activity_main.xml進行撥打電話的按鈕,如下所示:

<?xml version = "1.0" encoding = "utf-8"?>
<RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
   xmlns:app = "http://schemas.android.com/apk/res-auto"
   xmlns:tools = "http://schemas.android.com/tools"
   android:layout_width = "match_parent"
   android:layout_height = "match_parent"
   tools:context = ".MainActivity">
   <EditText
      android:id = "@+id/edit_text_phone_number"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:text = ""
      android:autofillHints = "@string/phone_number"/>
   <Button
      android:id = "@+id/call_contact_button"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:layout_below = "@id/edit_text_phone_number"
      android:text = "@string/call_contact"/>
   <Button
      android:id = "@+id/button"
      android:layout_width = "wrap_content"
      android:layout_height = "wrap_content"
      android:layout_centerHorizontal = "true"
      android:layout_below = "@id/call_contact_button"
      android:text = "@string/call"/>
</RelativeLayout>
  • 此外,在strings.xml資原始檔中新增以下專案:

<string name = "phone_number">Phone number</string>
<string name = "call">Call</string>
<string name = "call_contact">Select from contact list</string>
  • 現在,在主 Activity(MainActivity.java)的onCreate方法下新增以下程式碼。

public class MainActivity extends AppCompatActivity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      // ... code
      // Find call from contact button
      Button contactButton = (Button) findViewById(R.id.call_contact_button);
      contactButton.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
            // Uri uri = Uri.parse("content://contacts");
            Intent contactIntent = new Intent(Intent.ACTION_PICK,
               ContactsContract.Contacts.CONTENT_URI);
            contactIntent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE);
            startActivityForResult(contactIntent, REQUEST_CODE);
         }
      });
      // Find edit view
      final EditText phoneNumberEditView = (EditText)
         findViewById(R.id.edit_text_phone_number);
      // Find call button
      Button button = (Button) findViewById(R.id.button);
      button.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View view) {
            if(phoneNumberEditView.getText() != null) {
               Uri number = Uri.parse("tel:" + phoneNumberEditView.getText());
               Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
               startActivity(callIntent);
            }
         }
      });
   }
   // ... code
}

在這裡,我們為 ID 為call_contact_button的按鈕編寫了程式,以開啟聯絡人列表,併為 ID 為button的按鈕編寫了程式以撥打電話。

  • MainActivity類中新增一個靜態變數REQUEST_CODE,如下所示:

public class MainActivity extends AppCompatActivity {
   // ...
   private static final int REQUEST_CODE = 1;
   // ...
}
  • 現在,在MainActivity類中新增onActivityResult方法,如下所示:

public class MainActivity extends AppCompatActivity {
   // ...
   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
      if (requestCode == REQUEST_CODE) {
         if (resultCode == RESULT_OK) {
            // Bundle extras = data.getExtras();
            // String phoneNumber = extras.get("data").toString();
            Uri uri = data.getData();
            Log.e("ACT_RES", uri.toString());
            String[] projection = {
               ContactsContract.CommonDataKinds.Phone.NUMBER, 
               ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME };
            Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
            cursor.moveToFirst();
            
            int numberColumnIndex =
               cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
            String number = cursor.getString(numberColumnIndex);
            
            int nameColumnIndex = cursor.getColumnIndex(
               ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
            String name = cursor.getString(nameColumnIndex);
            Log.d("MAIN_ACTIVITY", "Selected number : " + number +" , name : "+name);
            
            // Find edit view
            final EditText phoneNumberEditView = (EditText)
               findViewById(R.id.edit_text_phone_number);
            phoneNumberEditView.setText(number);
         }
      }
   };
   // ...
}

在這裡,當用戶使用call_contact_button按鈕開啟聯絡人列表並選擇聯絡人後返回應用程式時,將呼叫onActivityResult。一旦呼叫onActivityResult方法,它將獲取使用者選擇的聯絡人,查詢聯絡電話號碼並將其設定到文字框中。

  • 執行應用程式並確保一切正常。Intent 示例應用程式的最終外觀如下所示:

Sample Application
  • 現在,在應用程式的 Gradle 檔案中配置 Espresso 意圖,如下所示:

dependencies {
   // ...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
  • 單擊 Android Studio 提供的同步選項。這將下載意圖測試庫並正確配置它。

  • 開啟ExampleInstrumentedTest.java檔案,並新增IntentsTestRule而不是通常使用的AndroidTestRuleIntentTestRule是一個處理意圖測試的特殊規則。

public class ExampleInstrumentedTest {
   // ... code
   @Rule
   public IntentsTestRule<MainActivity> mActivityRule =
   new IntentsTestRule<>(MainActivity.class);
   // ... code
}
  • 新增兩個區域性變數來設定測試電話號碼和撥號器包名,如下所示:

public class ExampleInstrumentedTest {
   // ... code
   private static final String PHONE_NUMBER = "1 234-567-890";
   private static final String DIALER_PACKAGE_NAME = "com.google.android.dialer";
   // ... code
}
  • 使用 Android Studio 提供的 Alt + Enter 選項修復匯入問題,或者包含以下匯入語句:

import android.content.Context;
import android.content.Intent;

import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.*;
  • 新增以下測試用例以測試撥號器是否已正確呼叫:

public class ExampleInstrumentedTest {
   // ... code
   @Test
   public void validateIntentTest() {
      onView(withId(R.id.edit_text_phone_number))
         .perform(typeText(PHONE_NUMBER), closeSoftKeyboard());
      onView(withId(R.id.button)) .perform(click());
      intended(allOf(
         hasAction(Intent.ACTION_DIAL),
         hasData("tel:" + PHONE_NUMBER),
         toPackage(DIALER_PACKAGE_NAME)));
   }
   // ... code
}

在這裡,hasActionhasDatatoPackage匹配器與allOf匹配器一起使用,只有在所有匹配器都透過時才會成功。

  • 現在,透過 Android Studio 中的上下文選單執行ExampleInstrumentedTest

intending()

Espresso 提供了一種特殊方法 - intending()來模擬外部意圖操作。intending()接受要模擬的意圖的包名,並提供一個respondWith方法來設定如何使用以下指定的響應模擬的意圖:

intending(toPackage("com.android.contacts")).respondWith(result);

在這裡,respondWith()接受型別為Instrumentation.ActivityResult的意圖結果。我們可以建立新的存根意圖並手動設定結果,如下所示:

// Stub intent
Intent intent = new Intent();
intent.setData(Uri.parse("content://com.android.contacts/data/1"));
Instrumentation.ActivityResult result =
   new Instrumentation.ActivityResult(Activity.RESULT_OK, intent); 

測試聯絡人應用程式是否已正確開啟的完整程式碼如下:

@Test
public void stubIntentTest() {
   // Stub intent
   Intent intent = new Intent();
   intent.setData(Uri.parse("content://com.android.contacts/data/1"));
   Instrumentation.ActivityResult result =
      new Instrumentation.ActivityResult(Activity.RESULT_OK, intent);
   intending(toPackage("com.android.contacts")).respondWith(result);
   
   // find the button and perform click action
   onView(withId(R.id.call_contact_button)).perform(click());
   
   // get context
   Context targetContext2 = InstrumentationRegistry.getInstrumentation().getTargetContext();
   
   // get phone number
   String[] projection = { ContactsContract.CommonDataKinds.Phone.NUMBER,
      ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME };
   Cursor cursor =
      targetContext2.getContentResolver().query(Uri.parse("content://com.android.cont
      acts/data/1"), projection, null, null, null);
   
   cursor.moveToFirst();
   int numberColumnIndex =
      cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
   String number = cursor.getString(numberColumnIndex);
   
   // now, check the data
   onView(withId(R.id.edit_text_phone_number))
   .check(matches(withText(number)));
}

在這裡,我們建立了一個新的意圖並將返回值(呼叫意圖時)設定為聯絡人列表的第一個條目content://com.android.contacts/data/1。然後,我們將intending方法設定為模擬新建立的意圖以代替聯絡人列表。當呼叫包com.android.contacts時,它會設定並呼叫我們新建立的意圖,並返回列表的預設第一個條目。然後,我們觸發click()操作以啟動模擬意圖,並最終檢查從呼叫模擬意圖得到的電話號碼和聯絡人列表中第一個條目的號碼是否相同。

如果存在任何缺少的匯入問題,請使用 Android Studio 提供的 Alt + Enter 選項修復這些匯入問題,或者包含以下匯入語句:

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;

import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.Intents.intending;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.AllOf.allOf;
import static org.junit.Assert.*;

在測試類中新增以下規則以提供讀取聯絡人列表的許可權 -

@Rule
public GrantPermissionRule permissionRule =
GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS);

在應用程式清單檔案AndroidManifest.xml中新增以下選項 -

<uses-permission android:name = "android.permission.READ_CONTACTS" />

現在,確保聯絡人列表至少有一個條目,然後使用 Android Studio 的上下文選單執行測試。

廣告

© . All rights reserved.