CodeGym /وبلاگ جاوا /Random-FA /همه چیز در مورد تست واحد: تکنیک ها، مفاهیم، تمرین
John Squirrels
مرحله
San Francisco

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

در گروه منتشر شد
امروز برنامه‌ای را پیدا نمی‌کنید که با آزمایش پوشیده نشده باشد، بنابراین این موضوع برای توسعه‌دهندگان مبتدی بیش از همیشه مرتبط خواهد بود: بدون آزمایش نمی‌توانید موفق شوید. بیایید در نظر بگیریم که اصولاً چه نوع آزمایشی مورد استفاده قرار می گیرد و سپس همه چیزهایی را که در مورد تست واحد باید بدانیم با جزئیات مطالعه خواهیم کرد. همه چیز در مورد تست واحد: تکنیک ها، مفاهیم، ​​تمرین - 1

انواع تست

تست چیست؟ طبق ویکی پدیا: "آزمایش نرم افزار شامل اجرای یک جزء نرم افزار یا جزء سیستم برای ارزیابی یک یا چند ویژگی مورد علاقه است." به عبارت دیگر، بررسی صحت سیستم ما در شرایط خاص است. خوب، بیایید ببینیم به طور کلی چه نوع آزمایشی وجود دارد:
  • تست واحد - تست هایی که هدف آن بررسی هر یک از ماژول های سیستم به طور جداگانه است. این آزمایش‌ها باید برای کوچک‌ترین بخش‌های اتمی سیستم، به عنوان مثال، ماژول‌ها اعمال شوند.
  • تست سیستم - تست سطح بالا برای بررسی عملکرد یک قطعه بزرگتر از برنامه یا سیستم به عنوان یک کل.
  • تست رگرسیون - آزمایشی که برای بررسی اینکه آیا ویژگی های جدید یا رفع اشکال بر عملکرد موجود برنامه تأثیر می گذارد یا اشکالات قدیمی را معرفی می کند، استفاده می شود.
  • تست عملکرد - بررسی اینکه آیا بخشی از برنامه الزامات ذکر شده در مشخصات، داستان های کاربر و غیره را برآورده می کند یا خیر.

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

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

  • تست عملکرد - تست هایی که برای تعیین نحوه عملکرد سیستم یا بخشی از سیستم تحت یک بار مشخص نوشته می شوند.
  • تست بار - آزمایش‌هایی که برای بررسی پایداری سیستم تحت بارهای استاندارد و یافتن حداکثر باری که برنامه هنوز در آن به درستی کار می‌کند طراحی شده است.
  • تست استرس - تست طراحی شده برای بررسی عملکرد برنامه تحت بارهای غیر استاندارد و تعیین حداکثر بار قبل از خرابی سیستم.
  • تست امنیتی - تست‌هایی که برای بررسی امنیت سیستم (از هکرها، ویروس‌ها، دسترسی غیرمجاز به داده‌های محرمانه و سایر حملات لذت‌بخش) استفاده می‌شوند.
  • تست محلی سازی - تست های محلی سازی برنامه.
  • تست قابلیت استفاده - آزمایشی با هدف بررسی قابلیت استفاده، قابل فهم بودن، جذابیت و یادگیری پذیری.
همه اینها خوب به نظر می رسد، اما در عمل چگونه کار می کند؟ ساده! ما از هرم آزمایشی مایک کوهن استفاده می کنیم: همه چیز در مورد تست واحد: تکنیک ها، مفاهیم، ​​تمرین - 2این یک نسخه ساده شده از هرم است: اکنون به بخش های کوچکتر نیز تقسیم شده است. اما امروز ما خیلی پیچیده نخواهیم شد. ما ساده ترین نسخه را در نظر خواهیم گرفت.
  1. واحد - این بخش به تست های واحد اشاره دارد که در لایه های مختلف برنامه اعمال می شود. آنها کوچکترین واحد قابل تقسیم منطق کاربردی را آزمایش می کنند. به عنوان مثال، کلاس ها، اما اغلب متدها. این تست‌ها معمولاً سعی می‌کنند تا حد امکان آنچه را که آزمایش می‌شود از هر منطق بیرونی جدا کنند. یعنی سعی می کنند این توهم را ایجاد کنند که بقیه برنامه طبق انتظار اجرا می شود.

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

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

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

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

    در تصویر بالا، می‌بینیم که اندازه‌های قسمت‌های مختلف مثلث متفاوت است: نسبت‌های تقریباً یکسانی در تعداد انواع مختلف آزمایش در کار واقعی وجود دارد.

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

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

