استفاده از Junit در جاوا و اندروید
از سری مقالات آموزش جاوا و اندروید, مبحث Unit Testing با استفاده از Junit را برمیگزینیم.
در مقاله قبل ماهیت Unit Testing و همچنین انوان آن بررسی شد. در این مقاله قصد داریم تا ابزار قدرتمند Junit را معرفی و از آن در پروژه های جاوا و اندروید استفاده کنیم.
یکی از مزایای junit این است که بر روی اکثر محیط های کدنویسی قابل اجرا است. همچنین اگر برنامه نویسی هستید که از خط فرمان برای اجرای کد های خود استفاده میکنید, junit در خط فرمان نیز قابل اجرا است.
Assert Equal
اولین قسمت Junit Framework, مبحث Assert Equal است. Assert Equal test نتیجه را بررسی میکند. درواقع بررسی میکند که مقداری که از پردازش متد بدست میآید, با مقدار مورد نظر یکسان باشد. اگر مقدار نتیجه و مقدار مورد نظر یکسان باشند, Test موفقیت آمیز و اگر نتایج با هم مغایرت داشته باشند, Test شکستخورده است. برای مثال یک متد ایجاد میکنیم و میخواهیم حرف اول رشته را با یک character ورودی مقایسه کنیم. خروجی این متد از نوع boolean است:
public class StringCheck { public boolean checkFirstChar(String inputString, char firstChar) { return (inputString.charAt(0) == firstChar); } }
سپس در آدرس src/test/java, یک کلاس به نام StringCheckTest ایجاد میکنیم و چنین متدی را در آن مینویسیم:
public class StringCheckTest { @Test public void checkFirstCharTest() { StringCheck stringCheck = new StringCheck(); assertEquals(true, stringCheck.checkFirstChar("Zero to Hero", 'z')); } }
با اجرای این کلاس, انتظار ما این است که خروجی true باشد. اما خروجی false است.
java.lang.AssertionError: Expected :true Actual :false
زیرا هنگام مقایسه رشته, نسبت به بزرگ و یا کوچک بودن حروف حساسیت به خرج میدهد. لذا برای اینکه نتیجه true باشد, باید متد را اینگونه تغییر دهیم:
public boolean checkFirstChar(String inputString, char firstChar) { return (inputString.toLowerCase().startsWith(String.valueOf(firstChar).toLowerCase())); }
و نتیجه با مقدار مورد انتظار ما یکسان میشود.
اگر بخواهیم این متد را در اندروید نیز پیاده کنیم, باید یک نکته بسیار مهم را مد نظر داشته باشیم. وقتی میخواهیم یک متد را Test کنیم, دو مسیر برای ایجاد کلاس Test وجود دارد. Src/androidTest و src/test دو مسیری هستند که موقع ایجاد کلاس Test توسط Android Studio مشخص میشوند و برنامه نویس باید مشخص کند که کلاس در کدام یک از این مسیر ها باید ایجاد شود. مسیر android test برای test کلاسهایی است که به ساختار اندرویدی نیاز دارند. درواقع android framework در این testing ها دخیل است.
اما اگر به ساختار اندروید نیاز نباشد, کلاس را در مسیر test ایجاد میکنیم. همانطور که در شکل زیر مشخص است, کلاس هایی که در مسیر test ایجاد میشوند از با استفاده از jvm اجرا میشوند. در نتیجه نیازی به Emulator ندارد.
در اینجا ما میخواهیم کارکرد این متد فوق را در اندروید بررسی کنیم و واضح است که به ساختار اندرویدی نیاز نداریم. پس کلاس StringCheckTest را در مسیر src/test ایجاد میکنیم
public class MainActivityTest { @Test public void checkFirstCharTest() { MainActivity mainActivity = new MainActivity(); assertEquals(true, mainActivity.checkFirstChar("Zero to Hero", 'z')); } }
همانطور که مشاهده میکنید, دیگر نیاز به ایجاد یک emulator نیست و تنها با ایجاد یک instance از روی کلاس, توانستیم این متد را Test کنیم. در پروژه های بزرگ, استفاده از Assert باعث میشود تا زمان Testing کاهش پیدا کند و با سرعت بیشتری عمل Testing را انجام دهیم.
Assert Null
مبحث بعدی Assert Null است. در این قسمت, متد هایی که نتیجه استفاده از آنها null است, مشخص میشوند. فرض کنید میخواهیم با استفاده از یک متد, یک کاربر را احراز هویت کنیم:
public class LoginService { public UserModel login(String username, String password) { return getFromDB(username, password); }
در این مثال, اگر کاربری با چنین مشخصاتی در پایگاه داده وجود نداشته باشد, نتیجه null است. اکنون میخواهیم با استفاده از Junit, صحت عملکرد متد را بررسی کنیم:
public class LoginServiceTest { @Test public void loginTest() throws Exception { LoginService loginService = new LoginService(); assertNull(loginService.login("vhdrjb", "password")); } }
اگر این کاربر در پایگاه داده وجود نداشته باشد, نتیجه Testing موفقیت آمیز است. به این معنا که مقدار برگردانده شده null است و در پایگاه داده وجود ندارد. اما اگر نتیجه Testing با شکست مواجه شود, نشان میدهد که اطلاعات این کاربر در پایگاه داده ذخیره شده است. اگر اطلاعات کاربر موجود باشد, چنین نتیجه ای را برمیگرداند:
java.lang.AssertionError: expected null, but was:<UserModel@7506e922>
در اینجا بهتر است به یک نکته اشاره کنیم. در بعضی از موارد, برنامه نویسان نتایج یک لیست بدون مقدار را برابر Null قرار میدهند. این امر ممکن است باعث شود تا در ادامه روند برنامه نویسی, مشکلی رخ دهد. برای مثال:
public List<String> result() { if(true){ List<String> results = new ArrayList<String>(); results.add(item1); results.add(item2); results.add(item3); return results; } else { return null; } }
بهتر کد فوق را این چنین بنویسیم:
public List<String> result() { if(true){ List<String> results = new ArrayList<String>(); results.add(item1); results.add(item2); results.add(item3); return results; } else { return new ArrayList<String>(); } } }
و در هنگام استفاده, بجای مقایسه مقدار آن با null, با استفاده از متد size, وجود مقادیر در این لیست را مشخص کنیم.
Timeout
ممکن است بعضی از پردازش ها با کمی تاخیر نتیجه را برگردانند. دریافت اطلاعات از Server از این دسته از پردازش ها است. اما نمیتوانیم مدت زمان زیادی را صرف انتظار برای چنین پردازش هایی کنیم. گاهی ممکن است خطایی رخ دهد و برنامه در حلقه بینهایت قرار بگیرد. به این ترتیب بهتر است برای چنین پردازش هایی, از Timeout استفاده کنیم. با استفاده از timeout, حداکثر زمان مجاز برای برگرداندن پاسخ توسط متد را مشخص میکنیم. برای مثال کد زیر را در نظر بگیرید:
public UserModel login(String username, String password) throws InterruptedException { UserModel userModel = getFromDB(username, password); if (userModel == null) { Thread.sleep(5000); userModel=new UserModel(); } return userModel; }
در اینجا اگر مقداری که از پایگاه داده دریافت میشود برابر null باشد, بعد از پنج ثانیه, یک instance از کلاس UserModel را ایجاد میکند. اکنون متد Testing را اینطور مینویسیم:
@Test(timeout = 1000) public void loginTest() throws Exception { LoginService loginService = new LoginService(); assertNull(loginService.login("vhdrjb", "password")); }
در کد بالا, اگر پردازش متد بیشتر از یک ثانیه طول بکشد, با خطای زیر مواجه میشویم:
org.junit.runners.model.TestTimedOutException: test timed out after 1000 milliseconds
و اگر کمتر از پنج ثانیه طول بکشد, با خطای زیر مواجه میشویم:
java.lang.AssertionError: expected null, but was:<UserModel@4a9b93cc>
به این معنا که این مقدار در پایگاه داده وجود دارد.
گاهی در پروژه های بزرگ, بررسی متد ها به صورت جداگانه آزار دهنده است. به این ترتیب, بهتر است با استفاده از Suite Testing این کلاس ها را در کنار یکدیگر اجرا کنیم. ابتدا یک کلاس SuiteTesting ایجاد میکنیم:
@RunWith(Suite.class) @Suite.SuiteClasses({LoginServiceTest.class,StringCheckTest.class}) public class SuiteTesting { }
در کد بالا, مشخص کردهایم که از کدام یک از کلاس ها میخواهیم استفاده کنیم. اکنون در یک کلاس دیگر, Suite Test ایجاد شده را اجرا میکنیم:
public class Runner { public static void main(String[] args) { Result result = JUnitCore.runClasses(SuiteTesting.class); for (Failure failure : result.getFailures()) { System.out.println(failure.toString()); } System.out.println(result.wasSuccessful()); } }
در کد فوق, علت خطا ها را در خروجی چاپ میکنیم. اگر پروژه بدون هیچ خطایی اجرا شد, مقدار was successful برابر true خواهد شد.
اگر برای انجام Testing, نیاز به ترتیب داشته باشیم, میتوانیم از annotation ها استفاده کنیم. برای مثال میخواهیم از Junit در Hibernate Testing استفاده کنیم:
public class HibernateTest { static Hibernate hibernate = new Hibernate(); @BeforeClass public static void initTest() { hibernate.init(); } @Before public void beforeTest() { System.out.println("Test Started"); } @After public void afterTest() { System.out.println("Test Complete"); } @Test public void addTest() { hibernate.saveUser(); } @Test public void getTest() { hibernate.getUsers(); } }
در کد بالا, ابتدا متد init در Hibernate فراخوانی میشود. پیش از هر Test, متد beforeTest و بعد از هر Test متد afterTest فراخوانی میشود. در اینجا ابتدا متد addTest و سپس متد getTest فراخوانی میشود. بنابراین متد های beforeTest و afterTest, هریک دوبار اجرا میشوند.
در این مقاله به مبحث Junit Unit testing پرداختیم. تمام روش های فوق در جاوا و اندروید قابل استفاده هستند. البته توجه داشته باشید که در این مقاله Junit تنها با استفاده از JVM اجرا میشود. در مقاله بعد به استفاده از Junit در Android test میپردازیم.
با ما همراه باشید.
دیدگاهتان را بنویسید
برای نوشتن دیدگاه باید وارد بشوید.