مشخصات مقاله
-
1765
-
0.0
-
4874
-
0
-
0
تست لایه ی 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 نصب نمایید.
تنظیم فایل 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 ایجاد می کند.
تنظیم 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 را انتخاب نمایید.
استفاده از سیستم کامپایل Gradle
بر روی آیکون کوچک Gradle در نوار کناری سمت راست محیط کلیک نموده و سپس گزینه ی connectedCheck را انتخاب نمایید تا تست به طور مستقیم از 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 MatcherwithItemHint(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()));