پوشش تست (پوشش کد) یکی از معیارهای اصلی میزان تست یک برنامه کاربردی است. این درصد کدی است که تحت پوشش تست ها قرار می گیرد (0-100%). در عمل، بسیاری این درصد را به عنوان هدف خود دنبال می کنند. این چیزی است که من با آن مخالفم، زیرا به این معنی است که آزمایش‌ها در جایی که نیازی به آن‌ها نیست، شروع می‌شوند. به عنوان مثال، فرض کنید ما عملیات استاندارد CRUD (ایجاد/دریافت/به روز رسانی/حذف) را بدون منطق اضافی در سرویس خود داریم. این روش ها صرفاً واسطه هایی هستند که کار را به لایه ای که با مخزن کار می کند واگذار می کنند. در این شرایط، ما چیزی برای آزمایش نداریم، به جز اینکه آیا روش داده شده یک متد DAO را فراخوانی می کند، اما این یک شوخی است. معمولاً از ابزارهای اضافی برای ارزیابی پوشش آزمایشی استفاده می شود: JaCoCo، Cobertura، Clover، Emma، و غیره. برای مطالعه دقیق تر این موضوع، در اینجا چند مقاله مرتبط وجود دارد: TDD مخفف عبارت test-driven development است. در این روش، قبل از انجام هر کار دیگری، تستی می نویسید که کد خاصی را بررسی می کند. معلوم می شود که این آزمایش جعبه سیاه است: ما می دانیم ورودی است و می دانیم که خروجی باید چه باشد. این امکان جلوگیری از تکرار کد را فراهم می کند. توسعه مبتنی بر تست با طراحی و توسعه تست‌ها برای هر بیت از عملکرد برنامه شما شروع می‌شود. در رویکرد TDD، ابتدا تستی ایجاد می کنیم که رفتار کد را تعریف و آزمایش می کند. هدف اصلی TDD این است که کد شما قابل فهم تر، ساده تر و بدون خطا باشد. همه چیز در مورد تست واحد: تکنیک ها، مفاهیم، ​​تمرین - 3رویکرد شامل موارد زیر است:
  • ما آزمون خود را می نویسیم.
  • ما تست را اجرا می کنیم. جای تعجب نیست، زیرا ما هنوز منطق مورد نیاز را اجرا نکرده ایم، شکست می خورد.
  • کدی را که باعث گذراندن آزمون می شود اضافه کنید (آزمون را دوباره اجرا می کنیم).
  • ما کد را بازسازی می کنیم.
TDD بر اساس تست های واحد است، زیرا آنها کوچکترین بلوک های ساختمانی در هرم اتوماسیون تست هستند. با تست های واحد می توانیم منطق تجاری هر کلاسی را آزمایش کنیم. BDD مخفف توسعه رفتار محور است. این رویکرد مبتنی بر TDD است. به طور خاص، از مثال‌های زبان ساده‌ای استفاده می‌کند که رفتار سیستم را برای همه درگیر در توسعه توضیح می‌دهد. ما به این اصطلاح نمی پردازیم، زیرا عمدتاً آزمایش کنندگان و تحلیلگران تجاری را تحت تأثیر قرار می دهد. تست مورد سناریویی است که مراحل، شرایط خاص و پارامترهای مورد نیاز برای بررسی کد مورد آزمایش را توصیف می‌کند. فیکسچر تست کدی است که محیط تست را طوری تنظیم می کند که حالت لازم برای اجرای موفقیت آمیز روش مورد آزمایش را داشته باشد. این مجموعه از پیش تعریف شده از اشیاء و رفتار آنها در شرایط مشخص است.

مراحل تست

یک آزمون شامل سه مرحله است:
  • داده های تست (لوازم) را مشخص کنید.
  • کد مورد آزمایش را اعمال کنید (روش تست شده را فراخوانی کنید).
  • نتایج را بررسی کنید و با نتایج مورد انتظار مقایسه کنید.
