تست واحد در جاوا چیست؟
قبل از شروع به یادگیری JUnit در جاوا، اجازه دهید به طور خلاصه مرور کنیم که تست واحد چیست و چرا اینقدر محبوب است (اگر قبلاً این موارد را میدانید، به «چگونه یک تست JUnit در جاوا بنویسم؟» بروید. تست واحد در جاوا توسعه نرم افزار در مقیاس بزرگ را بسیار کارآمدتر و بدون دردسر می کند. این می تواند به افراد و تیم ها کمک کند تا ساعت های بی شماری از اشکال زدایی را کاهش دهند و روند همکاری را بسیار ساده کند.
https://junit.org/junit4/
ایده اصلی آزمایش واحد این است: تست های اتمی ویژگی های فردی را بنویسید (به نام تست های واحد) و به آرامی ویژگی های بیشتری را پس از آزمایش و اطمینان از کارکرد موارد قبلی اضافه کنید. این یک ایده بسیار ساده اما قدرتمند است. به عنوان مثالی از نحوه ظاهر این فرآیند، تصور کنید که در حال ساخت یک ماشین حساب علمی مجازی هستید. علاوه بر عملگرهای محاسباتی ظاهری (
+
,
-
,
x
,
%
)، این ماشین حساب دارای ویژگی های پیشرفته ای است که برای کارکردن در آن به ویژگی های فرعی دیگری نیاز دارد. برای محاسبه توان، ماشین حساب شما باید بتواند به درستی ضرب کند. بنابراین یک رویکرد تست واحد برای ساخت و آزمایش این ماشین حساب به این صورت خواهد بود:
- یک تابع جمع بنویسید. آن را با دقت تست کنید، آن را تغییر دهید، تکرار کنید تا زمانی که کار کند.
- همین کار را برای توابع تفریق، ضرب، تقسیم انجام دهید.
- از این عملگرهای پایه برای نوشتن توابع عملگر پیشرفتهتر مانند توان استفاده کنید، سپس آن توابع را نیز آزمایش کنید.
این تضمین میکند که ویژگیهایی که سایر ویژگیهای فرعی کوچکتر را ایجاد میکنند، نه تنها به درستی کار میکنند، بلکه ویژگیهای فرعی معیوب هم ندارند. به عنوان مثال، اگر من تابع توان را آزمایش میکنم و مشکلی پیش میآید، میدانم که اشکال احتمالاً در ویژگی فرعی ضرب نیست، زیرا تابع ضرب قبلاً به طور گسترده آزمایش شده است. این به میزان زیادی کل کدی را که برای یافتن باگ نیاز به بازرسی و بازرسی دارم حذف می کند. امیدواریم، این مثال بیاهمیت نشان دهد که فرآیند فکری پیرامون تست واحد چگونه ساختار یافته است. اما چگونه تست واحد با بقیه فرآیند توسعه نرم افزار تعامل دارد؟ اگر ویژگیهای پیچیدهتری داشته باشید، که باید بتوانید با هم کار کنید و ارتباط برقرار کنید؟ تست واحد برای اطمینان از اینکه چنین ویژگی های پیچیده ای می توانند به درستی با هم کار کنند کافی نیست. در واقع، این فقط اولین مرحله از چهار سطح تست نرم افزار است (من از حروف بزرگ استفاده می کنم زیرا به استاندارد صنعت یا رایج ترین رویکرد برای تست نرم افزار اشاره می کنم). سه مرحله آخر عبارتند از
تست یکپارچه سازی ,
تست سیستم و
تست پذیرش . همه اینها احتمالاً دقیقاً به معنای همان چیزی است که شما فکر می کنید انجام می دهند، اما اجازه دهید توضیح دهم: تست یکپارچه سازی کاری است که ما انجام می دهیم تا اطمینان حاصل کنیم که موارد ذکر شده در بالا، "ویژگی های پیچیده" به درستی با یکدیگر تعامل دارند. (به عنوان مثال، اطمینان از اینکه ماشین حساب می تواند "3 + 7 * 4 - 2" را کنترل کند) آزمایش سیستم، طراحی کلی یک سیستم خاص را آزمایش می کند. اغلب سیستمهای متعددی از ویژگیهای پیچیده با هم در یک محصول کار میکنند، بنابراین شما آنها را در سیستمها گروهبندی کرده و آنها را بهصورت جداگانه آزمایش میکنید. (مثلاً اگر در حال ساختن یک ماشینحساب نموداری بودید، ابتدا «سیستم» حسابی را میسازید تا با اعداد سروکار داشته باشد، آزمایش کنید تا آنطور که در نظر گرفته شده عمل کند، و سپس «سیستم» نموداری را بسازید و آزمایش کنید تا با برداشت مواجه شوید، از سیستم حسابی ساخته می شود). تست پذیرش یک تست در سطح کاربر است. این نشان می دهد که آیا همه سیستم ها می توانند برای ایجاد یک محصول نهایی آماده برای پذیرش توسط کاربران (مثلاً کاربرانی که ماشین حساب را آزمایش می کنند) هماهنگ کار کنند. توسعهدهندگان نرمافزار گاهی اوقات میتوانند این مرحله نهایی فرآیند را نادیده بگیرند، زیرا شرکتها اغلب از کارکنان دیگر میخواهند که آزمایشهای کاربر (بتا) را به طور جداگانه اجرا کنند.
چگونه یک تست JUnit در جاوا بنویسم؟
اکنون که ایده واضح تری از مزایا و محدودیت های تست واحد دارید، بیایید نگاهی به کدهایی بیندازیم! ما از یک چارچوب آزمایشی محبوب جاوا به نام JUnit استفاده خواهیم کرد (یکی دیگر از موارد محبوب TestNG است که در صورت تمایل می توانید از آن استفاده کنید. آنها از نظر نحوی بسیار شبیه هستند؛ TestNG از JUnit الهام گرفته شده است). می توانید JUnit را
از اینجا
دانلود و نصب کنید . برای این کد مثال، ما به مثال "ماشین حساب علمی" که قبلا ذکر کردم ادامه می دهیم. پیچاندن سر در اطراف بسیار ساده است و کد تست بسیار آسان است. تمرین مرسوم این است که برای هر یک از کلاس های خود کلاس های آزمایشی جداگانه بنویسید، بنابراین این کاری است که ما انجام خواهیم داد. بیایید فرض کنیم که در این مرحله، ما یک
Math.java
فایل با تمام توابع ریاضی در آن (از جمله
Math.add
) داریم و در حال نوشتن یک
MathTests.java
فایل در همان بسته هستیم. اکنون بیایید دستورهای import و بدنه کلاس را تنظیم کنیم: (سوال مصاحبه احتمالی JUnit: ممکن است از شما بپرسند که آزمون JUnit خود را کجا قرار دهید و آیا نیاز به وارد کردن فایل های منبع خود دارید یا خیر. اگر کلاس های آزمایشی خود را در همان بسته می نویسید. کلاس های اصلی خود را، پس در کلاس آزمایشی به هیچ عبارت import برای فایل های منبع خود نیاز ندارید. در غیر این صورت، مطمئن شوید که فایل های منبع خود را وارد می کنید!)
import org.junit.jupiter.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MathTests {
}
اولین دستور import
@Test
هدر را به ما می دهد.
@Test
ما " " را مستقیماً در بالای هر تعریف تابع تست می نویسیم ، به طوری که JUnit بداند که این یک تست واحد منفرد است که می تواند جداگانه اجرا شود. بعداً، به شما نشان خواهم داد که چگونه می توانید تست های واحد خاص را با استفاده از این هدر اجرا کنید. عبارت import دوم کمی ما را از تایپ صرفه جویی می کند. تابع JUnit اولیه که ما برای آزمایش توابع خود استفاده میکنیم، این است
Assert.assertEquals()
که دو پارامتر (مقدار واقعی و مقدار مورد انتظار) را میگیرد و از برابری آنها مطمئن میشود.
assertEquals(...
داشتن این عبارت import دوم به ما اجازه میدهد تا به جای اینکه هر بار مشخص کنیم که بخشی از کدام بسته است، «» را تایپ کنیم . حالا بیایید یک تست بسیار ساده بنویسیم تا بررسی کنیم که 2 + 2 واقعاً 4 است!
import org.junit.jupiter.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MathTests {
@Test
public void add_twoPlusTwo_returnsFour(){
final int expected = 4;
final int actual = Math.add(2, 2);
assertEquals(“2+2 is 4”, actual, expected);
}
}
اجازه دهید به بررسی هر یک از پنج خط تابع تست و کارهایی که انجام میدهند بپردازیم: خط 5: این
@Test
هدر مشخص میکند که تعریف تابع زیر
add_twoPlusTwo_returnsFour()
در واقع یک تابع آزمایشی است که JUnit میتواند به طور جداگانه اجرا کند. خط 6: این امضای تابع برای مورد آزمایشی ما است. موارد آزمون همیشه بسیار منحصر به فرد هستند. آنها فقط یک مثال خاص را آزمایش می کنند، مانند 2+2=4. این یک قرارداد است که موارد آزمایشی خود را به شکل "
[function]_[params]_returns[expected]()
" نامگذاری کنید، جایی که
[function]
نام تابعی است که آزمایش می کنید،
[params]
پارامترهای مثال خاصی است که آزمایش می کنید، و
[expected]
مقدار بازگشتی مورد انتظار تابع است. توابع تست تقریبا همیشه یک نوع بازگشتی از "
void
" دارند، زیرا نکته اصلی کل تابع اجرای است
assertEquals
، که چه تست شما قبول شود یا نه، به کنسول خروجی میدهد. شما به هیچ داده دیگری برای بازگرداندن در هر جایی نیاز ندارید.
final
خط 7: یک متغیر ' ' از نوع بازگشتی را اعلام می کنیم
Math.add (int)
و طبق قرارداد آن را 'expected' می نامیم. مقدار آن پاسخی است که ما انتظار داریم (4).
final
خط 8: یک متغیر ' ' از نوع بازگشتی را اعلام می کنیم
Math.add (int)
و طبق قرارداد آن را 'واقعی' می نامیم. ارزش آن حاصل
Math.add(2, 2)
. خط 9: خط طلایی. این خطی است که واقعی و مورد انتظار را با هم مقایسه میکند و به ما میگوید که فقط در صورت مساوی بودن در آزمون قبول شدهایم. اولین پارامتری که "2+2 is 4" است توضیحی از تابع تست است.
اگر تابع من باید یک استثنا ایجاد کند چه؟
اگر نمونه آزمایشی خاص شما باید به جای بیان اینکه یک مقدار واقعی و مورد انتظار برابر است، یک استثنا ایجاد کند، JUnit راهی برای روشن کردن این موضوع در هدر دارد
@Test
. بیایید به مثال زیر نگاهی بیندازیم. با فرض اینکه تابعی به
Math.java
نام داریم
Math.divide
، میخواهیم مطمئن شویم که ورودیها را نمیتوان بر 0 تقسیم
Math.divide(a, 0)
کرد
ArithmeticException.class
. در هدر به این صورت مشخص می کنیم:
import org.junit.jupiter.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MathTests {
@Test (expectedExceptions = ArithmeticException.class)
public void divide_byZero_throwsException() throws ArithmeticException{
Math.divide(1, 0);
}
}
شما می توانید بیش از یک استثنا برای
expectedExceptions
, فقط مطمئن شوید که از براکت و کاما برای لیست کردن کلاس های استثنای خود استفاده کنید، به این صورت:
expectedException = {FirstException.class, SecondException.class, … }
چگونه تست های JUnit خود را در جاوا اجرا کنم؟
نحوه اضافه کردن JUnit به IntelliJ:
https://stackoverflow.com/questions/19330832/setting-up-junit-with-intellij-idea
می توانید پروژه خود را همانطور که معمولاً آزمایش ها را اجرا می کنید اجرا کنید. اجرای تمام تست ها در یک کلاس آزمایشی، آنها را به ترتیب حروف الفبا اجرا می کند. در JUnit 5 می توانید با اضافه کردن یک
@Order
برچسب، اولویت را به تست ها اضافه کنید. یک مثال:
@TestMethodOrder(OrderAnnotation.class)
public class Tests {
…
@Test
@Order(2)
public void a_test() { … }
@Test
@Order (1)
public void b_test() { … }
…
}
حتی اگر بر اساس حروف الفبا و کد
a_test()
قبل از آن باشد ، در اینجا قبل از آن اجرا می شود ، زیرا به ترتیب 1 قبل از 2 قرار می گیرد. بنابراین این در مورد اصول اولیه JUnit است. اکنون، بیایید به چند پرسش رایج در مصاحبه JUnit بپردازیم و در طول مسیر، اطلاعات بیشتری درباره JUnit بیاموزیم!
b_test()
b_test()
a_test()
سوالات مصاحبه JUnit (اطلاعات تکمیلی)
در اینجا من محبوب ترین سوالات مصاحبه JUnit را جمع آوری کرده ام. اگر چیزی برای اضافه کردن دارید - این کار را در نظرات زیر انجام دهید.
س: چه روشی را می توانید در روش آزمون خود فراخوانی کنید تا به طور خودکار در یک آزمون شکست بخورید؟ A: شکست ("شرح خطا در اینجا!")؛ س: شما در حال آزمایش یک کلاس سگ هستید. برای آزمایش یک شی Dog، قبل از اینکه بتوانید آزمایشهایی روی آن انجام دهید، باید آن را نمونهسازی کنید. بنابراین شما یک تابع setUp() برای نمونه سازی Dog می نویسید. شما فقط می خواهید این تابع را یک بار در طول تمام آزمایش اجرا کنید. چه چیزی را باید مستقیماً بالای امضای تابع setUp() قرار دهید تا JUnit بداند که setUp() را قبل از اجرای تست ها اجرا کند؟ A: @BeforeClass (@BeforeAll در JUnit 5) س: امضای تابع تابع setUp() که در بالا توضیح داده شد چه باید باشد؟ الف: خلأ استاتیک عمومی. هر تابع با @BeforeClass (@BeforeAll در JUnit 5) یا @AfterClass (@AfterAll در JUnit 5) باید ثابت باشد. س: شما آزمایش کلاس سگ را تمام کرده اید. شما تابع void tearDown() را می نویسید که داده ها را پاک می کند و پس از هر تست اطلاعات را در کنسول چاپ می کند. شما می خواهید این تابع بعد از هر آزمایش اجرا شود. چه چیزی را باید مستقیماً بالای امضای تابع ()tearDown قرار دهید تا JUnit بداند پس از اجرای هر آزمایش، tearDown () را اجرا کند؟ A: @After (@AfterEach در JUnit 5)
GO TO FULL VERSION