بنام خدا
در این سری تست نویسی قراره چند تا از best practice های تست نویسی در جاوا و spring رو با هم مرور کنیم. پس اگر این سری براتون مفید بود ممنون میشم که پست هارو لایک کنید و به اشتراک بگذارید
شاید اولش مثل من بگین که خب من که میدونم برنامم چطور کار میکنه، میدونم که فلان متد چه مقداری میگیره و چه مقداری برمیگردونه دیگه نیازی نیست خودمو به زحمت بندازم و بیام براش یه عالمه کد دیگه بنویسم که رفتار کد اصلیم رو تحلیل و بررسی کنه.
در واقع وقتی برنامه شما به حالت پروداکشن میره، ورودی هایی ممکنه به برنامه داده بشه که شما این ورودی ها رو پیش بینی نکرده بودین و فرض رو بر این گذاشته بودین که همیشه ورودی های درست به برنامه شما بیاد. یا اگرم پیش بینی کرده باشین، در نهایت با یه خطای نامفهوم به کاربر ریسپانس رو برمیگردانید که ممکنه این بازخورد اطلاعاتی مربوط به دیتابیس و چیزهای دیگه ای باشه که فقط برای شمای برنامه نویس فراهم شده نه برای کاربران، شما در واقع باگ فیکس نکرده اید، بلکه باگ رو پنهان کرده اید یا در حالت دوم، باگ بدتری ایجاد کرده اید.
یک مزیت دیگه این هست که هر تغییری در کد خود ایجاد کردید، دیگه لازم نیست برنامه رو استارت کنید و بصورت دستی تست کنید تا ببینید این تغییرات جدید، کد قبلی رو break کرده یا نه. فقط کافیه تست هایی که برای کد قبلی نوشتین رو اجرا کنین و هر وقت fail شد میفهمید که مشکل از کجاس و اینطوری سرعت توسعه و استحکام اپلیکیشن بالا میره
حالا که به اهمیت تست نرم افزار آگاه شدیم بریم با فریمورک هایی که برای اینکار در جاوا ساخته شده آشنا بشیم:
1 - Junit
2 - TestNG
3 - Selenide
4 - Spock
...
در مقاله در مورد Junit و نسخه ۵ ش حرف خواهیم زد.
نکته جالب در مورد Selenide این هست که میتونید به عنوان crawler هم ازش استفاده کنید و بقولی در وب سایت ها بخزید!
برای ایجاد پروژه میتونید از ide و کد ادیتور محبوب خودتون و build tool دلخواه استفاده کنید
اضافه کردن کتابخونه junit 5 :
gradle:
testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2'
maven:
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency>
تقریبا این فریمورک در هر پروژه جاوا توسط IDE ها یا project generator ها بصورت پیشفرض در وابستگی ها یا دپندنسی های پروژه اضافه میشود. فرض کنید همچین کلاسی در برنامه داریم:
public class App { public String getGreeting() { return "Hello World!" } public int division(int a, int b) { return a / b; } }
الان میخواهیم این دو تا متد این کلاس رو تست کنیم. برای اینکار لازمه یک کلاس تست در مسیر src/main/test/java/package_name ایجاد کنیم. معمولا اسم کلاس تست رو با کلاس که میخواهیم متد هاش رو تست کنیم همنام + Test قرار میدیم که در این مثال خاص به این صورت خواهد بود AppTest
برای تست واحد متد های کلاسمون میتونیم یک متد void با همان نام متد در کلاس اصلی بسازیم و با انوتیشن @Test اون رو به junit بفهمانیم که یک متد تست هست:
import static org.junit.jupiter.api.Assertions.*; class AppTest { @Test void getGreeting() { var classUnderTest = new App(); assert classUnderTest.getGreeting() != null; assertEquals("Hello, World!", classUnderTest.getGreeting()); } }
در این مثال ما اول اومدیم یک آبجکت از کلاس اصلی مون ساختیم که به متد هاش دسترسی داشته باشیم. در خط بعدی از کلیدواژه assert جاوا استفاده کردیم که یک مقدار boolean رو بررسی میکنه و اگر false بود، تست رو در اصطلاح fail میکنه و در لاگ ارور AssertionError رو مشاهده خواهیم کرد. این کلیدواژه در اصل بیسیک ترین روش تست هست.
در خط بعدی متد استاتیک assertEquals از کلاس Asserstions که استاتیک امپورت کرده ایم که دو آرگومان میگیرد اولی مقداری هست که ما انتظار داریم متد برگرداند، دومی هم مقداری است که متد برمیگرداند. در ادامه از متدهای استاتیک این کلاس خیلی استفاده خواهیم کرد. این کلاس پر از متد هایی برای تست کردن عملکرد اجزای برنامه هست که میتونید خود کلاس رو باز کنید و با متدهاش آشنا بشین و جاواداک هایی خوبی هم نوشتن که بهتون میگه نحوه استفادشون چجوریه.
در این تست ساده اول چک کردیم که متد ما null برنگرداند، دوم مقدار آن را بررسی کردیم.
بریم برای تست متد دوم:
@Test void division() { var classUnderTest = new App(); assertEquals(2, classUnderTest.division(4, 2)); } @Test void divisionByZero() { var classUnderTest = new App(); classUnderTest.division(4, 0); }
از آنجایی که متد ما میتونه دو تا حالت داشته باشه، یا مقداری برگردونه یا یک استثنا پرتاب کنه، پس ما دو متد تست براش مینویسیم که همه این حالت هارو پوشش بده.
تست اول که تا اینجای کار باید بدونید چیکار میکنه اما بازم توضیح میدم: اول یک آبجکت از کلاس تحت تست رو ایجاد میکنیم و با متد استاتیک assertEquals، چیزی که از مقدار بازگشتی این متد انتظار داریم را در آرگومان اول و خود فراخونی متد رو در آرگومتن دوم با ورودی هایی که انتظار داریم در آخر برابر با مقدار آرگومان اول باشد رو وارد میکنیم.
در تست دوم ما از عمد مقدار دوم آرگومان متد رو صفر گذاشتیم که برنامه به مشکل بخورد و ببینیم که توی کد اصلی راهکاری برای این مشکل اندیشیده ایم یا نه. اگر تست رو اجرا کنیم،تست بلافاصله با استثنای ArithmeticException شکست میخورد یا fail میشود. اینجاس که ما به یه مشکل در کد اصلی پی بردیم و میخواهیم که در این مثال بخصوص یک استثنای مناسب قبل از انجام عمل تقسیم به کاربر نشون بدهیم. کد اصلی رو به این صورت در میاوریم:
public int division(int a, int b) { if (b == 0) throw new IllegalArgumentException("Division by zero"); return a / b; }
و تست را به اینصورت بازنویسی میکنیم:
@Test void divisionByZero() { var classUnderTest = new App(); assertThrows(IllegalArgumentException.class, () -> classUnderTest.division(4, 0)); }
ایمجا اومدیم از متد استاتیک assertThrows استفاده کردیم که در آرگومان اولش نوع اکسپشنی که انتظار داریم این متد ما پرتاب کنه رو نوشتیم و در آرگومان دوم یک Executable که عمکردش عینا شبیه Runnable هست با این تفاوت که در این اینترفیس فانکشنال، متد execute در این اینترفیس throwable پرتاب میکند. این اینترفیس رو رو بصورت مدل لامبدا وارد میکنیم و داخل این لامبدا متد مورد نظر رو فراخونی کردیم اما اینبار با مقدار ۴ و ۰ چون تقسیم عدد بر صفر بینهایت هست و ما در متد اصلی این استثنا رو در صورتی پرتاب میکنیم که مقدار دوم صفر باشد. در این حالت ما برای متد اصلی مان چاره ای اندیشیده ایم و تست ما pass میشود
در خصوص junit چندتا انوتیشن های پرکابرد داریم که یکیش همین Test هست:
@BeforeEach @AfterEach @BeforeAll @AfterAll
شاید از اسم هاشون متوجه بشید که چه کاری میکنند. تمام این انوتیشن ها برای متد های void زده میشن که AfterAll و BeforeAll برای متد های static void هستن. کاربرد دوتای اول برای اجرای کد قبل و بعد هر تست هست و دوتای بعدی فقط یکبار اونم قبل و بعد از همه تست ها اجرا میشن
برای آشنایی بیشتر با انوتیشن ها و قابلیت های دیگه junit میتوانید به یک عالمه ویدیو و مقاله های آموزشی در اینترنت دسترسی داشته باشید که هزینه اش فقط یک سرچ ساده هست
لینک این پروژه در گیتهاب : LINK
اگر نظری، پیشنهادی، اصلاحی دارید، لطفا در کامنت ها من و بقیه رو مطلع کنید ?