همه چیز در مورد تست واحد: تکنیک ها، مفاهیم، ​​تمرین - 4برای اطمینان از ماژولار بودن تست، باید از لایه های دیگر برنامه جدا شوید. این را می توان با استفاده از خرد، تمسخر و جاسوس انجام داد. موک ها اشیایی هستند که می توان آنها را سفارشی کرد (به عنوان مثال، برای هر آزمون طراحی شده است). آنها به ما اجازه می دهند مشخص کنیم که از فراخوانی های متد چه انتظاری داریم، یعنی پاسخ های مورد انتظار. ما از اشیاء ساختگی استفاده می کنیم تا تأیید کنیم که آنچه را که انتظار داریم به دست آورده ایم. خرده‌ها در طول آزمایش، پاسخی با کد سخت به تماس‌ها ارائه می‌دهند. آنها همچنین می توانند اطلاعات مربوط به تماس (به عنوان مثال، پارامترها یا تعداد تماس ها) را ذخیره کنند. گاهی به اینها جاسوس نیز گفته می شود. گاهی اوقات افراد اصطلاحات خرد و مسخره را با هم اشتباه می گیرند: تفاوت این است که یک خرد چیزی را بررسی نمی کند - فقط یک حالت معین را شبیه سازی می کند. تمسخر شیئی است که انتظاراتی دارد. به عنوان مثال، اینکه یک متد معین باید چند بار فراخوانی شود. به عبارت دیگر، آزمون شما هرگز به دلیل یک خرد شکسته نمی شود، اما ممکن است به دلیل یک تمسخر باشد.

محیط های تست

بنابراین، در حال حاضر به نقطه. چندین محیط تست (فریم ورک) برای جاوا موجود است. محبوب ترین آنها JUnit و TestNG هستند. برای بررسی ما در اینجا، ما از این استفاده می کنیم: همه چیز در مورد تست واحد: تکنیک ها، مفاهیم، ​​تمرین - 5آزمون JUnit روشی در کلاس است که فقط برای آزمایش استفاده می شود. نام کلاس معمولاً همان کلاسی است که تست می‌کند و "Test" به انتهای آن اضافه می‌شود. به عنوان مثال، CarService -> CarServiceTest. سیستم ساخت Maven به طور خودکار چنین کلاس هایی را در محدوده آزمایش قرار می دهد. در واقع به این کلاس کلاس تست می گویند. اجازه دهید به طور خلاصه به حاشیه نویسی های اساسی بپردازیم:

  • @Test نشان می دهد که روش یک تست است (در اصل، روشی که با این حاشیه مشخص شده است یک تست واحد است).
  • @Before نشان دهنده روشی است که قبل از هر تست اجرا می شود. به عنوان مثال، برای پر کردن یک کلاس با داده های آزمایشی، خواندن داده های ورودی و غیره.
  • @After برای علامت گذاری روشی استفاده می شود که پس از هر آزمایش فراخوانی می شود (مثلاً برای پاک کردن داده ها یا بازیابی مقادیر پیش فرض).
  • @BeforeClass در بالای یک متد، مشابه @Before قرار می گیرد. اما چنین متدی فقط یک بار قبل از تمام تست های کلاس داده شده فراخوانی می شود و بنابراین باید ثابت باشد. از آن برای انجام عملیات با منابع فشرده تر، مانند چرخش یک پایگاه داده آزمایشی استفاده می شود.
  • @AfterClass برعکس @BeforeClass است: یک بار برای کلاس داده شده اجرا می شود، اما فقط پس از تمام تست ها. برای مثال برای پاک کردن منابع پایدار یا قطع ارتباط با پایگاه داده استفاده می شود.
  • @Ignore نشان می دهد که یک روش غیرفعال است و در طول اجرای آزمایشی کلی نادیده گرفته می شود. این در شرایط مختلف استفاده می شود، به عنوان مثال، اگر روش پایه تغییر کرده باشد و تست هنوز برای تطبیق با تغییرات دوباره کار نشده باشد. در چنین مواردی، اضافه کردن یک توضیحات، یعنی @Ignore ("Some description") نیز مطلوب است.
  • @Test(expected = Exception.class) برای تست های منفی استفاده می شود. اینها تست هایی هستند که بررسی می کنند روش در صورت بروز خطا چگونه رفتار می کند، یعنی آزمون انتظار دارد که متد نوعی استثنا ایجاد کند. چنین روشی با حاشیه نویسی @Test نشان داده می شود، اما با نشان دادن اینکه کدام خطا را باید گرفت.
  • @Test(timeout = 100) بررسی می کند که روش در حداکثر 100 میلی ثانیه اجرا شود.
  • @Mock در بالای یک فیلد برای اختصاص یک شی ساختگی استفاده می شود (این حاشیه نویسی JUnit نیست، بلکه از Mockito می آید). در صورت نیاز، رفتار ماکت را برای یک موقعیت خاص مستقیماً در روش تست تنظیم می کنیم.
  • @RunWith(MockitoJUnitRunner.class) بالای یک کلاس قرار می گیرد. این حاشیه نویسی به JUnit می گوید که تست های کلاس را فراخوانی کند. رانرهای مختلفی وجود دارد، از جمله این موارد: MockitoJUnitRunner، JUnitPlatform و SpringRunner. در JUnit 5، حاشیه نویسی @RunWith با حاشیه نویسی قدرتمندتر @ExtendWith جایگزین شده است.
