Учебники

Espresso Testing Framework — Содержание

Android Intent используется для открытия нового действия, либо внутреннего (открытие экрана сведений о продукте на экране списка продуктов), либо внешнего (например, открытие номеронабирателя для совершения вызова). Внутренние намерения прозрачно обрабатываются средой тестирования эспрессо и не требуют какой-либо специальной работы со стороны пользователя. Тем не менее, вызывать внешнюю активность действительно сложно, потому что она выходит за рамки нашего приложения, тестируемого приложения. Как только пользователь вызывает внешнее приложение и выходит из тестируемого приложения, вероятность того, что пользователь вернется в приложение с заранее определенной последовательностью действий, будет значительно меньше. Поэтому нам нужно принять на себя действие пользователя перед тестированием приложения. Эспрессо предоставляет две возможности справиться с этой ситуацией. Они заключаются в следующем,

предназначена

Это позволяет пользователю убедиться, что правильное намерение открыто из тестируемого приложения.

намеревающийся

Это позволяет пользователю высмеивать внешнюю активность, например снимать фотографию с камеры, набирать номер из списка контактов и т. Д., И возвращаться к приложению с предопределенным набором значений (например, предопределенное изображение с камеры вместо фактического изображения). ,

Настроить

Espresso поддерживает опцию intent через библиотеку плагинов, и ее необходимо настроить в файле gradle приложения. Вариант конфигурации заключается в следующем,

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

предназначена()

Плагин Espresso intent предоставляет специальные средства сопоставления, чтобы проверить, является ли вызванное намерение ожидаемым. Предоставленные сопоставители и их назначение заключаются в следующем:

hasAction

Это принимает действие намерения и возвращает совпадение, которое соответствует указанному намерению.

hasData

Это принимает данные и возвращает сопоставление, которое сопоставляет данные, предоставленные намерению, при его вызове.

toPackage

Это принимает имя пакета намерения и возвращает сопоставление, которое совпадает с именем пакета вызванного намерения.

Теперь давайте создадим новое приложение и протестируем приложение на внешнюю активность, используя предназначенный (), чтобы понять концепцию.

  • Запустите Android-студию.

  • Создайте новый проект, как обсуждалось ранее, и назовите его IntentSampleApp.

  • Перенесите приложение на платформу AndroidX, используя меню параметров Refactor → Migrate to AndroidX .

  • Создайте текстовое поле, кнопку, чтобы открыть список контактов, и еще одну, чтобы набрать звонок, изменив файл activity_main.xml, как показано ниже,

Запустите Android-студию.

Создайте новый проект, как обсуждалось ранее, и назовите его IntentSampleApp.

Перенесите приложение на платформу AndroidX, используя меню параметров Refactor → Migrate to 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 ,

Также добавьте следующий элемент в файл ресурсов strings.xml ,

<string name = "phone_number">Phone number</string>
<string name = "call">Call</string>
<string name = "call_contact">Select from contact list</string>
  • Теперь добавьте приведенный ниже код в основное действие ( MainActivity.java ) в методе onCreate .

Теперь добавьте приведенный ниже код в основное действие ( 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
}

Здесь мы запрограммировали кнопку с идентификатором call_contact_button, чтобы открыть список контактов, и кнопку с идентификатором, кнопку, чтобы набрать звонок.

  • Добавьте статическую переменную REQUEST_CODE в класс MainActivity, как показано ниже,

Добавьте статическую переменную REQUEST_CODE в класс MainActivity, как показано ниже,

public class MainActivity extends AppCompatActivity {
   // ...
   private static final int REQUEST_CODE = 1;
   // ...
}
  • Теперь добавьте метод onActivityResult в класс MainActivity, как показано ниже:

Теперь добавьте метод onActivityResult в класс MainActivity, как показано ниже:

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);
         }
      }
   };
   // ...
}

Здесь onActivityResult будет вызываться, когда пользователь возвращается в приложение после открытия списка контактов с помощью кнопки call_contact_button и выбора контакта. Как только метод onActivityResult вызывается, он получает выбранный пользователем контакт, находит номер контакта и устанавливает его в текстовое поле.

  • Запустите приложение и убедитесь, что все в порядке. Окончательный вид заявки Intent приведен ниже,

Запустите приложение и убедитесь, что все в порядке. Окончательный вид заявки Intent приведен ниже,

Образец заявки

  • Теперь настройте намерение эспрессо в файле gradle приложения, как показано ниже,

Теперь настройте намерение эспрессо в файле gradle приложения, как показано ниже,

dependencies {
   // ...
   androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
  • Выберите пункт меню « Синхронизировать сейчас», предоставляемый Android Studio. Это загрузит библиотеку тестирования намерений и настроит ее должным образом.

  • Откройте файл ExampleInstrumentedTest.java и добавьте IntentsTestRule вместо обычно используемого AndroidTestRule . IntentTestRule — это специальное правило для обработки намеренного тестирования.

Выберите пункт меню « Синхронизировать сейчас», предоставляемый Android Studio. Это загрузит библиотеку тестирования намерений и настроит ее должным образом.

Откройте файл ExampleInstrumentedTest.java и добавьте IntentsTestRule вместо обычно используемого AndroidTestRule . IntentTestRule — это специальное правило для обработки намеренного тестирования.

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
}
  • Исправьте проблемы с импортом, используя опцию Alt + Enter, предоставляемую android studio, или включите нижеприведенные операторы импорта,

Исправьте проблемы с импортом, используя опцию Alt + Enter, предоставляемую android studio, или включите нижеприведенные операторы импорта,

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
}

Здесь, hasAction , hasData и toPackage используются вместе с allOf matcher для достижения успеха, только если все сопоставления пройдены.

  • Теперь запустите ExampleInstrumentedTest через меню контента в Android-студии.

Теперь запустите ExampleInstrumentedTest через меню контента в Android-студии.

намереваясь ()

Эспрессо предоставляет специальный метод — намерение (), чтобы высмеивать действие внешнего намерения. intending () принимает имя пакета намерения, которое должно быть смоделировано, и предоставляет метод responseWith, чтобы установить, как смоделированное намерение должно отвечать, как указано ниже,

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

Здесь responseWith () принимает намеренный результат типа 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)));
}

Здесь мы создали новое намерение и установили возвращаемое значение (при вызове намерения) в качестве первой записи списка контактов, содержимое: //com.android.contacts/data/1 . Затем мы установили метод intending, чтобы смоделировать вновь созданное намерение вместо списка контактов. Он устанавливает и вызывает наше вновь созданное намерение, когда вызывается пакет com.android.contacts и возвращается первая запись списка по умолчанию. Затем мы запустили действие click (), чтобы запустить фиктивное намерение и, наконец, проверили, совпадают ли телефонный номер из вызывающего фиктивного намерения и номер первой записи в списке контактов.

Если есть проблема с отсутствующим импортом, исправьте эти проблемы с помощью опции Alt + Enter, предоставляемой android studio, или включите приведенные ниже операторы импорта,

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.