Espresso ile Android Uygulama Testi Kadir ŞEN kadirsen002@gmail.com www.yazilimcigenclik.com 1
İçindekiler Espresso Nedir?... 3 Espresso Paketleri :... 3 Espresso yu hazır hale getirme... 7 Android Studioda test yapılandırılması oluşturma... 10 Komut satırından Gradle yoluyla... 10 Birkaç temel Espresso komutu:... 10 Örnek : 1. Espresso Testi... 11 Espresso Testlerini çalıştırma:... 15 Sık kullanılan Android Studio metotları ve test önekleri.... 16 Genel olarak login metot tasarımı.... 16 ACTİON BAR UYGULAMA... 18 CUSTOM LİST TEST... 21 DATE TİME PİCKER TEST... 23 DİALOG TESTS(MESAJ EKRANI)... 25 ENTERNAME TEST... 27 SEARCH VİEW TEST... 29 SPİNNER SELECTİON TEST... 31 VİEW PAGER TEST... 33 2
Espresso Nedir? Esprossa, güvenilir kullanıcı arabirimi testleri yazmak için Android in bir test aracıdır. Google tarafından 2013 de piyasaya sürülmüştür. 2.0 sürümünden beri Android in test destek aracıdır. Espresso, test hareketlerinizi geliştirmiş olduğunuz uygulamanızın kullanıcı arabirimi ile otomatik olarak senkronizasyonunu gerçekleştirir. Espresso testleri Android 2.3.3 (API seviyesi 10) ve üstünü çalıştıran cihazlarda çalışabilir. Espresso Paketleri : Espresso-core - Çekirdek ve temel Görünüm eşleştiriciler, eylemler ve onaylamaları içerir. Espresso-web - WebView desteği için kaynakları içerir. Espresso-idling-resource - Espresso'nun arka plandaki işlerle senkronizasyon mekanizması. Espresso-contrib - DatePicker, RecyclerView ve Drawer eylemleri, Erişilebilirlik denetimleri ve CountingIdlingResource içeren dış katkı. Espresso-İntents- Hermetik testler için Nitelikleri doğrulamak ve saplamak için uzatma. Espresso nun üç temel bileşeni vardır. onview(viewmatcher) 1.perform(ViewAction) 2.check(ViewAssertion); 3 1. Test etmek istediğimiz görünümü bulur. 2. Görünüm üzerinde bir işlem gerçekleştirir. 3. Bir onaylamayı doğrular. Aşağıdaki kod, Espresso test çerçevesinin kullanımını göstermektedir. 3
4 KADİR ŞEN
View Matchers da kullanılan kod parçaları 5
View Actions ve View Assertions ile beraber kulanılacak kod parçaları. 6
Espresso yu hazır hale getirme Espresso nun kurulması Espresso yüklemek için Android SDK Yöneticisini kullanın. Espresso için Gradle yapı dosyasının yapılandırması Bunları uygulamanızın build.gradledosyasına ekleyin // Android JUnit Runner androidtestcompile 'com.android.support.test:runner:0.5' // JUnit4 Rules androidtestcompile 'com.android.support.test:rules:0.5' // Espresso core 'com.android.support.test.espresso:espresso- androidtestcompile core:2.2.2' // Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource 7
androidtestcompile 'com.android.support.test.espresso:espressocontrib:2.2.2' // Espresso-web for WebView support androidtestcompile 'com.android.support.test.espresso:espressoweb:2.2.2' // Espresso-idling-resource for synchronization with background jobs androidtestcompile 'com.android.support.test.espresso:espressoidling-resource:2.2.2' // Espresso-intents for validation and stubbing of Intents androidtestcompile 'com.android.support.test.espresso:espressointents:2.2.2' // UiAutomator androidtestcompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2' LICENCE.txt dosyasının yapılandırılması apply plugin: 'com.android.application' android { compilesdkversion 22 buildtoolsversion '22.0.1' defaultconfig { applicationid "com.example.android.testing.espresso.basicsample" minsdkversion 10 targetsdkversion 22 versioncode 1 versionname "1.0" testinstrumentationrunner "android.support.test.runner.androidjunitrunner" packagingoptions { exclude 'LICENSE.txt' lintoptions { abortonerror false 8
dependencies { // as before... Cihaz Ayarları 9
Android Studioda test yapılandırılması oluşturma In Android Studio: Open Run menu -> Edit Configurations Add a new Android Tests configuration Choose a module Add a specific instrumentation runner: android.support.test.runner.androidjunitrunner Yeni oluşturulan yapılandırmayı çalıştırın. Komut satırından Gradle yoluyla gerçekleştirmek./gradlew connectedandroidtest Kurulum işlemi tamamlanmıştır fakat Android SDK nızın tamamen güncel olması gerekmektedir hata almamak için. Birkaç temel Espresso komutu: ViewActions.click(): Clicks on the view. ViewActions.typeText(): Clicks on a view and enters a specified string. ViewActions.scrollTo(): Scrolls to the view. The target view must be subclassed from ScrollView and the value of its android:visibilityproperty must be VISIBLE. For views that extend AdapterView (for example, ListView), the ondata() method takes care of scrolling for you. ViewActions.pressKey(): Performs a key press using a specified keycode. ViewActions.clearText(): Clears the text in the target view. If the target view is inside a ScrollView, perform the ViewActions.scrollTo() action first to display the view in the screen before other proceeding with other actions. The ViewActions.scrollTo() action will have no effect if the view is already displayed. 10
Örnek : 1. Espresso Testi Öncelikle Espresso Firs adıyla bir Android projesi oluşturuyoruz. Bu projede boş şablonu temel alıyoruz. Oluşturulan activity_main.xlm dosyasını aşağıdaki gibi değiştiriyoruz. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <EditText android:id="@+id/inputfield" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/changetext" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="new Button" android:onclick="onclick"/> <Button android:id="@+id/switchactivity" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="change Text" android:onclick="onclick"/> </LinearLayout> İkinci işlem olarak Activity_second.xml adlı yeni bir dosya oluşturun. <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textappearance="?android:attr/textappearancelarge" android:text="large Text" 11
android:id="@+id/resultview" /> </LinearLayout> Üçüncü işlem olarak aşağıdaki kodla yeni bir etkinlik oluşturuyoruz. package com.vogella.android.espressofirst; import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.textview; public class SecondActivity extends Activity{ @Override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_second); TextView viewbyid = (TextView) findviewbyid(r.id.resultview); Bundle inputdata = getintent().getextras(); String input = inputdata.getstring("input"); viewbyid.settext(input); Dördüncü adım olarak MainActivitiy sınıfımızı düzenliyoruz. package com.vogella.android.espressofirst; import android.app.activity; import android.content.intent; import android.os.bundle; import android.view.view; import android.widget.edittext; public class MainActivity extends Activity { EditText edittext; @Override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); 12
edittext = (EditText) findviewbyid(r.id.inputfield); public void onclick(view view) { switch (view.getid()) { case R.id.changeText: edittext.settext("lalala"); break; case R.id.switchActivity: Intent intent = new Intent(this, SecondActivity.class); intent.putextra("input", edittext.gettext().tostring()); startactivity(intent); break; Beşinci adım olarak Espresso Testinizi oluşturun. package com.vogella.android.espressofirst; import android.support.test.rule.activitytestrule; import android.support.test.runner.androidjunit4; import org.junit.rule; import org.junit.test; import org.junit.runner.runwith; import static android.support.test.espresso.espresso.onview; import static android.support.test.espresso.action.viewactions.click; import static android.support.test.espresso.action.viewactions.closesoftkeyb oard; import static android.support.test.espresso.action.viewactions.typetext; import static android.support.test.espresso.assertion.viewassertions.matches ; import static android.support.test.espresso.matcher.viewmatchers.withid; 13
import static android.support.test.espresso.matcher.viewmatchers.withtext; @RunWith(AndroidJUnit4.class) public class MainActivityEspressoTest { @Rule public ActivityTestRule<MainActivity> mactivityrule = new ActivityTestRule<>(MainActivity.class); @Test public void ensuretextchangeswork() { // Type text and then press the button. onview(withid(r.id.inputfield)).perform(typetext("hello"), closesoftkeyboard()); onview(withid(r.id.changetext)).perform(click()); // Check that the text was changed. onview(withid(r.id.inputfield)).check(matches(withtext("lalala "))); @Test public void changetext_newactivity() { // Type text and then press the button. onview(withid(r.id.inputfield)).perform(typetext("newtext"), closesoftkeyboard()); onview(withid(r.id.switchactivity)).perform(click()); // This view is in a different Activity, no need to tell Espresso. onview(withid(r.id.resultview)).check(matches(withtext("newtex t"))); Son olarak bütün ayarlamalarımız bitmiştir. Geldik testimizi çalıştırmaya 14
Espresso Testlerini çalıştırma: 15
Sık kullanılan Android Studio metotları ve test önekleri. Genel olarak login metot tasarımı. public class LoginActivity extends AppCompatActivity implements View.OnClickListener { private EditText username; private EditText password; @Override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_login); username = (EditText) findviewbyid(r.id.username); password = (EditText) findviewbyid(r.id.password); findviewbyid(r.id.login).setonclicklistener(this); @Override public void onclick(view v) { if (v.getid() == R.id.login) { Intent intent = new Intent(this, MainActivity.class); intent.putextra("name", username.gettext().tostring().trim()); intent.putextra("pwd", password.gettext().tostring().trim()); startactivity(intent); Tasarlamış olduğumuz Login ekranının TEST işlemi @RunWith(AndroidJUnit4.class) public class LoginActivityTest { @Rule public ActivityTestRule<LoginActivity> mactivityrule = new ActivityTestRule<>( LoginActivity.class); @Test public void loginwithwrongpassword() {//login test onview(withid(r.id.username)).perform(typetext("android"), closesoftkeyboard()); onview(withid(r.id.password)).perform(typetext("wrong"), closesoftkeyboard()); onview(withid(r.id.login)).perform(click()); onview(withid(r.id.tvloginresult)).check(matches(withtext("kadir"))); @Test public void loginwithrightpassword() {//login test 16
onview(withid(r.id.username)).perform(typetext("android1"), closesoftkeyboard()); onview(withid(r.id.password)).perform(typetext("123456"), closesoftkeyboard()); onview(withid(r.id.login)).perform(click()); onview(withid(r.id.tvloginresult)).check(matches(withtext("kadirşen"))); 17
ACTİON BAR UYGULAMA blic ActionBarExampleTest() { super(actionbarexampleactivity.class); @Override protected void setup() throws Exception { super.setup(); getactivity(); public void testclickonmenuitem() { // Click on an item from ActionBar onview(withid(r.id.action_settings)).perform(click()); // Verify the correct item was clicked by checking the content of the status TextView onview(withid(r.id.status)).check(matches(withtext("settings"))); public void testoverflowmenuoroptionsmenu() { // Open the action bar overflow or options menu (depending if the device has or not a hardware menu button.) openactionbaroverfloworoptionsmenu(getinstrumentation().getcontext()); 18
// Find the menu item with text "About" and click on it onview(withtext("about")).perform(click()); // Verify the correct item was clicked by checking the content of the status TextView onview(withid(r.id.status)).check(matches(withtext("about"))); public void testactionmode() { // Show the contextual ActionBar onview(withid(r.id.toggle_action_mode)).perform(click()); // Click on a context item onview(withid(r.id.action_one)).perform(click()); // Verify the correct item was clicked by checking the content of the status TextView onview(withid(r.id.status)).check(matches(withtext("actionmode1"))); Action Bar Test İşlemi public class ActionBarExampleTest extends ActivityInstrumentationTestCase2<ActionBarExampleActivity> { public ActionBarExampleTest() { super(actionbarexampleactivity.class); @Override protected void setup() throws Exception { super.setup(); getactivity(); public void testclickonmenuitem() { // Click on an item from ActionBar onview(withid(r.id.action_settings)).perform(click()); // Verify the correct item was clicked by checking the content of the status TextView onview(withid(r.id.status)).check(matches(withtext("settings"))); public void testoverflowmenuoroptionsmenu() { // Open the action bar overflow or options menu (depending if the device has or not a hardware menu button.) openactionbaroverfloworoptionsmenu(getinstrumentation().getcontext()); // Find the menu item with text "About" and click on it onview(withtext("about")).perform(click()); // Verify the correct item was clicked by checking the content of the status TextView onview(withid(r.id.status)).check(matches(withtext("about"))); 19
public void testactionmode() { // Show the contextual ActionBar onview(withid(r.id.toggle_action_mode)).perform(click()); // Click on a context item onview(withid(r.id.action_one)).perform(click()); // Verify the correct item was clicked by checking the content of the status TextView onview(withid(r.id.status)).check(matches(withtext("actionmode1"))); 20
CUSTOM LİST TEST public class CustomListTest extends ActivityInstrumentationTestCase2<CustomListActivity> { private static final String BOOK_TITLE = "Java Concurrency in Practice"; private static final String BOOK_AUTHOR = "Brian Goetz"; public CustomListTest() { super(customlistactivity.class); @Override protected void setup() throws Exception { super.setup(); getactivity(); public void testopenbookbyid() { // Click on the Book with ID 5 ondata(withbookid(5)).perform(click()); // Check the correct book title is displayed onview(withid(r.id.book_title)).check(matches(withtext(book_title))); // Check the correct author is displayed 21
onview(withid(r.id.book_author)).check(matches(withtext(book_author))); public void testopenbookbytitleandauthor() { // Match a book with a specific title and author name ondata(allof(withbooktitle(book_title), withbookauthor(book_author))).perform(click()); // Check the correct book title is displayed onview(withid(r.id.book_title)).check(matches(withtext(book_title))); // Check the correct author is displayed onview(withid(r.id.book_author)).check(matches(withtext(book_author))); public void testclickonbookbyposition(){ ondata(anything()).atposition(5).perform(click()); onview(withid(r.id.book_title)).check(matches(withtext(book_title))); onview(withid(r.id.book_author)).check(matches(withtext(book_author))); 22
DATE TİME PİCKER TEST public class DateTimePickerTest extends ActivityInstrumentationTestCase2<DateTimePickerActivity> { public DateTimePickerTest() { super(datetimepickeractivity.class); @Override protected void setup() throws Exception { super.setup(); getactivity(); public void testsetdate() { int year = 2020; int month = 11; int day = 15; onview(withid(r.id.date_picker_button)).perform(click()); onview(withclassname(matchers.equalto(datepicker.class.getname()))).perform (PickerActions.setDate(year, month + 1, day)); onview(withid(android.r.id.button1)).perform(click()); 23
onview(withid(r.id.status)).check(matches(withtext(year + "/" + month + "/" + day))); public void testsettime() { int hour = 10; int minutes = 59; onview(withid(r.id.time_picker_button)).perform(click()); onview(withclassname(matchers.equalto(timepicker.class.getname()))).perform (PickerActions.setTime(hour, minutes)); onview(withid(android.r.id.button1)).perform(click()); onview(withid(r.id.status)).check(matches(withtext(hour + ":" + minutes))); 24
DİALOG TESTS(MESAJ EKRANI) public class DialogTests extends ActivityInstrumentationTestCase2<DialogExampleActivity> { public DialogTests() { super(dialogexampleactivity.class); @Override protected void setup() throws Exception { super.setup(); getactivity(); public void testcheckdialogdisplayed() { // Click on the button that shows the dialog onview(withid(r.id.confirm_dialog_button)).perform(click()); // Check the dialog title text is displayed onview(withtext(r.string.dialog_title)).check(matches(isdisplayed())); public void testclickokbutton() { onview(withid(r.id.confirm_dialog_button)).perform(click()); // android.r.id.button1 = positive button 25
onview(withid(android.r.id.button1)).perform(click()); onview(withid(r.id.status_text)).check(matches(withtext(r.string.ok))); public void testclickcancelbutton() { onview(withid(r.id.confirm_dialog_button)).perform(click()); // android.r.id.button2 = negative button onview(withid(android.r.id.button2)).perform(click()); onview(withid(r.id.status_text)).check(matches(withtext(r.string.cancel))); 26
ENTERNAME TEST public class EnterNameTest extends ActivityInstrumentationTestCase2<EnterNameActivity> { public static final String USER_NAME = "John"; public static final String GREETING_MESSAGE = "Hello " + USER_NAME + "!"; public EnterNameTest() { super(enternameactivity.class); @Override protected void setup() throws Exception { super.setup(); getactivity(); public void testhintdisplayed() { onview(withid(r.id.name_edittext)).check(matches(withhint(r.string.enter_na me))); 27
public void testerrormessagedisplayed() { // Making sure the error message is not displayed by default onview(withid(r.id.error_text)).check(matches(not(isdisplayed()))); // Click on "Next" button onview(withid(r.id.next_button)).perform(click()); // Now check the error message is displayed onview(withid(r.id.error_text)).check(matches(isdisplayed())); public void testgreetingmessagewithnamedisplayed() { onview(withid(r.id.name_edittext)).perform(typetext(user_name)); onview(withid(r.id.next_button)).perform(click()); onview(withid(r.id.greeting_message)).check(matches(withtext(greeting_messa GE))); 28
SEARCH VİEW TEST public class SearchViewTest extends ActivityInstrumentationTestCase2<SearchViewActivity> { public static final String HELSINKI = "Helsinki"; public SearchViewTest() { super(searchviewactivity.class); @Override protected void setup() throws Exception { super.setup(); getactivity(); public void testitemnotfound() { // Click on the search icon onview(withid(r.id.action_search)).perform(click()); // Type the text in the search field and submit the query onview(isassignablefrom(edittext.class)).perform(typetext("no such item"), pressimeactionbutton()); // Check the empty view is displayed onview(withid(r.id.empty_view)).check(matches(isdisplayed())); public void testitemfound() { onview(withid(r.id.action_search)).perform(click()); 29
onview(isassignablefrom(edittext.class)).perform(typetext(helsinki), pressimeactionbutton()); // Check empty view is not displayed onview(withid(r.id.empty_view)).check(matches(not(isdisplayed()))); // Check the item we are looking for is in the search result list. ondata(allof(is(instanceof(string.class)), withitemcontent(helsinki))).check(matches(isdisplayed())); public void testsearchsuggestiondisplayed() { onview(withid(r.id.action_search)).perform(click()); onview(isassignablefrom(edittext.class)).perform(typetext(helsinki), pressimeactionbutton()); // Go back to previous screen pressback(); // Clear the text in search field onview(isassignablefrom(edittext.class)).perform(cleartext()); // Enter the first letter of the previously searched word onview(isassignablefrom(edittext.class)).perform(typetext("he")); // Check the search suggestions appear onview(withtext(helsinki)).inroot(withdecorview(not(matchers.is(getactivity().getwindow().getdecorview()) ))).check(matches(isdisplayed())); public void testclickonsearchsuggestion() { onview(withid(r.id.action_search)).perform(click()); onview(isassignablefrom(edittext.class)).perform(typetext(helsinki), pressimeactionbutton()); // Go back to previous screen pressback(); // Clear the text in search field onview(isassignablefrom(edittext.class)).perform(cleartext()); // Enter the first letter of the previously searched word onview(isassignablefrom(edittext.class)).perform(typetext("he")); // Click on the "Java" item from the suggestions list onview(withtext(helsinki)).inroot(withdecorview(not(matchers.is(getactivity().getwindow().getdecorview()) ))).perform(click()); // Check the item appears in search results list. ondata(allof(is(instanceof(string.class)), withitemcontent(helsinki))).check(matches(isdisplayed())); 30
SPİNNER SELECTİON TEST public class SpinnerSelectionTest extends ActivityInstrumentationTestCase2<SpinnerSelectionActivity> { public static final String INVALID_COUNTRY_NAME = "NoSuchCountry"; public static final String VALID_COUNTRY_NAME = "Moldova"; public static final String FIRST_ITEM_TEXT = "Select your country"; public SpinnerSelectionTest() { super(spinnerselectionactivity.class); @Override protected void setup() throws Exception { super.setup(); getactivity(); public void testcountrynotinlist() { onview(withid(r.id.countries_spinner)).check(matches(not(withadapteddata(wi thitemcontent(invalid_country_name))))); public void testlabeldoesnotchangeiffirstitemselected() { // Click on the Spinner 31
onview(withid(r.id.countries_spinner)).perform(click()); // Click on the first item from the list, which is a marker string: "Select your country" ondata(allof(is(instanceof(string.class)))).atposition(0).perform(click()); // Check that the country label is not updated. onview(withid(r.id.country_label)).check(matches(not(withtext(first_item_te XT)))); public void testlabelupdatesifvalidcountryselected() { // Click on the Spinner onview(withid(r.id.countries_spinner)).perform(click()); // Select a country from the list ondata(allof(is(instanceof(string.class)), is(valid_country_name))).perform(click()); // Check that the country label is updated with selected country onview(withid(r.id.country_label)).check(matches(withtext(valid_country_nam E))); 32
VİEW PAGER TEST public class ViewPagerTest extends ActivityInstrumentationTestCase2<ViewPagerActivity> { private static final String BOOK_TITLE = "Clean Code"; private static final String BOOK_AUTHOR = "Robert C. Martin"; public ViewPagerTest() { super(viewpageractivity.class); @Override protected void setup() throws Exception { super.setup(); getactivity(); public void testalltabdisplayedonswipe() { // Locate the ViewPager and perform a swipe left action onview(withid(r.id.pager)).perform(swipeleft()); // Check the "ALL BOOKS" text is displayed onview(allof(withid(r.id.header_text), isdisplayed())).check(matches(withtext("all BOOKS"))); public void testclickonbookfromnewtab() { // The below commented out line will fail with 33
AmbiguousViewMatcherException because the same ListView is used in both pages of ViewPager. // ondata(allof(withbooktitle(book_title), withbookauthor(book_author))).perform(click()); // We have to refine the query specifying that we are looking for an AdapterView that is currently visible. ondata(allof(withbooktitle(book_title), withbookauthor(book_author))).inadapterview(allof(isassignablefrom(adapterview.class), isdisplayed())).perform(click()); // Check the correct book title is displayed onview(withid(r.id.book_title)).check(matches(withtext(book_title))); // Check the correct author is displayed onview(withid(r.id.book_author)).check(matches(withtext(book_author))); 34