بیایید نگاهی به چند روش مورد استفاده برای مقایسه نتایج بیندازیم:

  • assertEquals (Object مورد انتظار، Object actuals) - بررسی می کند که آیا اشیاء ارسال شده برابر هستند یا خیر.
  • assertTrue (پرچم بولی) - بررسی می کند که آیا مقدار ارسال شده درست است یا خیر.
  • assertFalse (پرچم بولی) - بررسی می کند که آیا مقدار ارسال شده نادرست است یا خیر.
  • assertNull (شیء شی) - بررسی می کند که آیا شیء ارسال شده تهی است یا خیر.
  • assertSame (Object firstObject، Object secondObject) - بررسی می کند که آیا مقادیر ارسال شده به یک شی اشاره دارد یا خیر.
  • ادعا می کند که (T t, Matcher همسان) - بررسی می کند که آیا t شرایط مشخص شده در matcher را برآورده می کند یا خیر.
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 - دونده ما. خط 4 - با جایگزین کردن یک ماک، سرویس را از لایه DAO جدا می کنیم. خط 11 - ما یک موجودیت آزمایشی (آنی که به عنوان خوکچه هندی استفاده خواهیم کرد) را برای کلاس تنظیم کردیم. خط 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 - رفتار را برای ساختگی DAO تنظیم کنید. خط 5 - نمونه ای را تنظیم کنید که در بالای استاندارد خود به روز می کنیم. خط 11 - از روش استفاده کنید و نمونه حاصل را بگیرید. خط 13 - بررسی کنید که پوچ نباشد. خط 14 - شناسه نتیجه و آرگومان های متد داده شده را با هم مقایسه کنید. خط 15 - بررسی کنید که آیا نام به روز شده است یا خیر. خط 16 - نتیجه CPU را ببینید. خط 17 - ما این فیلد را در نمونه مشخص نکردیم، بنابراین باید به همان صورت باقی بماند. ما آن شرایط را در اینجا بررسی می کنیم. بیایید آن را اجرا کنیم: همه چیز در مورد تست واحد: تکنیک ها، مفاهیم، ​​تمرین - 6تست سبز است! ما می توانیم نفس راحتی بکشیم :) به طور خلاصه، آزمایش کیفیت کد را بهبود می بخشد و روند توسعه را انعطاف پذیرتر و قابل اعتمادتر می کند. تصور کنید چقدر تلاش برای طراحی مجدد نرم افزار شامل صدها فایل کلاسی لازم است. وقتی برای همه این کلاس‌ها تست‌های واحد نوشته شده باشد، می‌توانیم با اطمینان اصلاح کنیم. و مهمتر از همه، به ما کمک می کند تا به راحتی باگ ها را در طول توسعه پیدا کنیم. بچه ها و دخترها، این تمام چیزی است که امروز دارم. لایک کن و نظر بده :)
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION