JavaRush /وبلاگ جاوا /Random-FA /تست واحد جاوا: تکنیک ها، مفاهیم، تمرین

تست واحد جاوا: تکنیک ها، مفاهیم، تمرین

در گروه منتشر شد
امروزه به سختی برنامه ای را پیدا خواهید کرد که با تست پوشانده نشده باشد، بنابراین این موضوع برای توسعه دهندگان مبتدی بیش از هر زمان دیگری مرتبط خواهد بود: بدون آزمایش نمی توانید به جایی برسید. به عنوان یک تبلیغ، پیشنهاد می کنم مقالات گذشته من را ببینید. برخی از آنها تست ها را پوشش می دهند (و حتی در این صورت مقالات بسیار مفید خواهند بود):
  1. تست یکپارچه سازی پایگاه داده با استفاده از MariaDB برای جایگزینی MySql
  2. اجرای برنامه چند زبانه
  3. ذخیره فایل ها در برنامه و داده های مربوط به آنها در پایگاه داده
بیایید در نظر بگیریم که اصولاً چه نوع آزمایشی مورد استفاده قرار می گیرد و پس از آن همه چیزهایی را که باید در مورد تست واحد بدانید را با جزئیات مطالعه خواهیم کرد.

انواع تست

تست چیست؟ همانطور که ویکی می گوید: " آزمایش یا تست روشی برای مطالعه فرآیندهای اساسی یک سیستم با قرار دادن سیستم در موقعیت های مختلف و ردیابی تغییرات قابل مشاهده در آن است." به عبارت دیگر، این آزمایش عملکرد صحیح سیستم ما در شرایط خاص است. همه چیز در مورد تست واحد: روش ها، مفاهیم، ​​تمرین - 2خوب، بیایید ببینیم چه نوع آزمایشی وجود دارد:
  1. تست واحد تست هایی است که وظیفه آنها آزمایش هر ماژول سیستم به صورت جداگانه است. مطلوب است که این قطعات حداقل قابل تقسیم از سیستم باشند، به عنوان مثال، ماژول ها.

  2. تست سیستم یک تست سطح بالا برای آزمایش عملکرد یک قطعه بزرگتر از یک برنامه کاربردی یا سیستم به عنوان یک کل است.

  3. تست رگرسیون آزمایشی است که برای بررسی اینکه آیا ویژگی‌های جدید یا رفع اشکال بر عملکرد موجود برنامه تأثیر می‌گذارد و آیا باگ‌های قدیمی دوباره ظاهر می‌شوند یا خیر استفاده می‌شود.

  4. تست عملکردی بررسی انطباق بخشی از برنامه با الزامات ذکر شده در مشخصات، داستان های کاربر و غیره است.

    انواع تست عملکردی:

    • تست "جعبه سفید" برای انطباق بخشی از برنامه با الزامات با دانش اجرای داخلی سیستم.
    • تست "جعبه سیاه" برای انطباق بخشی از برنامه با الزامات بدون اطلاع از اجرای داخلی سیستم.
  5. تست عملکرد نوعی تست است که برای تعیین سرعت اجرای یک سیستم یا بخشی از آن تحت یک بار مشخص نوشته می شود.
  6. تست بار - تست هایی که برای بررسی پایداری سیستم تحت بارهای استاندارد و یافتن حداکثر پیک ممکن که برنامه در آن به درستی کار می کند طراحی شده است.
  7. تست استرس نوعی آزمایش است که برای بررسی عملکرد یک برنامه کاربردی تحت بارهای غیر استاندارد و تعیین حداکثر پیک ممکن که در آن سیستم خراب نمی شود، طراحی شده است.
  8. تست امنیتی - تست هایی که برای بررسی امنیت یک سیستم (از حملات هکرها، ویروس ها، دسترسی غیرمجاز به داده های محرمانه و سایر لذت های زندگی) استفاده می شود.
  9. تست محلی سازی ، تست بومی سازی برای یک برنامه کاربردی است.
  10. تست کاربردپذیری نوعی تست با هدف بررسی قابلیت استفاده، قابل فهم بودن، جذابیت و یادگیری برای کاربران است.
  11. همه اینها خوب به نظر می رسد، اما در عمل چگونه کار می کند؟ ساده است: از هرم آزمایشی مایک کوهن استفاده می شود: همه چیز درباره تست واحد: روش ها، مفاهیم، ​​تمرین - 4این یک نسخه ساده شده از هرم است: اکنون به قسمت های کوچکتر تقسیم شده است. اما امروز ما منحرف نخواهیم شد و ساده ترین گزینه را در نظر می گیریم.
    1. تست های واحد - واحد مورد استفاده در لایه های مختلف برنامه، آزمایش کوچکترین منطق قابل تقسیم برنامه: به عنوان مثال، یک کلاس، اما اغلب یک روش. این تست‌ها معمولاً سعی می‌کنند تا حد امکان از منطق خارجی جدا شوند، یعنی این توهم ایجاد کنند که بقیه برنامه در حالت استاندارد کار می‌کند.

      همیشه باید تعداد زیادی از این تست ها (بیشتر از انواع دیگر) وجود داشته باشد، زیرا آنها قطعات کوچک را آزمایش می کنند و بسیار سبک وزن هستند و منابع زیادی را مصرف نمی کنند (منظور از منابع RAM و زمان است).

    2. ادغام - تست یکپارچه سازی. قطعات بزرگتر سیستم را بررسی می کند، یعنی یا ترکیبی از چندین قطعه منطق (چند روش یا کلاس)، یا صحت کار با یک جزء خارجی است. معمولاً تعداد این تست‌ها کمتر از تست‌های واحد است، زیرا سنگین‌تر هستند.

      به عنوان نمونه ای از تست های یکپارچه سازی، می توانید اتصال به پایگاه داده و بررسی اجرای صحیح روش های کار با آن را در نظر بگیرید .

    3. UI - تست هایی که عملکرد رابط کاربری را بررسی می کنند. آنها منطق را در تمام سطوح برنامه تحت تأثیر قرار می دهند، به همین دلیل است که به آنها end-to-end نیز می گویند. به عنوان یک قاعده، تعداد بسیار کمتری از آنها وجود دارد، زیرا آنها سنگین ترین وزن هستند و باید مسیرهای ضروری (استفاده شده) را بررسی کنند.

      در شکل بالا نسبت مساحت قسمت های مختلف مثلث را می بینیم: تقریباً همین نسبت در تعداد این تست ها در کار واقعی حفظ می شود.

      امروز ما نگاهی دقیق تر به تست های پرکاربرد - تست های واحد خواهیم داشت، زیرا همه توسعه دهندگان جاوا که به خود احترام می گذارند باید بتوانند از آنها در سطح پایه استفاده کنند.

    مفاهیم کلیدی تست واحد

    پوشش تست (Code Coverage) یکی از ارزیابی های اصلی کیفیت تست برنامه است. این درصد کدی است که توسط تست ها پوشش داده شده است (0-100٪). در عمل، بسیاری از مردم این درصد را دنبال می کنند، که من با آن موافق نیستم، زیرا آنها شروع به اضافه کردن تست ها در جایی که نیازی ندارند، می کنند. به عنوان مثال، سرویس ما دارای عملیات استاندارد CRUD (ایجاد/دریافت/به‌روزرسانی/حذف) بدون منطق اضافی است. این روش ها صرفاً واسطه هایی هستند که کار را به لایه ای که با مخزن کار می کند واگذار می کنند. در این شرایط، ما چیزی برای آزمایش نداریم: شاید این روش متدی را از تائو فراخوانی می کند، اما این جدی نیست. برای ارزیابی پوشش تست، معمولاً از ابزارهای اضافی استفاده می شود: JaCoCo، Cobertura، Clover، Emma و غیره. برای مطالعه دقیق تر این موضوع، چند مقاله مناسب را نگه دارید: TDD (توسعه مبتنی بر آزمایش) - توسعه آزمایش محور. در این رویکرد ابتدا تستی نوشته می شود که کد خاصی را بررسی می کند. معلوم می شود که آزمایش جعبه سیاه است: ما می دانیم در ورودی چه چیزی وجود دارد و می دانیم که در خروجی چه اتفاقی باید بیفتد. این از تکرار کد جلوگیری می کند. توسعه مبتنی بر تست با طراحی و توسعه تست‌ها برای هر عملکرد کوچک برنامه آغاز می‌شود. در رویکرد TDD، ابتدا تستی ایجاد می شود که مشخص می کند و تأیید می کند که کد چه کاری انجام خواهد داد. هدف اصلی TDD این است که کد را واضح تر، ساده تر و بدون خطا کند. همه چیز در مورد تست واحد: روش ها، مفاهیم، ​​تمرین - 6رویکرد شامل اجزای زیر است:
    1. ما در حال نوشتن آزمون خود هستیم.
    2. ما آزمایش را انجام می دهیم، چه قبول شده باشد یا نه (می بینیم که همه چیز قرمز است - نترسید: اینطوری باید باشد).
    3. ما کدی را اضافه می کنیم که باید این تست را برآورده کند (تست را اجرا کنید).
    4. ما کد را بازسازی می کنیم.
    بر اساس این واقعیت که تست های واحد کوچکترین عناصر در هرم اتوماسیون تست هستند، TDD بر اساس آنها است. با کمک تست های واحد می توانیم منطق تجاری هر کلاسی را آزمایش کنیم. BDD (توسعه مبتنی بر رفتار) - توسعه از طریق رفتار. این رویکرد مبتنی بر TDD است. به‌طور دقیق‌تر، از نمونه‌هایی استفاده می‌کند که به زبان واضح (معمولاً به زبان انگلیسی) نوشته شده‌اند که رفتار سیستم را برای همه افراد درگیر در توسعه نشان می‌دهد. ما به این اصطلاح نمی پردازیم، زیرا عمدتاً آزمایش کنندگان و تحلیلگران تجاری را تحت تأثیر قرار می دهد. Test Case - اسکریپتی که مراحل، شرایط خاص و پارامترهای لازم برای تأیید اجرای کد تحت آزمایش را توصیف می کند. فیکسچر وضعیتی از محیط آزمایش است که برای اجرای موفقیت آمیز روش مورد آزمایش ضروری است. این یک مجموعه از پیش تعیین شده از اشیاء و رفتار آنها در شرایط مورد استفاده است.

    مراحل تست

    آزمون شامل سه مرحله است:
    1. مشخص کردن داده هایی که باید آزمایش شوند (تجهیزات).
    2. استفاده از کد تحت آزمایش ( فراخوانی متد تحت آزمایش ) .
    3. بررسی نتایج و مقایسه آنها با نتایج مورد انتظار.
    همه چیز در مورد تست واحد: روش ها، مفاهیم، ​​تمرین - 7برای اطمینان از ماژولار بودن تست، باید از لایه های دیگر برنامه جدا باشید. این را می توان با استفاده از خرد، تمسخر و جاسوس انجام داد. موک ها اشیایی هستند که قابل تنظیم هستند (مثلاً مخصوص هر تست) و به شما امکان می دهند انتظاراتی را برای فراخوانی متدها در قالب پاسخ هایی که قصد دریافت آن ها را داریم تعیین کنید. بررسی های انتظار از طریق فراخوانی به اشیاء Mock انجام می شود. Stubs - پاسخ سیمی سخت به تماس ها در طول آزمایش ارائه می دهد. آنها همچنین می توانند اطلاعات مربوط به تماس (به عنوان مثال، پارامترها یا تعداد این تماس ها) را ذخیره کنند. اینها گاهی اوقات با اصطلاح خودشان - جاسوس ( جاسوس ) نامیده می شوند. گاهی اوقات این اصطلاحات stub و mock اشتباه گرفته می‌شوند: تفاوت این است که یک خرد چیزی را بررسی نمی‌کند، بلکه فقط یک حالت معین را شبیه‌سازی می‌کند. تمسخر شیئی است که انتظاراتی دارد. به عنوان مثال، اینکه یک متد کلاس معین باید چند بار فراخوانی شود. به عبارت دیگر، آزمون شما هرگز به دلیل یک خرد شکسته نمی شود، اما ممکن است به دلیل ساختگی خراب شود.

    محیط های تست

    خب حالا بیایید به کار بپردازیم. چندین محیط تست (فریم ورک) برای جاوا وجود دارد. محبوب ترین آنها JUnit و TestNG هستند. برای بررسی ما از این موارد استفاده می کنیم: همه چیز در مورد تست واحد: روش ها، مفاهیم، ​​تمرین - 8آزمون JUnit روشی است که در یک کلاس وجود دارد که فقط برای آزمایش استفاده می شود. یک کلاس معمولاً با کلاسی که آزمایش می کند با +Test در پایان نامگذاری می شود. به عنوان مثال، CarService → CarServiceTest. سیستم ساخت Maven به طور خودکار چنین کلاس هایی را در منطقه آزمایشی شامل می شود. در واقع به این کلاس کلاس تست می گویند. بیایید کمی حاشیه نویسی های اولیه را مرور کنیم: @Test - تعریف این روش به عنوان یک روش تست (در واقع روش مشخص شده با این حاشیه یک تست واحد است). @Before - متدی را که قبل از هر تست اجرا می شود علامت گذاری می کند. به عنوان مثال، پر کردن داده‌های آزمون کلاس، خواندن داده‌های ورودی و غیره @After - بالای متدی قرار می‌گیرد که بعد از هر آزمون فراخوانی می‌شود (پاک کردن داده‌ها، بازیابی مقادیر پیش‌فرض). @BeforeClass - در بالای روش قرار داده شده است - مشابه @Before. اما این متد فقط یک بار قبل از همه تست ها برای یک کلاس مشخص فراخوانی می شود و بنابراین باید ثابت باشد. برای انجام عملیات سنگین تر، مانند بلند کردن پایگاه داده آزمایشی استفاده می شود. @AfterClass برعکس @BeforeClass است: یک بار برای یک کلاس مشخص اجرا می شود، اما پس از تمام تست ها اجرا می شود. به عنوان مثال، برای پاکسازی منابع پایدار یا قطع ارتباط از پایگاه داده استفاده می شود. @Ignore - خاطرنشان می کند که روش زیر غیرفعال است و هنگام اجرای آزمایشات به طور کلی نادیده گرفته می شود. در موارد مختلفی استفاده می شود، مثلاً اگر روش پایه تغییر کرده باشد و زمانی برای انجام مجدد تست برای آن وجود نداشته باشد. در چنین مواردی، توصیه می‌شود که یک توضیح اضافه کنید - @Ignore ("برخی توضیحات"). @Test (مورد انتظار = Exception.class) - برای تست های منفی استفاده می شود. اینها تست‌هایی هستند که نحوه رفتار یک متد را در صورت بروز خطا بررسی می‌کنند، یعنی آزمون انتظار دارد که متد استثناهایی ایجاد کند. چنین روشی با حاشیه نویسی @Test نشان داده می شود، اما با یک خطا برای catch. @Test(timeout=100) - بررسی می کند که روش در حداکثر 100 میلی ثانیه اجرا شود. @Mock - یک کلاس روی یک فیلد برای تنظیم یک شی داده شده به عنوان mock استفاده می شود (این از کتابخانه Junit نیست، بلکه از Mockito است)، و اگر به آن نیاز داشته باشیم، رفتار mock را در یک موقعیت خاص تنظیم می کنیم. ، مستقیماً در روش تست. @RunWith(MockitoJUnitRunner.class) - متد بالای کلاس قرار می گیرد. این دکمه برای اجرای تست ها در آن است. Runner ها می توانند متفاوت باشند: به عنوان مثال، موارد زیر وجود دارد: MockitoJUnitRunner، JUnitPlatform، SpringRunner، و غیره). در JUnit 5، حاشیه نویسی @RunWith با حاشیه نویسی قدرتمندتر @ExtendWith جایگزین شد. بیایید به چند روش برای مقایسه نتایج نگاهی بیندازیم:
    • assertEquals(Object expecteds, Object actuals)- بررسی می کند که آیا اشیاء ارسال شده برابر هستند یا خیر.
    • assertTrue(boolean flag)- بررسی می کند که آیا مقدار ارسال شده درست است یا خیر.
    • assertFalse(boolean flag)- بررسی می کند که آیا مقدار ارسال شده false برمی گردد یا خیر.
    • assertNull(Object object)- بررسی می کند که آیا شیء تهی است یا خیر.
    • assertSame(Object firstObject, Object secondObject)- بررسی می کند که آیا مقادیر ارسال شده به یک شی اشاره دارد یا خیر.
    • assertThat(T t, Matcher<T> matcher)- بررسی می کند که آیا t شرایط مشخص شده در تطبیق را برآورده می کند یا خیر.
    همچنین یک فرم مقایسه مفید از assertj وجود دارد - assertThat(firstObject).isEqualTo(secondObject) در اینجا من در مورد روش های اساسی صحبت کردم، زیرا بقیه انواع مختلفی از موارد بالا هستند.

    تمرین تست

    حال بیایید با استفاده از یک مثال خاص به مطالب بالا نگاه کنیم. ما روش را برای سرویس آزمایش خواهیم کرد - به روز رسانی. ما لایه dao را در نظر نخواهیم گرفت، زیرا این لایه پیش فرض ما است. بیایید یک شروع کننده برای تست ها اضافه کنیم:
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <version>2.2.2.RELEASE</version>
       <scope>test</scope>
    </dependency>
    بنابراین، کلاس خدمات:
    @Service
    @RequiredArgsConstructor
    public class RobotServiceImpl implements RobotService {
       private final RobotDAO robotDAO;
    
       @Override
       public Robot update(Long id, Robot robot) {
           Robot found = robotDAO.findById(id);
           return robotDAO.update(Robot.builder()
                   .id(id)
                   .name(robot.getName() != null ? robot.getName() : found.getName())
                   .cpu(robot.getCpu() != null ? robot.getCpu() : found.getCpu())
                   .producer(robot.getProducer() != null ? robot.getProducer() : found.getProducer())
                   .build());
       }
    }
    8 - شی به روز شده را از پایگاه داده 9-14 بکشید - شی را از طریق سازنده ایجاد کنید، اگر شی ورودی دارای فیلد است - آن را تنظیم کنید، اگر نه - آنچه در پایگاه داده است را رها کنید و به تست ما نگاه کنید:
    @RunWith(MockitoJUnitRunner.class)
    public class RobotServiceImplTest {
       @Mock
       private RobotDAO robotDAO;
    
       private RobotServiceImpl robotService;
    
       private static Robot testRobot;
    
       @BeforeClass
       public static void prepareTestData() {
           testRobot = Robot
                   .builder()
                   .id(123L)
                   .name("testRobotMolly")
                   .cpu("Intel Core i7-9700K")
                   .producer("China")
                   .build();
       }
    
       @Before
       public void init() {
           robotService = new RobotServiceImpl(robotDAO);
       }
    1 - Runner 4 ما - با جایگزین کردن یک مدل ساختگی 11، سرویس را از لایه dao جدا کنید - یک موجودیت آزمایشی برای کلاس تنظیم کنید (آنی که به عنوان همستر آزمایشی استفاده خواهیم کرد) 22 - یک شی سرویس را تنظیم کنید که آن را آزمایش خواهیم کرد.
    @Test
    public void updateTest() {
       when(robotDAO.findById(any(Long.class))).thenReturn(testRobot);
       when(robotDAO.update(any(Robot.class))).then(returnsFirstArg());
       Robot robotForUpdate = Robot
               .builder()
               .name("Vally")
               .cpu("AMD Ryzen 7 2700X")
               .build();
    
       Robot resultRobot = robotService.update(123L, robotForUpdate);
    
       assertNotNull(resultRobot);
       assertSame(resultRobot.getId(),testRobot.getId());
       assertThat(resultRobot.getName()).isEqualTo(robotForUpdate.getName());
       assertTrue(resultRobot.getCpu().equals(robotForUpdate.getCpu()));
       assertEquals(resultRobot.getProducer(),testRobot.getProducer());
    }
    در اینجا ما شاهد تقسیم واضح تست به سه بخش هستیم: 3-9 - تنظیم فیکسچر 11 - اجرای قسمت آزمایش شده 13-17 - بررسی نتایج جزئیات بیشتر: 3-4 - تنظیم رفتار برای moka dao 5 - تنظیم یک نمونه که ما در بالای استاندارد 11 خود را به روز خواهیم کرد - از روش استفاده کنید و نمونه حاصل را 13 در نظر بگیرید - بررسی کنید که صفر نباشد 14 - شناسه نتیجه و آرگومان های متد مشخص شده را بررسی کنید 15 - بررسی کنید که آیا نام به روز شده است یا خیر 16 - نگاه کنید در نتیجه توسط cpu 17 - از آنجایی که ما این را در قسمت نمونه به‌روزرسانی تنظیم نکرده‌ایم، باید ثابت بماند، بیایید آن را بررسی کنیم. همه چیز در مورد تست واحد: روش ها، مفاهیم، ​​تمرین - 9بیایید راه اندازی کنیم: همه چیز در مورد تست واحد: تکنیک ها، مفاهیم، ​​تمرین - 10آزمایش سبز است، می توانید بازدم کنید)) بنابراین، بیایید خلاصه کنیم: آزمایش کیفیت کد را بهبود می بخشد و فرآیند توسعه را انعطاف پذیرتر و قابل اعتمادتر می کند. تصور کنید هنگام طراحی مجدد نرم افزار با صدها فایل کلاس چقدر باید تلاش کنیم. هنگامی که برای همه این کلاس ها تست های واحد نوشته شده است، می توانیم با اطمینان مجدداً آن را اصلاح کنیم. و مهمتر از همه، به ما کمک می کند تا به راحتی خطاها را در طول توسعه پیدا کنیم. بچه ها، امروز همه اینها برای من است: لایک کنید، نظر بنویسید))) همه چیز در مورد تست واحد: روش ها، مفاهیم، ​​تمرین - 11
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION