مشخصات مقاله
آزمایش برنامه های کاربردی android- تست رابط کاربری در android
نکات تکمیلی درباره ی تست برنامه ها در اندروید (additional assertion)
کلاس های اضافی رابط برنامه سازی کاربردی تست برنامه های اندروید
Testing API اندروید علاوه بر کلاس استاندارد Assert JUnit, کلاس های MoreAsserts و ViewAsserts را در اختیار برنامه نویس قرار می دهد.
android test group
می توان تست ها را با @SmallTest , @MediumTest و @LargeTest حاشیه نویسی (annotate) کرده و از این طریق خود انتخاب کرد کدام test group اجرا شود .
تصویر زیر پروسه ی انتخاب را در قسمت Run Configuration محیط برنامه نویسی Eclipse نمایش می دهد .

این کار به شما اجازه می دهد, برای مثال, تنها تست هایی را راه اندازی کنید که اجرای آن ها در محیط Eclipse زمان زیادی طول نمی کشد . تست هایی که زمان اجرای آن طولانی می باشد باید روی integration server متوالی راه اندازی شود .
استفاده از تست های Flaky (حاشیه نویسی @FlakyTest)
عملیات (actions) در اندروید گاهی وابسته به زمان هستند . جهت فرمان دادن به اندروید برای اجرای مجدد تست در صورت ناموفق بودن آن, می توان از حاشیه نویسی (annotation) @FlakyTest بهره جست . از طریق خصیصه (attribute) tolerance می توان تعیین کرد چند بار یک تست باید اجرا شده و ناموفق بوده تا android test framework آن را کاملاً به عنوان تست ناموفق علامت گذاری و شناسایی کند .
تمرین : اجرای تست به وسیله ی Apache ant (حاشیه نویسی Apache)
,p> پروژه ی پیشین خود به نام com.vogella.android.test.simpleactivity.test را بروز رسانی کرده تا از این طریق به فایل build.xml دسترسی پیدا کنید, سپس تست های مربوطه را با دستور ant test راه اندازی کنید.تست پردازش ناهمگام (asynch processing)
انجام تست روی پردازش ناهمگام امری دشوار و پیچیده تلقی می گردد . روش معمول آن این است که یک نمونه از کلاس CountDownLatch در کد آزمایشی خود استفاده کرده و از پردازش ناهمگام علامتی (signal) مبنی بر اتمام پردازش ارسال کنید . به عنوان مثال, پردازش ناهمگام اجازه می دهد یک گوش فراخوان listener)) برای فرایند پردازش ثبت کنید که تست بتواند با آن مشترک شود (subscribe کند( .
برای این منظور پروژه ی جدیدی به نام com.vogella.android.test.async ایجاد کنید که امکان فعال سازی AsyncTask را با زدن یک دکمه فراهم می آورد .
نمونه ای از کد activity :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | package com.vogella.android.test.async; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; public class MainActivity extends Activity { private IJobListener listener; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public static interface IJobListener { void executionDone(); } public void setListener(IJobListener listener) { this .listener = listener; } public void onClick(View view) { myTask.execute( "test" ); } final AsyncTask<string, void ,= "Void," string= "String" > myTask = new AsyncTask<string, void ,= "Void," string= "String" >() { @Override protected String doInBackground(String... arg0) { return "Long running stuff" ; } @Override protected void onPostExecute(String result) { super .onPostExecute(result); if (listener != null ) { listener.executionDone(); } } }; } </string,></string,><button></button> |
نمونه از کد برای تست :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | package com.vogella.android.test.async.test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import android.content.Intent; import android.test.ActivityUnitTestCase; import android.widget.Button; import com.vogella.android.test.async.MainActivity; import com.vogella.android.test.async.MainActivity.IJobListener; import com.vogella.android.test.async.R; public class AsyncTaskTester extends ActivityUnitTestCase<mainactivity> { private MainActivity activity; public AsyncTaskTester() { super (MainActivity. class ); } protected void setUp() throws Exception { super .setUp(); Intent intent = new Intent(getInstrumentation().getTargetContext(), MainActivity. class ); startActivity(intent, null , null ); activity = getActivity(); } protected void tearDown() throws Exception { super .tearDown(); } public void testSomeAsynTask () throws Throwable { // create CountDownLatch for which the test can wait. final CountDownLatch latch = new CountDownLatch( 1 ); activity.setListener( new IJobListener() { @Override public void executionDone() { latch.countDown(); } }); // Execute the async task on the UI thread! THIS IS KEY! runTestOnUiThread( new Runnable() { @Override public void run() { Button button = (Button) activity.findViewById(R.id.button1); button.performClick(); } }); boolean await = latch.await( 30 , TimeUnit.SECONDS); assertTrue(await); } } </mainactivity><button></button> |
تست رابط کاربری (UI)
تست تمامی مولفه های (cross-component) برنامه مورد نظر
آزمایش جعبه سیاه گونه یا عملیاتی رابط کاربری, کل یک برنامه کاربردی را مورد آزمایش قرار می دهد و نه تنها یک مولفه ی خاص از برنامه ی مورد نظر را .
بهره گیری از کتابخانه ی uiautomator زبان جاوا برای ایجاد تست های رابط کاربری
ابزار توسعه ی نرم افزاری (SDK) اندروید دربردارنده ی کتابخانه ی uiautomator زبان جاوا است که به منظور ایجاد تست های رابط کاربری و فراهم آوردن موتوری برای اجرای این تست ها بکار می رود . هر دو ابزار و تجهیزات نام برده فقط روی API 16 به بعد قابلیت اجرا شدن را دارند .
پروژه های آزمایشی uiautomator اشیإ مستقل جاوا هستند که کتابخانه ی JUnit3, uiautomator.jar , فایل های android.jar از پوشه ی android-sdk/platforms/api-version را به build path اضافه کرده است .
کتابخانه ی uiautomator اندروید به منظور برقراری ارتباط با دستگاه کلاس UiDevice و جهت جستجو و یافتن المان های روی صفحه نمایش کلاس UiSelector و برای ارائه ی عناصر رابط کاربری کلاس UiObject را ارائه می دهد که این کلاس خود بر اساس کلاس UiSelector ساخته شده است . کلاس UiCollection اجازه ی انتخاب چندین عنصر رابط کاربری به طور همزمان را می دهد و کلاس UiScrollable امکان پیمایش (scroll) در لیست برای یافتن درایه یا المان مورد نظر را بوجود می اورد .
مثالی که ذیل عرضه شده را می توان تحت این URL بدست آورد :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | package com.uia.example.my; // Import the uiautomator libraries import com.android.uiautomator.core.UiObject; import com.android.uiautomator.core.UiObjectNotFoundException; import com.android.uiautomator.core.UiScrollable; import com.android.uiautomator.core.UiSelector; import com.android.uiautomator.testrunner.UiAutomatorTestCase; public class LaunchSettings extends UiAutomatorTestCase { public void testDemo() throws UiObjectNotFoundException { // Simulate a short press on the HOME button. getUiDevice().pressHome(); // We’re now in the home screen. Next, we want to simulate // a user bringing up the All Apps screen. // If you use the uiautomatorviewer tool to capture a snapshot // of the Home screen, notice that the All Apps button’s // content-description property has the value “Apps”. We can // use this property to create a UiSelector to find the button. UiObject allAppsButton = new UiObject( new UiSelector().description( "Apps" )); // Simulate a click to bring up the All Apps screen. allAppsButton.clickAndWaitForNewWindow(); // In the All Apps screen, the Settings app is located in // the Apps tab. To simulate the user bringing up the Apps tab, // we create a UiSelector to find a tab with the text // label “Apps”. UiObject appsTab = new UiObject( new UiSelector().text( "Apps" )); // Simulate a click to enter the Apps tab. appsTab.click(); // Next, in the apps tabs, we can simulate a user swiping until // they come to the Settings app icon. Since the container view // is scrollable, we can use a UiScrollable object. UiScrollable appViews = new UiScrollable( new UiSelector().scrollable( true )); // Set the swiping mode to horizontal (the default is vertical) appViews.setAsHorizontalList(); // create a UiSelector to find the Settings app and simulate // a user click to launch the app. UiObject settingsApp = appViews .getChildByText( new UiSelector() .className(android.widget.TextView. class .getName()), "Settings" ); settingsApp.clickAndWaitForNewWindow(); // Validate that the package name is the expected one UiObject settingsValidation = new UiObject( new UiSelector() .packageName( "com.android.settings" )); assertTrue( "Unable to detect Settings" , settingsValidation.exists()); } } <button></button> |
برای ساخت و اجرای پروژه ی مربوطه باید از Apache Ant استفاده کنید .
1 2 3 4 5 6 7 8 9 10 11 | <android-sdk>/tools/android create uitest-project -n <name> -t 1 -p <path> # build the test jar ant build # push JAR to device adb push output.jar /data/local/tmp/ # Run the test adb shell uiautomator runtest LaunchSettings.jar -c com.uia.example.my.LaunchSettings </path></name></android-sdk><button></button> |
استفاده از ابزار uiautomatorviewer جهت تحلیل رابط کاربری برنامه
اندروید به منظور تجزیه و تحلیل رابط کاربری برنامه, uiautomatorviewer را در اختیار برنامه نویس قرار می دهد . با استفاده از این امکان برنامه نویس قادر خواهد بود شاخص (index), متن و خصیصه ی برنامه ی کربردی را پیدا کند .
قابلیت مذکور همچنین به افرادی که خود برنامه نویس نیستند اجازه می دهد برنامه را تحلیل کرده و به وسیله ی کتابخانه ی uiautomator برای آن تست طراحی کند .
ابزار ذکر شده در تصویر زیر نمایان می باشد .

Monkey
یکی از تجهیزات خط فرمان است که رخدادهای شبه تصادفی (pseudo random events) را به دستگاه ارسال می کند . می توانید Monkey را طوری تنظیم کنید که فقط برای یک پکیج مشخص اجرا شود و از این طریق تنها برنامه ی دلخواه شما را مورد آزمایش قرار دهد .
به عنوان مثال نمونه ی زیر حدود 2000 رخداد تصادفی با پکیج de.vogella.android.test.target به برنامه ی کاربردی مورد نظر می فرستد .
استفاده از Monkey گاهی اوقات ممکن است منجر به بروز مشکلاتی با سرویس دهنده ی adb شود . در صورت بروز این رویداد با استفاده از دستورات زیر سرور را مجدداً راه اندازی کنید .
1 2 3 | adb kill-server adb start-server <button></button> |
می توانید با استفاده از پارامتر -s [seed] مطمئن شوید که رشته یا توالی رویداد های حاصله (تولید شده) همیشه یکسان باشد .
Monkeyrunner
انجام تست با استفاده از ابزار Monkeyrunner
Monkeyrunner یک رابط برنامه سازی کاربردی Python ویژه ی برنامه نویسی عرضه می کند که امکان مدیریت دستگاه اندروید یا شبیه ساز را از بیرون کد اندروید فراهم می آورد.
از طریق monkeyrunner شما می توانید رویه ی تست (test procedure) را کاملاً به صورت نوشته (script) در آورید . monkeyrunner را می توان از طریق پل اشکال زدایی اندروید (adb debug bridge) راه اندازی کرد . ابزار نام برده به شما اجازه می دهد اپلیکیشن را نصب, راه اندازی کرده و همچنین جریان برنامه را کنترل کنید و حتی در صورت لزوم از برنامه تصویر تهیه کنید .
به منظور استفاده از Monkeyrunner, ابتدا باید python را در مسیر مرتبط یا روی دستگاه خود نصب کنید .
در Monkeyrunner برنامه نویس اجازه ی استفاده از کلاس های اصلی و اولیه زیر را دارد :
- MonkeyRunner : قابلیت اتصال به دستگاه ها را ایجاد می کند .
- MonkeyDevice : امکان نصب و پاک کردن پکیج ها, ارسال رخدادهای مربوط به صفحه کلید و لمس به یک برنامه ی کاربردی را فراهم می کند .
- MonkeyImage : اجازه ی ایجاد تصاویر, قیاس و ذخیره سازی آن ها را فراهم می آورد
MonkeyImage قادر است تصویر مورد نظر را با استفاده از متد sameAs() با تصویر موجود مقایسه کند . یک تصویر (screenshot) حاوی notification bar که ممکن است دربردارنده ی زمان و غیره باشد, است . می توانید یک درصد را به عنوان پارامتر دوم برای sameAs () وارد کرده یا متد getSubImage() را بکار ببرید .
ارجاع API ویژه ی monkeyrunner را می توان از طریق دستور زیر تولید کرد .
1 2 3 4 | # outfile is the path qualified name # of the output file monkeyrunner help.py help <outfile> </outfile><button></button> |
مثالی از کاربرد monkeyrunner
پیش از هر کاری اطمینان حاصل کنید که Python در مسیر مورد نظر نصب شده است . همچنین مطمئن شوید پوشه ی [android-sdk]/tools نیز در مسیر مزبور موجود می باشد . اکنون فایل جدیدی ایجاد کنید و آن راtestrunner.py نام گذاری کنید .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice import commands import sys import os # starting the application and test print "Starting the monkeyrunner script" if not os.path.exists( "screenshots" ): print "creating the screenshots directory" os.makedirs( "screenshots" ) # connection to the current device, and return a MonkeyDevice object device = MonkeyRunner.waitForConnection() apk_path = device.shell( 'pm path com.vogella.android.test.simpleactivity' ) if apk_path.startswith( 'package:' ): print "application installed." else : print "not installed, install APK" device.installPackage( 'com.vogella.android.test.simpleactivity.apk' ) print "starting application...." device.startActivity(component='com.vogella.android.test.simpleactivity/[...CONTINUE] com.vogella.android.test.simpleactivity.MainActivity') #screenshot MonkeyRunner.sleep( 1 ) result = device.takeSnapshot() result.writeToFile( './screenshots/splash.png' , 'png' ) print "screenshot taken and stored on device" #sending an event which simulate a click on the menu button device.press( 'KEYCODE_MENU' , MonkeyDevice.DOWN_AND_UP) print "Finishing the test" <button></button> |
این تست را با استفاده از دستور monkeyrunner testrunner.py روی کنسول خود اجرا کنید .
ملزومات معمول تست در اندروید و رویکردهای مختلف حل مشکلات
ثبت وقایع (logging) در سرویس دهنده یا سرور
به طور معمول فایل های log (فایل هایی که وقایع در آن ثبت می شود) بر روی سرور ذخیره می شوند و نه روی دستگاه اندروید, از این رو بهترین روش تهیه ی یک server backend و ارسال نتیجه از طریق HTTP request به سرور مذکور است . سرور فایل log را ذخیره کرده و دسترسی مرکزی (کلی) به آن را امکان پذیر می سازد .
اعمال تغییرات در سیستم از طریق تست
طی فرایند تست گاهی لازم است تغییراتی در سیستم ایجاد کنید, مثلاً wi-fi را فعال کنید . اغلب انجام این کار از طریق خود تست به طور مستقیم امکان پذیر نمی باشد, به این خاطر که تست تنها مجوزهای برنامه ی در حال تست را دارد .
روش صحیح انجام این عملیات نصب برنامه ای دیگر بر روی دستگاه است که دارای مجوزهای لازم بوده, سپس آن را به وسیله ی یک intent از تست مربوطه فعال سازی کنید .
چهارچوب کاری Espresso
شرکت گوگل ویژه ی تست برنامه های اندروید چهارچوب کاری (framework) Espresso را در اکتبر 2013 منتشر کرد . Espresso در حقیقت یک لایه ی نازک اضافه بر instrumentation (توانایی نظارت بر و سنجش سطح کارایی یک محصول جهت تشخیص و کشف خطا ها و اشکالات و همچنین نوشتن اطلاعات مربوط به ثبت وقایع و ردیابی) می باشد که طراحی تست های رابط کاربری معتبر را به مراتب آسان می سازد.
Espresso بر پایه ی JUnit3 بوده و برای فرایند بررسی خود از تطبیق دهنده Hamcrest (matcher)(کتابخانه ای از تطبیق دهنده / matcher ها که با استفاده از آن می توان عبارات تست را ایجاد کرد) استفاده می کند . جهت استفاده از آن کافی است Espresso و Dependencies را از https://code.google.com/p/android-test-kit دانلود کرده و کتابخانه ی مورد نظر را به classpath خود اضافه کنید .
ظاهر یک تست معمولاً ترکیبی از یک view matcher, view action و assert statement روی این view می باشد . کد زیر مثالی را در این زمینه ارائه می کند . اینجا نوع view کاملاً بی ربط تلقی می گردد, بررسی باید روی کلاس عمومی view صورت گیرد .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public void testSayHello() { // find a view with a specific test and check if it is displayed onView(withText( "Button" )).check(matches(isDisplayed())); // others checks are isEnabled() or isFocused() // combine them onView(withText( "Button1" )).check(matches(not(isEnabled()))); // find a view with an ID and type some text into it onView(withId(R.id.name_field)).perform(typeText( "New text" )); // find a view and click on it onView(withId(R.id.greet_button)).perform(click()); // check several conditions at once onView(allOf(withId(R.id.greet_button), not(withText( "Placehodler" ))); // scroll a view onView(withId(R.id.listview)).performScroll(); // scroll and click onView(withId(R.id.listview)).perform(scrollTo(), click()); } <button></button> |
چنانچه Espresso, view را پیدا نکرد, در آن صورت Espresso کل سلسله مراتب view را داخل پیام خطا می گنجاند, که برای تحلیل مشکل بسیار کارامد می باشد .
به منظور استفاده از Espresso testrunner, درایه (entry) مربوطه را در AndroidManifest.xml اصلاح کنید .
1 2 | <instrumentation android:name= "com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner" android:targetpackage= "your_package" ></instrumentation> <button></button> |
در صورت راه اندازی مجدد تست, test runner جدید مورد استفاده قرار می گیرد . می توانید این مسئله را درlaunch configuration اعتبار سنجی کنید .

onView خود به صورت اتوماتیک در صفحه پیمایش (scroll) می کند . برای AdapterViews می توان به جای تابع onView() از متد onData() استفاده کرد .
1 2 | onData(allOf(is(instanceOf(String. class )), is( "Hello" )).perform(click())); <button></button> |
دیگر testing framework های منبع آزاد
Robotium یک چهارچوب کاری منبع آزاد اضافه بر testing framework خود اندروید می باشد که رابط برنامه سازی کاربردی تست برنامه ها در اندروید را به مراتب ساده تر می سازد.
Robolectric نیز یک چهارچوب کاری منبع آزاد است که به برنامه نویس اجازه می دهد تست هایی را اجرا کند که رابط برنامه سازی کاربردی اندروید را مستقیما روی دستگاه مجازی جاوا (JVM) بکار می برد.
Roboguice نیز امکان استفاده از قابلیت تزریق وابستگی (dependency injection) در کامپوننت های اندروید را فراهم می کند که در نهایت منجر به آسان سازی پروسه ی تست می شود.