شاید در نگاه اول پیش خودتان فکر کنید که وقتی برنامه را میتوانیم بصورت دستی تست کنیم دیگر چه احتیاجی به تست نویسی اتوماتیک داریم، تست های اتوماتیک نیز تست هایی هستند که برنامه نویس به صورت برنامه می نویسد تا قادر باشد هر طور که می خواهد هر قسمت از برنامه را که می خواهد مورد تست قرار دهد. آن هم تنها با یک یونیت تست (هر یونیت تست تنها یک تابع است ). اگر بخواهیم به صورت ساده این مسئله را توضیح دهیم و خلاصه کنیم ، تست کردن به صورت دستی در مقایسه با تست کردن به صورت اتوماتیک مثل کندن پی یک ساختمان با کلنگ در مقایسه با یک بیل مکانیکی میباشد.
مشکل اول : زیاد بودن موارد مورد تست که باید تست شوند . مشکل دوم : سخت بودن یا زمانبر بودن بازسازی مراحل قبل از رسیدن به مرحله تست. مشکل سوم : زیاد بودن وضعیت های دستگاه یا برنامه ما. مشکل پنجم : هزینه زیاد برطرف کردن باگ بعد از انتشار برنامه مشکل ششم : طولانی شدن زمان توسعه
یونیت تست (Unit Test) : یک مرحله از تست نرم افزار است که در آن بخشهای کوچک از یک برنامه (Units) یا کامپوننتهای مختلف یک نرم افزار تست میشوند. برنامه نویسان از Unit Test استفاده میکنند تا ببیند بازدهی برنامه آنها چیزی است که انتظارش را داشتند یا خیر. به عبارتی Unit Testing به برنامه نویس نشان میدهد که چقدر به طراحی اولیه نزدیک شده و برنامه او مطابق استانداردهای طراحی اولیه نرم افزار عمل میکند یا خیر . منظور از Unit کوچکترین بخش از برنامه است که قابل تست بوده و به طور معمول شامل چند ورودی و نهایت یک خروجی میشود.
در فریمورک اندروید معروف ترین ابزارهای Unit Test شامل موارد زیر می باشد :
. برای تست UI کتابخانه Espresso
. برای تست تابع های برنامه کتابخانه 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 های مختلفی استفاده می کنیم که خلاصه ای از آنها را در شرح ذیل توضیح خواهیم داد که در قسمت ذیل به توضیح بعضی از موارد آن می پردازیم:
یک ابزار برای نوشتن تست های 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));
پس از پیدا کردن View ی مورد نظر در صفحه شما میتوانید اکشن های مختلفی روی آن ها انجام دهید.(click clearText , doubleClick , scrollTo)
در مثال زیر میخواهیم روی دکمه ای که در مرحله قبل شناسایی کردیم کلیک انجام دهیم.
onView(withId(R.id.button)).perform(click());
در مثال دیگری میخواهیم متنی را روی EditText بنویسیم :
onView(withId(R.id.editText)).perform(typeText("متن آزمایشی"));
مهمترین قسمت تست Espresso مطابقت چرخه حیات اکتیویتی یا فرگمنت و یا سرویس با annotation آن می باشد که ترتیب اجرای آن بصورت زیر می باشد :
1 . BeforeClass@
2. onCreate@
3. @
4. @onResum
5. Before@
6. Test@
7. After@
8.
9. onDestroy
10. AfterClass@
اگر وابستگی بین کلاسهای برنامه شما ایجاد مرز بین آنچه را که می خواهید آزمایش کنید و بقیه کد کد را دشوار کند نوشتن آزمون واحد دشوار است. Mockito به شما کمک می کند تا با استفاده از اشیا ساختگی به راحتی تست های جاوا را ایجاد کنید. اگر وابستگی بین کلاس های برنامه شما ایجاد مرز بین آنچه را که می خواهید آزمایش کنید و بقیه کد کد را دشوار کند ، نوشتن تست واحد دشوار است. ابتدا باید یاد بگیریم که چگونه از اشیا objects به جای وابستگی واقعی استفاده کنید. برای تست نویسی همانند Junit در قسمت com.example.program(test) کلاسهای آن پیاده سازی میشود.
برای استفاده از آن ابتدا باید dependency آن را اضافه کنیم :
testImplementation 'org.mockito:mockito-core:1.10.19'
androidTestImplementation "org.mockito:mockito-core:1.+"
یک کلاس بصورت زیر ایجاد می کنیم:
public class Repository {
public String getData(){
return "This is data"
}
public void getValue(String a,String b){
Log.i("",""+a + ""+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("this is mock string");
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@ حتی اگر به آن مقداری ندهیم مقداری را بر میگرداند که در کلاس اصلی آن مقدار دهی شده است.