تست نویسی در اندروید

شاید در نگاه اول پیش خودتان فکر کنید که وقتی برنامه را میتوانیم بصورت دستی تست کنیم دیگر چه احتیاجی به تست نویسی اتوماتیک داریم، تست های اتوماتیک نیز تست هایی هستند که برنامه نویس به صورت برنامه می نویسد تا قادر باشد هر طور که می خواهد هر قسمت از برنامه را که می خواهد مورد تست قرار دهد. آن هم تنها با یک یونیت تست (هر یونیت تست تنها یک تابع است ). اگر بخواهیم به صورت ساده این مسئله را توضیح دهیم و خلاصه کنیم ، تست کردن به صورت دستی در مقایسه با تست کردن به صورت اتوماتیک مثل کندن پی یک ساختمان با کلنگ در مقایسه با یک بیل مکانیکی میباشد. مشکلات و محدودیتهای تست کردن به صورت غیر اتوماتیک یا دستیمشکل اول : زیاد بودن موارد مورد تست که باید تست شوند . مشکل دوم : سخت بودن یا زمانبر بودن بازسازی مراحل قبل از رسیدن به مرحله تست. مشکل سوم : زیاد بودن وضعیت های دستگاه یا برنامه ما. مشکل پنجم : هزینه زیاد برطرف کردن باگ بعد از انتشار برنامه مشکل ششم : طولانی شدن زمان توسعهانواع تست در اندرویدیونیت تست (Unit Test) : یک مرحله از تست نرم افزار است که در آن بخش‌های کوچک از یک برنامه (Units) یا کامپوننت‌های مختلف یک نرم افزار تست می‌شوند. برنامه نویسان از Unit Test استفاده می‌کنند تا ببیند بازدهی برنامه آنها چیزی است که انتظارش را داشتند یا خیر. به عبارتی Unit Testing به برنامه نویس نشان می‌دهد که چقدر به طراحی اولیه نزدیک شده و برنامه او مطابق استانداردهای طراحی اولیه نرم افزار عمل می‌کند یا خیر . منظور از Unit کوچک‌ترین بخش از برنامه است که قابل تست بوده و به طور معمول شامل چند ورودی و نهایت یک خروجی می‌شود.در فریمورک اندروید معروف ترین ابزارهای Unit Test شامل موارد زیر می باشد :. برای تست UI کتابخانه Espresso . برای تست تابع های برنامه کتابخانه Junit کتابخانه Junitدر IDE اندروید استودیو در مسیرcom.example.program(test) نوشته میشود. به عنوان نمونه ما متدی داریم که دو عدد به آن می دهیم و جمع آنها را به ما بر میگرداند:public class math{ public int plus(int a,int b){ return a+b; }}در قسمت test برای نوشتن تابعی که این متد را تست کند به صورت زیر می نویسیم:public class ExampleUnitTest { @Test public void testDivisition(){ Math math = new Math(); assertEquals(12,math.devesiton(8,4)); }}همانطور که مشاهده می کنید در قسمت بالا در تابع Junit ما عددهای ورودی را 8 و 4 داده ایم و حاصل جمع آن باید 12 باشد، وقتی این تابع را run می کنیم باید تست ما pass شود اگر حاصل جمع 8 و 4 را 11 قرار دهیم تست ما باید fail شود . با اینکار به راحتی در کمترین زمان ممکن و بدون تست دستی متوجه میشویم که تابع نتیجه را درست بر میگرداند یا خیر یا اصلا درست کار میکند.ما در Junit از assert های مختلفی استفاده می کنیم که خلاصه ای از آنها را در شرح ذیل توضیح خواهیم داد که در قسمت ذیل به توضیح بعضی از موارد آن می پردازیم:. ابزار assertEquals : برای وقتی که میخواهیم متوجه بشویم تابع برابر است با این مواردی که در داخل asserEquals می نویسیم یا خیرابزار assertNotNull : برای چک کردن مواقعی که میخواهیم متوجه بشیم که شی مورد نظر ما null نباشد یعنی اگر null باشد تست pass می شود و اگر null باشد تست fail می شود. . ابزار assertNull: دقیقا بر عکس assertNotNull می باشد . ابزار assertNotSame : برای مقایسه بین دو تا متغیری که داخل یه کلاس یا یک متد هستند، مثلا یک متدی داریم که دوتا متغیری از نوع int بر میگرداند اگر دوتا متغیر را هم عدد قرار دهیم تست fail میشود اگر اعداد آن برابر نباشد پاس میشود.ابزار assertAll : برای تست کردن مجموعه از assert ها، وقتی داخل متدمان چندین نوع assert نوشتیم و میخواهیم همه را همزمان تست کنیم از assertAll استفاده میکنیم.ابزار assertThat : وقتی داخل خود متد تست میخواهیم بین دو تا شی یا آبجکتی تست انجام دهیم از assertThat استفاده میکنیم که در واقع اشاره دارد به بالا ابزار assertArrayEquals : برای مقایسه بین دو تا array بکار می رود. مثلا دو تا متغیری داریم بصورت string لیستی از array ها دارد مقایسه می کنید که دقیقا هم نام و هم اندازه هم باشند تا تست passشود در غیر اینصورت fail میشود. مثال: assertLinesMatch: چک میکند که اندزه array ها با هم یکی هستند یا خیر . ابزار assertThrows : برای چک کردن این موضوع پیام ما داخل Exeption می رود یا خیرابزار assertTimeout: برای بررسی اینکه thread.sleep بصورت دقیق کار میکند یا خیر مثلا بعد از یک ثانیه می رود عمل مورد نظر را انجام میدهد یا خیر.کتابخانه Espresso Testیک ابزار برای نوشتن تست های UI مختصر، زیبا و قابل اطمینان است. در مسیر در قسمت پکیج نیم برنامه com.example.program(androidTest) کلاسهای آن درج میشود. برای شروع در مسیر ذکر شده یک کلاس جدید ایجاد می کنیم، سپس در بالای کلاس @RunWith(AndroidJUnit4.class) را بنویسید. دلیل نوشتن این annotation آن است که در زمان اجرای تست ، هر کلاسی که دارای این annotation باشد به عنوان کلاس حاوی کد های تست شناسایی و پردازش میشود.معرفی اکتیویتی :اکنون برای معرفی اکتیویتی مورد نظر برای اجرا از annotation @Rule استفاده میکنیم. مثال :@RunWith(AndroidJUnit4.class)public class MainActivityTest { @Rule public ActivityTestRule<MainActivity> activityActivityTestRule = new ActivityTestRule<>(MainActivity.class);}حالت کلی تست Espersso بصورت ذیل می باشد: onView(withId(R.id.button)).perform(click()).check(matches(isEnabled()));شناسایی ویو ها در صفحه به کمک ViewMatcher : اسپرسو برای شناسای ویو ها در صفحه از کلاس ViewMatcher کمک میگیرد: به عنوان مثال ما در اکتیویتی main خود یک دکمه (android:id="@+id/button") تعریف کرده ایم . اسپرسو برای یافتن این ویو در صفحه چندین راه را ارائه کرده است. قطعه کد زیر ویو را به کمک id آن در صفحه شناسایی میکند.onView(withId(R.id.button));اجرای Action های مختلف بر روی View ها به کمک perform:پس از پیدا کردن View ی مورد نظر در صفحه شما میتوانید اکشن های مختلفی روی آن ها انجام دهید.(click clearText , doubleClick , scrollTo)در مثال زیر میخواهیم روی دکمه ای که در مرحله قبل شناسایی کردیم کلیک انجام دهیم.onView(withId(R.id.button)).perform(click()); در مثال دیگری میخواهیم متنی را روی EditText بنویسیم :onView(withId(R.id.editText)).perform(typeText(&quotمتن آزمایشی&quot));مهمترین قسمت تست Espresso مطابقت چرخه حیات اکتیویتی یا فرگمنت و یا سرویس با annotation آن می باشد که ترتیب اجرای آن بصورت زیر می باشد :1 . BeforeClass@2. onCreate@3. @4. @onResum5. Before@6. Test@7. After@8. 9. onDestroy10. AfterClass@کتابخانه Mockito Testاگر وابستگی بین کلاسهای برنامه شما ایجاد مرز بین آنچه را که می خواهید آزمایش کنید و بقیه کد کد را دشوار کند نوشتن آزمون واحد دشوار است. Mockito به شما کمک می کند تا با استفاده از اشیا ساختگی به راحتی تست های جاوا را ایجاد کنید. اگر وابستگی بین کلاس های برنامه شما ایجاد مرز بین آنچه را که می خواهید آزمایش کنید و بقیه کد کد را دشوار کند ، نوشتن تست واحد دشوار است. ابتدا باید یاد بگیریم که چگونه از اشیا objects به جای وابستگی واقعی استفاده کنید. برای تست نویسی همانند Junit در قسمت com.example.program(test) کلاسهای آن پیاده سازی میشود.برای استفاده از آن ابتدا باید dependency آن را اضافه کنیم : testImplementation 'org.mockito:mockito-core:1.10.19' androidTestImplementation &quotorg.mockito:mockito-core:1.+&quotیک کلاس بصورت زیر ایجاد می کنیم:public class Repository { public String getData(){ return &quotThis is data&quot } public void getValue(String a,String b){ Log.i(&quot&quot,&quot&quot+a + &quot&quot+b); }}کلاس تست را بصورت زیر اجرا می کنیم:@RunWith(MockitoJUnitRunner.class)class RepositoryTest { @Mock Repository repository ; @Spy Repository repository1; @BeforeEach public void setup() { MockitoAnnotations.initMocks(this); } @Test public void getData() { // mock(Repository.class); when(repository.getData()).thenReturn(&quotthis is mock string&quot); System.out.println(repository.getData()); } @Test public void getData1() { doReturn(repository1.getData()).when(repository1).getData(); System.out.println(repository1.getData()); }}نکته : تفاوت Mock@ و spy@ : آبجکتی که با Mock@ ساخته میشود باید در کلاس تست به آن مقدار بدهیم در غیر اینصورت Null بر میگرداند، اما spy@ حتی اگر به آن مقداری ندهیم مقداری را بر میگرداند که در کلاس اصلی آن مقدار دهی شده است.