Framework testowania espresso - intencje
Android Intent służy do otwierania nowej aktywności, wewnętrznej (otwarcie ekranu szczegółów produktu z ekranu listy produktów) lub zewnętrznej (jak otwarcie dialera w celu wykonania połączenia). Wewnętrzna aktywność intencyjna jest obsługiwana w sposób przejrzysty przez framework testowy espresso i nie wymaga żadnej konkretnej pracy ze strony użytkownika. Jednak wywołanie aktywności zewnętrznej jest naprawdę wyzwaniem, ponieważ wykracza poza nasz zakres, testowaną aplikację. Gdy użytkownik wywoła zewnętrzną aplikację i wyjdzie z testowanej aplikacji, szanse na powrót użytkownika do aplikacji z predefiniowaną sekwencją działań są raczej mniejsze. Dlatego przed przetestowaniem aplikacji musimy przyjąć działanie użytkownika. Espresso daje dwie możliwości rozwiązania tej sytuacji. Są one następujące:
zamierzony
Pozwala to użytkownikowi upewnić się, że z testowanej aplikacji zostanie otwarta właściwa intencja.
zamierzający
Pozwala to użytkownikowi na wyśmiewanie zewnętrznej czynności, takiej jak zrobienie zdjęcia z aparatu, wybranie numeru z listy kontaktów itp., I powrót do aplikacji z predefiniowanym zestawem wartości (jak predefiniowany obraz z aparatu zamiast rzeczywistego obrazu) .
Ustawiać
Espresso obsługuje opcję zamiaru poprzez bibliotekę wtyczek, a bibliotekę należy skonfigurować w pliku gradle aplikacji. Opcje konfiguracji są następujące:
dependencies {
// ...
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
zamierzony()
Wtyczka zamiaru Espresso zapewnia specjalne dopasowania, które sprawdzają, czy wywołana intencja jest oczekiwaną intencją. Dostarczone dopasowania i cel dopasowania są następujące:
hasAction
To akceptuje akcję zamiaru i zwraca dopasowanie, które pasuje do określonej intencji.
hasData
Spowoduje to zaakceptowanie danych i zwrócenie dopasowania, które dopasowuje dane dostarczone do intencji podczas jej wywoływania.
zapakować
To akceptuje nazwę pakietu intencji i zwraca element dopasowujący, który pasuje do nazwy pakietu przywołanej intencji.
Teraz stwórzmy nową aplikację i przetestujmy ją pod kątem aktywności zewnętrznej przy użyciu zamierzonego (), aby zrozumieć koncepcję.
Uruchom studio Android.
Utwórz nowy projekt, jak omówiono wcześniej, i nadaj mu nazwę IntentSampleApp.
Przenieś aplikację do frameworka AndroidX za pomocą menu opcji Refactor → Migrate to AndroidX .
Utwórz pole tekstowe, przycisk do otwierania listy kontaktów i inny do wybierania połączenia, zmieniając plik activity_main.xml, jak pokazano poniżej,
<?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>
Dodaj również poniższy element w pliku zasobów strings.xml ,
<string name = "phone_number">Phone number</string>
<string name = "call">Call</string>
<string name = "call_contact">Select from contact list</string>
Teraz dodaj poniższy kod w głównym działaniu ( MainActivity.java ) w metodzie 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
}
Tutaj zaprogramowaliśmy przycisk z id, przycisk call_contact_button do otwierania listy kontaktów i przycisk z id, przycisk do wybierania połączenia.
Dodaj zmienną statyczną REQUEST_CODE w klasie MainActivity, jak pokazano poniżej,
public class MainActivity extends AppCompatActivity {
// ...
private static final int REQUEST_CODE = 1;
// ...
}
Teraz dodać onActivityResult metodę w główną działalność klasie jak poniżej,
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);
}
}
};
// ...
}
W tym przypadku onActivityResult zostanie wywołany, gdy użytkownik powróci do aplikacji po otwarciu listy kontaktów za pomocą przycisku call_contact_button i wybraniu kontaktu. Po wywołaniu metody onActivityResult pobiera ona wybrany kontakt przez użytkownika, znajduje numer kontaktu i umieszcza go w polu tekstowym.
Uruchom aplikację i upewnij się, że wszystko jest w porządku. Ostateczny wygląd przykładowej aplikacji Intent jest pokazany poniżej,
Teraz skonfiguruj intencję espresso w pliku gradle aplikacji, jak pokazano poniżej,
dependencies {
// ...
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.1'
}
Kliknij opcję menu Synchronizuj teraz udostępnioną przez Android Studio. Spowoduje to pobranie biblioteki testów intencji i skonfigurowanie jej poprawnie.
Otwórz plik ExampleInstrumentedTest.java i dodaj IntentsTestRule zamiast normalnie używanej AndroidTestRule . IntentTestRule to specjalna reguła dotycząca testowania intencji.
public class ExampleInstrumentedTest {
// ... code
@Rule
public IntentsTestRule<MainActivity> mActivityRule =
new IntentsTestRule<>(MainActivity.class);
// ... code
}
Dodaj dwie zmienne lokalne, aby ustawić testowy numer telefonu i nazwę pakietu dialera, jak poniżej,
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
}
Napraw problemy z importem za pomocą opcji Alt + Enter udostępnionej przez studio Android lub dołącz poniższe instrukcje importu,
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.*;
Dodaj poniższy przypadek testowy, aby sprawdzić, czy dialer jest poprawnie wywoływany,
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
}
Tutaj dopasowania hasAction , hasData i toPackage są używane wraz z dopasowaniem allOf, aby odnieść sukces tylko wtedy, gdy wszystkie dopasowania zostaną przekazane.
Teraz uruchom ExampleInstrumentedTest za pomocą menu zawartości w Android Studio.
zamierzający ()
Espresso zapewnia specjalną metodę - zamierzającą () kpić z zewnętrznego działania zamierzonego. intending () akceptuje nazwę pakietu intencji, która ma być wyszydzana, i udostępnia metodę responseWith, aby ustawić sposób odpowiedzi na udaną intencję, jak określono poniżej,
intending(toPackage("com.android.contacts")).respondWith(result);
Tutaj respondWith () akceptuje wynik intencji typu Instrumentation.ActivityResult . Możemy utworzyć nową intencję pośredniczącą i ręcznie ustawić wynik, jak określono poniżej,
// 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);
Kompletny kod do sprawdzenia, czy aplikacja kontaktowa jest poprawnie otwarta, wygląda następująco:
@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)));
}
Tutaj utworzyliśmy nową intencję i ustawiliśmy wartość zwracaną (podczas wywoływania intencji) jako pierwszy wpis na liście kontaktów, content: //com.android.contacts/data/1 . Następnie mamy ustawić zamierzają metodę Mock nowo utworzony intencji zamiast listy kontaktów. Ustawia i wywołuje naszą nowo utworzoną intencję, gdy wywoływany jest pakiet com.android.contacts i zwracany jest domyślny pierwszy wpis z listy. Następnie uruchomiliśmy akcję click (), aby rozpocząć pozorowaną intencję, a na koniec sprawdziliśmy, czy numer telefonu z przywołania fałszywej intencji i numer pierwszego wpisu na liście kontaktów są takie same.
Jeśli występuje brakujący problem z importem, napraw te problemy za pomocą opcji Alt + Enter dostarczonej przez studio Android lub dołącz poniższe instrukcje importu,
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.*;
Dodaj poniższą regułę w klasie testowej, aby zapewnić uprawnienia do czytania listy kontaktów -
@Rule
public GrantPermissionRule permissionRule =
GrantPermissionRule.grant(Manifest.permission.READ_CONTACTS);
Dodaj poniższą opcję w pliku manifestu aplikacji, AndroidManifest.xml -
<uses-permission android:name = "android.permission.READ_CONTACTS" />
Teraz upewnij się, że lista kontaktów ma co najmniej jeden wpis, a następnie uruchom test za pomocą menu kontekstowego Android Studio.