کانال بله, جهت پشتیبانی و اطلاع رسانی کانال بله, جهت پشتیبانی و اطلاع رسانی
عضویت

تست لایه ی UI اپلیکیشن های اندرویدی به واسطه ی فریم ورک Espresso

فریم ورک تست گیری Espresso و تست لایه ی UI اپلیکیشن

Espresso یک فریم ورک جهت آسان سازی تعبیه تست های نرم افزاری قابل اطمینان برای لایه ی رابط کاربری پروژه های اندرویدی می باشد. شرکت گوگل این فریم ورک را برای اولین بار در اکتبر سال 2013 معرفی کرد.

Espresso از ویرایش 2.0 بخشی از Android Support Repository محسوب می شود.

این فریم ورک تست گیری به صورت خودکار عملیات تست گیری شما را با لایه ی UI اپلیکیشن هماهنگ می سازد.

Espresso اطمینان حاصل می کند که قبل از به اجرا در آمدن تست، activity مورد نظر حتما راه اندازی شده باشد. از دیگر ویژگی های جالب توجه فریم ورک مذکور این است که منتظر می ماند تا تمامی background activity ها پایان یابند و سپس خود به اجرا در می آید.

Espresso در اصل برای تست قابلیت های یک اپلیکیشن (اطمینان از عملکرد صحیح اپلیکیشن در سطح UI یک اپلیکیشن) در آن واحد طراحی شده، اما می توان از آن برای تست تعامل اپلیکیشن ها با هم نیز بهره گرفت. چنانچه قصد تست تعامل بین اپلیکیشن ها را دارید (نه صرفا بررسی صحت عملیاتی که در سطح UI یک اپلیکیشن رخ می دهد)، لازم است از تکنیک تست گیری black box استفاده نمایید (تکنیک تست گیری black box به روشی در تست نرم‌افزار اشاره دارد که در آن فرض می‌شود اطلاعاتی در مورد جزئیات داخلی عملکرد اپلیکیشن مورد نظر وجود ندارد و تمرکز تست‌ها منحصرا بر روی خروجی‌های مختلف در مقابل ورودی‌های متفاوت است).

Espresso در کل از سه کامپوننت نرم افزاری زیر تشکیل شده است:

  • ViewMatchers - به شما امکان می دهد تا view مورد نظر را در نمودار درختی view ها / view hierarchy جاری پیدا کنید.
  • ViewActions - اجازه می دهد تا عملیاتی را بر روی view ها انجام دهید.
  • ViewAssertions - به توسعه دهنده این امکان را می دهد تا جهت اطمینان از صحت اطلاعات مربوط به وضعیت view / view state مورد نظر (برآورده شدن انتظار خاصی در رابطه با وضعیت view)، view را تست کند.

یک تست ساده ی Espresso

ساختار کلی تست های Espresso به صورت زیر می باشد:

onView(ViewMatcher)       
 .perform(ViewAction)     
   .check(ViewAssertion); 

این تست : 1) view را پیدا می کند 2) عملیاتی بر روی view اجرا می نماید 3) با بررسی وضعیت view پی می برد آیا اطلاعات و وضعیت view با آنچه انتظار می رفت مطابقت دارد یا خیر.

کد زیر استفاده ی کاربردی از Espresso را به نمایش می دهد.

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
// image more code here...
// test statement
onView(withId(R.id.my_view))            // withId(R.id.my_view) is a ViewMatcher
        .perform(click())               // click() is a ViewAction
        .check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
// new test
onView(withId(R.id.greet_button))
.perform(click())
.check(matches(not(isEnabled()));

چنانچه Espresso با فراخوانی ViewMatcher قادر به یافتن view مورد نظر نبود، در آن صورت کل ساختار درختی view را در پیغام خطا می گنجاند. این امر برای تجزیه و تحلیل و برطرف نمودن مشکل مفید می باشد.

نصب و استفاده از Espresso

نصب

ابتدا Android Support Repository را از طریق Android SDK manager نصب نمایید.

نصب Espresso

تنظیم فایل Gradle build برای استفاده از توابع Espresso

جهت استفاده از امکانات Espresso در تست های نرم افزاری خود، کتابخانه ی زیر را به فایل Gradle build اپلیکیشن اضافه نمایید.

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    // Android runner and rules support
    androidTestCompile 'com.android.support.test:runner:0.5'
    androidTestCompile 'com.android.support.test:rules:0.5'
    // Espresso support
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    // add this for intent mocking support
    androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'
    // add this for webview testing support
    androidTestCompile 'com.android.support.test.espresso:espresso-web:2.2.2'
}

لازم است android.support.test.runner.AndroidJUnitRunner را به عنوان مقدار پارامتر testInstrumentationRunner داخل فایل build اپلیکیشن خود ذکر نمایید. شاید لازم باشد با توجه به کتابخانه ی مورد استفاده، مقدار LICENSE.txt را از packagingOptions حذف نمایید. کد زیر مثالی در این زمینه را نشان می دهد.

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
    }
}
dependencies {
    // as before.......
}

تنظیمات دستگاه (Device settings)

اگر می خواهید از Espresso جهت تست لایه ی UI اپلیکیشن استفاده کنید، در آن صورت توصیه می شود انیمیشن های سیستمی را غیرفعال نموده، بدین وسیله از رخداد خطا یا از کار افتادگی ناگهانی نرم افزار جلوگیری کنید و نیز مطمئن شوید که خروجی تست ثابت و تکرار پذیر می باشد.

تنظیمات دستگاه تمرین: استفاده ی کاربردی از Espresso جهت تست پروژه

ایجاد پروژه ی و تست آن توسط Espresso

یک پروژه ی جدید اندرویدی به نام Espresso First با اسم پکیج com.vogella.android.espressofirst ایجاد نمایید. این پروژه را بر اساس قالب آماده ی Blank Template ایجاد نمایید.

محتوای فایل layout پروژه ی خود، activity_main.xml را به صورت زیر ویرایش نمایید.






یک فایل جدید به نام activity_second.xml ایجاد نمایید.





یک activity جدید با محتوای زیر ایجاد نمایید.

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

سپس بدنه ی کلاس MainActivity خود را نیز به صورت زیر ویرایش نمایید.

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

تنظیم و ویرایش فایل app build.gradle

تنظیمات را طبق توضیحات مرتبط با تنظیم فایل Gradle build برای استفاده از Espresso در مقاله ی حاضر انجام دهید.

تست نویسی برای پروژه بر اساس فریم ورک Espresso

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.closeSoftKeyboard;
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;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class MainActivityEspressoTest {
    @Rule
    public ActivityTestRule 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("NewText")));
    }
}

ساخت و تنظیم start intent (intent اجرا و راه اندازی activity دوم)

پس از ارسال مقدار false به عنوان پارامتر سوم به متد ActivityTestRule، می توانید به ساخت و تنظیم آبجکت intent برای فراخوانی و اجرای activity دوم بپردازید. این عملیات در نمونه کد زیر به نمایش گذاشته شده است.

package com.vogella.android.testing.espressosamples;
import android.content.Intent;
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.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class SecondActivityTest {
    @Rule
    // third parameter is set to false which means the activity is not started automatically
    public ActivityTestRule rule =
            new ActivityTestRule(SecondActivity.class, true, false);
    @Test
    public void demonstrateIntentPrep() {
        Intent intent = new Intent();
        intent.putExtra("EXTRA", "Test");
        rule.launchActivity(intent);
  onView(withId(R.id.display)).check(matches(withText("Test")));
    }
}

ضبط تعامل با UI اپلیکیشن و جهت اجرای تست Espresso (Espresso UI recorder)

محیط برنامه نویسی Android Studio در منوی Run خود آیتمی به نام Record Espresso Test دارد (جهت دسترسی به آن این مسیر را طی نمایید: Run ▸ Record Espresso Test) که با فعال شدن، تعامل کاربر با UI اپلیکیشن را ضبط کرده و از روی آن یک تست Espresso ایجاد می کند.

اجرای تست Espresso اجرای تست Espresso

تنظیم activity مورد تست

می توانید به آبجکت activity مورد تست دسترسی داشته و توابع دلخواه را بر روی آن فراخوانی نمایید. فرض کنید می خواهید یک متد را به صورت زیر بر روی نمونه ای از کلاس activity خود صدا بزنید.

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public void configureMainActivity(String Uri) {
        // do something with this
    }
}

می توانید متد ConfigureMainActivity را به صورت زیر بر روی نمونه ای از کلاس activity خود (MainActivity) فراخوانی نمایید.

package com.vogella.android.myapplication;
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;
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Rule
    public ActivityTestRule mActivityRule =
            new ActivityTestRule(MainActivity.class);
    @Test
    public void useAppContext() throws Exception {
        MainActivity activity = mActivityRule.getActivity();
        activity.configureMainActivity("http://www.vogella.com");
        // do more
    }
}
نکته :

همچنین می توانید متدهای مورد نظر را در ActivityTestRule بازنویسی (override) نمایید. برای مثال، بدنه ی توابع beforeActivityLaunched و afterActivityLaunched را بازنویسی نمایید.

می توانید به activity جاری نیز دسترسی داشته باشید.

@Test
public void navigate() {
        Activity instance = getActivityInstance();
        onView(withText("Next")).perform(click());
        Activity activity = getActivityInstance();
        boolean b = (activity instanceof  SecondActivity);
        assertTrue(b);
        // do more
    }
    public Activity getActivityInstance() {  			1)
        final Activity[] activity = new Activity[1];
        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable( ) {
            public void run() {
                Activity currentActivity = null;
                Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED);
                if (resumedActivities.iterator().hasNext()){
                    currentActivity = (Activity) resumedActivities.iterator().next();
                    activity[0] = currentActivity;
                }
            }
        });
        return activity[0];
    }

امکان دسترسی به activity جاری را فراهم می سازد.

توجه :

لازم به ذکر است که دستور ActivityLifecycleMonitorRegistry جزء توابع کتابخانه ای / API نیست و به همین جهت امکان تغییر آن در آینده وجود دارد.

اجرای تست های Espresso

اجرای تست در محیط کاری Android Studio

بر روی تست راست کلیک کرده و سپس گزینه ی Run را انتخاب نمایید.

اجرای تست های Espresso

استفاده از سیستم کامپایل Gradle

بر روی آیکون کوچک Gradle در نوار کناری سمت راست محیط کلیک نموده و سپس گزینه ی connectedCheck را انتخاب نمایید تا تست به طور مستقیم از Gradle اجرا شود.

کامپایل Gradle

بررسی صحت نمایش پیغام toast

در زیر مثالی را مشاهده می کنید که در آن بررسی می شود آیا پس از کلیک بر روی آیتمی از لیست یک پیغام toast در نمایشگر ارائه می شود یا خیر.

package com.vogella.android.test.juntexamples;
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.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
@RunWith(AndroidJUnit4.class)
public class MainActivityTestList {
    @Rule
   public ActivityTestRule rule  = new  ActivityTestRule<>(MainActivity.class);
    @Test
    public void ensureListViewIsPresent() throws Exception {
        onData(hasToString(containsString("Frodo"))).perform(click());
        onView(withText(startsWith("Clicked:"))).
        inRoot(withDecorView(
                not(is(rule.getActivity().
                getWindow().getDecorView())))).
                check(matches(isDisplayed()));
    }
}

شبیه سازی آبجکت های Intent با توابع کتابخانه ای Espresso

Espresso این اجازه را به توسعه دهنده می دهد تا آّبجکت های Intent را شبیه سازی / mock کند (mock = تعریف یک آبجکت intent ساختگی که رفتار آبجکت واقعی intent را شبیه سازی می کند). به واسطه ی این امکان توسعه دهنده قادر خواهد بود بررسی کند آیا کلاس activity آبجکت های intent مد نظر را صادر می کند یا خیر و نیز اینکه پس از دریافت خروجی مرتبط intent ، واکنش داخواه را نشان می دهد یا خیر.

فریم ورک تست گیری Espresso، این intent های ساختگی را در قالب کتابخانه ای به نام com.android.support.test.espresso:espresso-intents در اختیار توسعه دهنده قرار می دهد. لازم است تنظیمات لازم را نیز در فایل Gradle build اعمال نمایید (جهت دریافت اطلاعات کامل به بخش مربوطه در مقاله ی حاضر مراجعه فرمایید).

اگر می خواهید از intent های ساختگی Espresso در تست های نرم افزاری خود استفاده کنید، لازم است بجای ActivityTestRule ، از IntentTestRule استفاده نمایید.

package testing.android.vogella.com.simpleactivity;
import android.support.test.espresso.intent.rule.IntentsTestRule;
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.intent.Intents.intended;
import static android.support.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
@RunWith(AndroidJUnit4.class)
public class TestIntent {
    @Rule
    public IntentsTestRule mActivityRule =
            new IntentsTestRule<>(MainActivity.class);
    @Test
    public void triggerIntentTest() {
        onView(withId(R.id.button)).perform(click());
        intended(toPackage("testing.android.vogella.com.simpleactivity"));
    }
{

نوشتن تست برای بررسی عملکرد intent

ایجاد پروژه ی آزمایشی (project under test)

یک پروژه ی جدید اندروید با اسم پکیج testing.android.vogella.com.simpleactivity بر اساس قالب آماده ی Empty Activity ایجاد نمایید.

حال با طی نمودن این مسیر: File ▸ New ▸ Activity ▸ Empty Activity، یک activity جدید به نام SecondActivity به پروژه ی خود اضافه نمایید. این activity بایستی جهت تنظیم ظاهر خود یک فایل layout با حداقل یک المان TextView را فراخوانی نماید. شناسه (id) المان رابط کاربری TextView باید "resultText" و مقدار متنی آن بایستی بر روی "Started" تنظیم شده باشد.

یک فیلد EditText به فایل layout که ظاهر کلاس MainActivity را در UI تنظیم می کند، اضافه نمایید.

اکنون یک دکمه به فایل layout مورد استفاده ی MainActivity اضافه نمایید. پس از کلیک بر روی دکمه، activity دوم بایستی فراخوانی و اجرا شود.

فیلد EditText را به واسطه ی فراخوانی دستور intent.putExtra(); ، به عنوان کلید داخل آبجکت Intent قرار دهید. سپس String/مقدار رشته ای http://www.vogella.com را نیز با توجه به کلید "URL" و به وسیله ی پل ارتباطی extra (با ارسال آن به عنوان پارامتر اول به دستور intent.putExtra()) داخل آّبجکت intent قرار دهید.

در زیر پیاده سازی نمونه از کلاس MainActivity را مشاهده می کنید.

package testing.android.vogella.com.simpleactivity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public void onClick(View view) {
        Intent intent = new Intent(this, SecondActivity.class);
        intent.putExtra("URL", "http://www.vogella.com");
        startActivity(intent);
    }
}

نوشتن تست

حال یک تست مبتنی بر فریم ورک Espresso بنویسید که موارد زیر را آزمایش کرده و از عملکرد صحیح اطمینان حاصل می نماید:

  • بررسی کند آیا کلاس MainActivity یک المان دکمه با اسم یا شناسه ی R.id.button را دارد یا خیر.
  • مطمئن شود مقدار نمایش داده شده بر روی دکمه رشته ی "Start new activity" می باشد.
  • بررسی صحت فراخوانی متد getActivity.onClick() و اینکه آیا intent صحیح فراخوانی شده است یا خیر. این intent بایستی دستور String extra (hasExtra("URL", "http://www.vogella.com") را در خود محصور داشته باشد.

بررسی صحت تست (اعتبارسنجی)

کد آزمایشی شما بایستی محتوایی مشابه زیر داشته باشد.

package testing.android.vogella.com.simpleactivity;
import android.support.test.espresso.intent.rule.IntentsTestRule;
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.assertion.ViewAssertions.matches;
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra;
import static android.support.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.IsNull.notNullValue;
@RunWith(AndroidJUnit4.class)
public class TestIntent {
    @Rule
    public IntentsTestRule mActivityRule = new IntentsTestRule<>(MainActivity.class);
    @Test
    public void triggerIntentTest() {
        // check that the button is there
        onView(withId(R.id.button)).check(matches(notNullValue() ));
        onView(withId(R.id.button)).check(matches(withText("Start new activity")));
        onView(withId(R.id.button)).perform(click());
        intended(toPackage("testing.android.vogella.com.simpleactivity"));
        intended(hasExtra("URL", "http://www.vogella.com"));
    }
}
تمرین: طراحی تست های functional (تست بخش های برنامه به صورت ایزوله) برای بررسی عملکرد صحیح activity

هدف از این تمرین

در تمرین حاضر، یک activity دیگر را فراخوانی کرده و اطمینان حاصل می کنید که activity مورد نظر به درستی اجرا می شود.

طراحی تست های functional برای activity ها

package testing.android.vogella.com.simpleactivity;
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.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class TestSecondActivityIsStarted {
    @Rule
    public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class);
    @Test
    public void validateSecondActivity() {
        // check that the button is there
        onView(withId(R.id.button)).perform(click());
        onView(withId(R.id.resultText))
                .check(matches(withText(("Started"))));
    }
}

برای بررسی صحت اطمینان ویرایش مسقیم view، لازم است کلاس تست گیری زیر را برای آزمایش عملکرد کلاس SecondActivity ایجاد نمایید.

package testing.android.vogella.com.simpleactivity;
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.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class SecondActivityFunctionalTest {
    @Rule
    public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class);
    @Test
    public void validateSecondActivity() {
        // check that the button is there
        onView(withId(R.id.button)).perform(click());
        onView(withId(R.id.resultText))
                .check(matches(withText(("Started"))));
        pressBack();
        onView(withId(R.id.button))
                .check(matches(withText(("Start new activity"))));
    }
}

آزمایش عملکرد کد ناهزمان با استفاده از فریم ورک تست گیری Espresso

تست عملکرد کد ناهمزمان بدون کمک فریم ورک، امری بسیار پیچیده و ملال آور است. قبل از عرضه ی این فریم ورک، توسعه دهنده مجبور می شد مدت زمان مشخصی را جهت تست کد ناهمزمان صبر نماید و یا به واسطه ی استفاده از نمونه ی کلاس CountDownLatch در test code (کد آزمایشی)، از پردازش ناهمزمان اعلان نماید که پروسه به پایان رسیده است. از آنجایی که Espresso به صورت خودکار بر روی thread pool (انباره ای که thread ها در آن قرار دارند و تسک هایی که اغلب در قالب یک queue یا صف سازمان دهی شده اند را انجام می دهند) مربوط به AsynchronousTask نظارت دارد، این کار بسیار آسان می شود (کلاس AsyncTask یک کلاس abstract در چارچوب نرام افزاری اندروید است که تسک های زمان بر را در پس زمینه اجرا کرده و نتیجه را بدون اینکه بر روی thread اصلی تاثیر داشته باشد، در آن (UI thread) به صورت بهینه نمایش دهد). علاوه بر آن، Espresso صفی که event های مربوط به UI اپلیکیشن در آن قرار گرفته اند را رصد کرده و سپس تنها زمانی اجرای تست را از سرمی گیرد که هیچ تسکی در حال اجرا نباشد.

در صورت استفاده از منابع متفرقه همچون IntentService، بایستی اینترفیس IdlingResource را پیاده سازی نمایید. پیاده سازی interface مذکور بایستی بر این resource نظارت داشته و آن را برای فریم ورک Espresso معرفی نماید (IntentService یک کلاس پایه است که برای پیاده سازی کامپوننت سرویس بایستی از آن ارث بری نمایید و وظیفه ی آن مدیریت درخواست های (ارائه شده در قالب آبجکت های intent) ناهمزمان می باشد(.

(IdlingResource نشانگر یکی از منابع اپلیکیشن مورد تست است که امکانی را فراهم می کند تا همزمان با اجرای تست، عملیات پس زمینه ای ناهمزمان اجرا شود. برای مثال می توان به intent resource اشاره کرد که کلیک بر روی یک دکمه را پردازش می کند. به صورت پیش فرض، فریم ورک Espresso تمامی عملیات مربوط به view را علاوه بر AsyncTasks با UI Thread هماهنگ می سازد. اما، برای منابع hand-made هیچ راهی برای انجام این کار در اختیار ندارد. در چنین سناریوی، طراح تست می تواند منابع اختصاصی را به Espresso معرفی کند. پس از آن، Espresso صبر می کند تا resource مربوطه بی کار شود و بعد عملیات view را اجرا می نماید).

package com.vogella.android.espressointentservice;
import android.app.ActivityManager;
import android.content.Context;
import android.support.test.espresso.IdlingResource;
import java.util.List;
public class IntentServiceIdlingResource implements IdlingResource {
    ResourceCallback resourceCallback;
    private Context context;
    public IntentServiceIdlingResource(Context context) {
        this.context = context;
    }
    @Override
    public String getName() {
        return IntentServiceIdlingResource.class.getName();
    }
    @Override
    public void registerIdleTransitionCallback(
            ResourceCallback resourceCallback) {
        this.resourceCallback = resourceCallback;
    }
    @Override
    public boolean isIdleNow() {
        boolean idle = !isIntentServiceRunning();
        if (idle && resourceCallback != null) {
            resourceCallback.onTransitionToIdle();
        }
        return idle;
    }
    private boolean isIntentServiceRunning() {
        ActivityManager manager =
                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        // Get all running services
        List runningServices =
                manager.getRunningServices(Integer.MAX_VALUE);
        // check if our is running
        for (ActivityManager.RunningServiceInfo info : runningServices) {
            if (MyIntentService.class.getName().equals(
                    info.service.getClassName())) {
                return true;
            }
        }
        return false;
    }
}
package com.vogella.android.espressointentservice;
import android.app.Instrumentation;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.Espresso;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
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.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.notNullValue;
@RunWith(AndroidJUnit4.class)
public class IntegrationTest {
    @Rule
    public ActivityTestRule rule = new ActivityTestRule(MainActivity.class);
    IntentServiceIdlingResource idlingResource;
    @Before
    public void before() {
        Instrumentation instrumentation
                = InstrumentationRegistry.getInstrumentation();
        Context ctx = instrumentation.getTargetContext();
        idlingResource = new IntentServiceIdlingResource(ctx);
        Espresso.registerIdlingResources(idlingResource);
    }
    @After
    public void after() {
        Espresso.unregisterIdlingResources(idlingResource);
    }
    @Test
    public void runSequence() {
        // this triggers our intent service, as we registered
        // Espresso for it, Espresso wait for it to finish
        onView(withId(R.id.action_settings)).perform(click());
        onView(withText("Broadcast")).check(matches(notNullValue()));
    }
}
تمرین: تست کاربردی کد ناهمزمان با استفاده از Espresso

یک پروژه جدید با اسم پکیج testing.android.vogella.com.asynctask ایجاد نمایید. پروژه حاضر بایستی این امکان را فراهم کند تا با کلیک بر روی دکمه، یک نمونه از AsyncTask جهت مدیریت عملیات در پس زمینه به طور ناهمگام، فراخوانی شود.

نمونه ای از پیاده سازی activity:

package testing.android.vogella.com.asynctask;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public void onClick(View view) {
        TextView textView = (TextView) findViewById(R.id.text);
        textView.setText("Running");
        myTask.execute("test");
    }
    final AsyncTask myTask = new AsyncTask() {
        @Override
        protected String doInBackground(String... arg0) {
            return "Long running stuff";
        }
        @Override
        protected void onPostExecute(String result) {
            TextView textView = (TextView) findViewById(R.id.text);
            textView.setText("Done");
        }
    };
}

کد پیاده سازی شده برای تست:

package testing.android.vogella.com.asynctask;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
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.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class EspressoTest {
    @Rule
    public ActivityTestRule mActivityRule =
            new ActivityTestRule<>(MainActivity.class);
    @Test
    public void buttonShouldUpdateText(){
        onView(withId(R.id.update)).perform(click());
        onView(withId(R.id.text)).check(matches(withText("Done")));
    }
}
تمرین: تعریف matcher اختصاصی برای Espresso

(matcher = یک موتور است که رشته ای از حروف را بر اساس الگو یا pattern تفسیر شده، بررسی کرده و سعی بر یافتن موارد منطبق دارد. matcher در واقع با فراخوانی متد matcher از pattern مورد نظر، ایجاد می شود.)

اندروید با ارائه ی کلاسی به نام BoundedMatcher به شما امکان می دهد تا view matcher های مورد نظر (matcher هایی بنویسید که با view مورد نظر منطبق باشد) را ویژه ی view type های (جهت انطباق با view ای از نوع مورد نظر همچون textbox یا button) معین اعلان نمایید.

حال یک matcher بنویسید که صحت عملکرد متن های راهنمای (text hint) فیلد EditText را بررسی می کند.

public static Matcher withItemHint(String itemHintText) {
  checkArgument(!(itemHintText.equals(null)));
  return withItemHint(is(itemHintText));
}
public static Matcher withItemHint(final Matcher matcherText) {
  // use preconditions to fail fast when a test is creating an invalid matcher.
  checkNotNull(matcherText);
  return new BoundedMatcher(EditText.class) {
    @Override
    public void describeTo(Description description) {
      description.appendText("with item hint: " + matcherText);
    }
    @Override
    protected boolean matchesSafely(EditText editTextField) {
      return matcherText.matches(editTextField.getHint().toString());
    }
  };
}

می توان از طریق کد زیر آن را انجام داد:

import static com.your.package.test.Matchers.withItemHint;
...
onView(withItemHint("test")).check(matches(isDisplayed()));
1396/01/17 4874 1765
رمز عبور : tahlildadeh.com یا www.tahlildadeh.com
نظرات شما

نظرات خود را ثبت کنید...