تست نویسی با Junit5 (بررسی فیچر ها و روش مهاجرت)

اگر از اون دسته اندروید دولوپر هایی باشید که تست مینویسن ، احتمالا دارید از Junit4 برای تست های خودتون استفاده میکنید ، نمیدونم ! شاید هم به Junit5 مهاجرت کرده باشید ، به هر حال اگر که میخواهید درباره Junit5 بیش تر بدونید تو این مقاله با من همراه باشید .راستی اگر مقاله من درمورد unit test نویسی رو هم نخوندید یه نگاهی بهش بندازید .?اصلا Junit چیه ؟در ابتدا اجازه دهید تا تعریفی از Junit داشته باشیم :جی یونیت یک فریمورک اپن سورس است که برای نوشتن و اجرای تست ها در زبان جاوا به کار میرود.به کمک Junit framework میتوانیم با روش علامت گذاری (annotation) ، تست متد های خود را بنویسیم و همچنین با استفاده از test runner های فراهم شده توسط فریمورک آن ها را اجرا کنیم .از مزایای این فریمورک میتوان به : سادگی و سهولت در استفاده افزایش سرعت اجرای تست ها و بالابردن کیفیت آن ها استفاده از مکانیسم assertions برای بررسی نتیجه تست هااجرای خودکار و همچنین ارائه گزارش (report) فوری از نتیجه ی تست ها همونطور که میدونید Junit ورژن های مختلفی داره که هر کدوم از این ورژن ها با سایر ورژن ها تفاوت هایی داره . جدید ترین نسخه این فریمورک نسخه 5 آن است به گفته توسعه دهندگان آن یک نسخه programmer-friendly از این فریمورک است.مقایسه Junit4 با Junit5 : اگر بخواهیم بین Junit4 و Junit5 مقایسه ای انجام بدهیم به طور کلی :تغییرات انوتیشن ها در ورژن 5 در مقایسه با ورژن 4:در Junit5 برخی از انوتیشن های کاربردی اضافه شده اند که در آینده به بررسی آن ها خواهیم پرداخت.تغییر در معماری Junit5 :در Juint4 همه چیز در یک jar file باندل شده است (در واقع شامل یک معماری مونولیتیک است)در حالی که Junit5 از یک معماری مدرن چند لایه بهره برده است و شامل 3 زیر ماژول زیر است:JUnit Jupiter - JUnit Platform - JUnit Vintageتبدیل شدن runner ها در Junit4 به extension ها در Junit5:در Junit4 به کمک رانر ها میتوانستیم فیچر ها و فانکشنالیتی های مد نظر خودمان را به Junit اضافه کنیم (مانند MockitoRunner یا SpringRunner) رانر ها در Junit4 از امکان رفلکشن استفاده می کردند .اما در Junit5 رانر ها به اکستنشن تبدیل شده اند .حذف Rule ها در Junit4 و تبدیل آن ها به extension در Junit5:رول ها کامپوننت هایی هستند که به کمک آن ها میتوانیم قبل یا بعد از اجرای یک تست یک عمل خاص را انجام بدهیم . (مانند InstantTaskExecutorRule)رول ها نیز در Junit5 به اکستنشن تبدیل شده اند .تغییر ورژن جاوای مورد نیاز :Junit 4 requires Java 5 or higher.Junit 5 requires Java 8 or higher.تغییر در Assertation ها :در Junit5 تعدادی assertion جدید نیز اضافه شده است .چرا باید به Junit5 مهاجرت کرد؟ مهم ترین دلیلی که میتونیم برای پاسخ به این سوال بیاریم اینه که در Junit5 امکاناتی مانند Repeated Tests, Dynamic Tests , Assumptions , Parameterized Tests , ... اضافه شدند که باعث سهولت ، کارایی و افزایش کیفیت تست ها میشن و کمک میکنن تا از تست نویسی لذت بیش تری ببریم ?همچنین معماری ماژولار به کار رفته در Junit5 باعث میشه تا این فریمورک از قابلیت بسط پذیری و کاستومایز بسیار بالاتری نسبت به ورژن های قبلی برخوردار بشه .به کمک همین معماری ماژولار است که Junit 5 میتواند تست های نوشته شده با Junit 4 را اجرا و از آن ها پشتیبانی کند(backward compatibility) که این مورد باعث میشود تا مهاجرت به آن نیز آسان شود.در Juint5 سعی شده تا به محدودیت ها و مشکلات Junit4 تا حد امکان توجه بشه و برای برطرف ساختن اون ها برنامه ریزی شده.تمامی این دلایل سبب شده تا Junit5 به یکی از ورژن های محبوب این فریمورک تبدیل بشه و تجربه ی جذابی رو برای توسعه دهنده هایی که ازش استفاده میکنن فراهم کنه.معماری Junit5 :یکی از موارد مهم در Juint5 آشنایی با معماری آن است . Junit5 از سه لایه (ماژول) تشکیل شده است:ماژولِ JUnit Platform (?) : این ماژول یک زیر ساخت بنیانی برای اجرا و لانچ تست ها در jvm فراهم می کند. به کمک Api های ارائه شده ، این ماژول به عنوان یک واسط بین Junit و client هایی مانند (build tools ها و ide ها) عمل میکند. در صورتی که در Juint4 کلاینت ها مجبور به استفاده از رفلکشن برای دسترسی به Junit بودند.همچنین این ماژول امکان کاستومایز کردن Test Engine را بر روی Junit Platform میدهد تا لایبرری های شخص ثالث مانند Spock, Cucumber, FitNesse بتوانند مستقیما با Junit در ارتباط باشند و Test Engine های مختص خودشان را ارائه دهند.تست انجین(Test Engine) برای کشف و اجرای تست ها به کار میرود.ماژولِ Junit Jupiter (?) : فیچر ها ، اکستنشن ها و انوتیشن های جدیدِ Junit5 در این ماژول فراهم شده است.همچنین این ماژول به طور پیشفرض از Test Engine ارائه شده در ماژول platform استفاده میکند.ماژول Junit Vintage (?) :به کمک Engine ارائه شده در این ماژول میتوانیم تست ها نوشته شده با junit3 و Junit4 را نیز اجرا کنیم (backward compatibility) بنابراین اگر از تست های Junit3 یا Junit4 استفاده نمیکنیم نیازی نیست تا dependency این ماژول را به پروژه خود اضافه کنیم.برای آشنایی بیشتر با ماژول ها و زیرماژول های Junit5 لایبرری آن را بررسی کنید.مهاجرت به Junit5 :برای اجرای تست های Junit5 در اندروید از یک گردل پلاگین استفاده میکنیم .ابتدا دیپندنسی پلاگین را به گردل (سطح پروژه) خود اضافه میکنیم :dependencies { classpath(&quotde.mannodermaus.gradle.plugins:android-junit5:1.8.2.0&quot) }همچنین در گردل (سطح ماژول) :plugins { id(&quotde.mannodermaus.android-junit5&quot) } dependencies { // (Required) Writing and executing Unit Tests on the JUnit Platform testImplementation(&quotorg.junit.jupiter:junit-jupiter-api:5.8.2&quot) testRuntimeOnly(&quotorg.junit.jupiter:junit-jupiter-engine:5.8.2&quot) // (Optional) If you need &quotParameterized Tests&quot testImplementation(&quotorg.junit.jupiter:junit-jupiter-params:5.8.2&quot) // (Optional) If you also have JUnit 4-based tests testImplementation(&quotjunit:junit:4.13.2&quot) testRuntimeOnly(&quotorg.junit.vintage:junit-vintage-engine:5.8.2&quot) // (Required) For Mockito in the JUnit Platform testImplementation(&quotorg.mockito:mockito-junit-jupiter:4.3.1&quot) }حالا به توضیح دیپندنسی ها خواهیم پرداخت:دیپندنسی (org.junit.jupiter:junit-jupiter-api:5.x.x) :به کمک jupiter api می توانیم از امکانات و انوتیشن های جدید Junit5 استفاده کنیم .دیپندنسی (org.junit.jupiter:junit-jupiter-engine:5.x.x) : برای کشف و اجرای تست ها به انجین Junit5 احتیاج داریم .دیپندنسی (org.junit.jupiter:junit-jupiter-params:5.x.x) : برای استفاده از پارامترایز تست ها که بعدا به توضیح آن خواهیم پرداخت به این دیپندنسی احتیاج داریم . اگر از این نوع تست ها استفاده نمیکنیم میتوانیم این دیپندنسی را اضافه نکنیم .دیپندنسی های (junit:junit:4.13.2 و org.junit.vintage:junit-vintage-engine:5.x.x) : در این جا دو سناریو مطرح است : اگر تست های خود را از ابتدا با Junit5 می نویسید احتیاجی به Junit4 و Vintage ندارید پس دیپندنسی های آن را اضافه نکنید .اما اگر تست های Junit4 را در کنار Junit5 مینویسید برای استفاده از امکانات Junit5 و همچنین اجرای تست های Junit4 به دیپندنسی Vintage احتیاج دارید .دیپندنسی (org.mockito:mockito-junit-jupiter:4.x.x) : اگر در پروژه تان از ماکیتو استفاده می کنید برای initialize کردن انوتیشن های ماکیتو به MockitoExtension احتیاج دارید . که این اکستنشن در این لایبرری موجود است .سایر دیپندنسی های ماکیتو :testImplementation(&quotorg.mockito:mockito-inline:x.x.x&quot) testImplementation(&quotorg.mockito:mockito-core:x.x.x&quot)حالا پروژه را سینک میکنیم تا دیپندنسی ها resolve بشوند.کار ما هنوز تمام نشده ? ممکن است بعد از اجرای اولین تست با ارور زیر مواجه بشویم :No tests found for given includes: [com.example.......XXXXX]خصوصا در مواردی که از parameterized test ها استفاده کنیم وقوع این ارور محتمل است. برای رفع آن باید یک تسک به تسک های گردل اضافه کنیم .بنابراین در گردل سطح ماژول کد زیر را اضافه میکنیم ://kotlin dsl plugins { } android { } tasks.withType<Test> { useJUnitPlatform() }//groovy gradle plugins { } android { } test { useJUnitPlatform() }حالا مجددا گردل را سینک میکنیم . تا این جا دیپندنسی های لازم را اضافه کرده ایم و Junit5 آماده است ، یک تست ساده را اجرا کنید اکنون نباید با اروری مواجه شوید .نوشتن اولین تست :در ابتدا همانطور که از مقاله قبل به خاطر دارید در Junit4 برای نوشتن تست ها از ساختار زیر استفاده می کردیم :@SmallTest @ExperimentalCoroutinesApi @RunWith(MockitoJUnitRunner::class) class viewModelTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() @get:Rule val coroutineTestRule = CoroutineTestRule() @Test fun test(){ //write your test } }اما همانطور که گفته شد در Junit5 رانر ها (RunWith) و رول ها (Rule)حذف و به اکستنشن تبدیل شده اند.بنابراین بازنویسی کد بالا در Juint5 به صورت زیر می شود:@SmallTest @ExperimentalCoroutinesApi @ExtendWith( InstantExecutorExtension::class, CoroutineTestExtension::class, MockitoExtension::class ) class viewModelTest{ @Test fun firstTest(){ //write first test } } همانطور که در کد بالا مشاهده می کنید برای رول های InstantTaskExecutorRule و CoroutineTestRule اکستنشن های مجزا نوشتیم و آن هارا در ExtendWith معرفی کرده ایم .اگر از کاربرد این رول ها مطلع نیستید مقاله قبل درمورد Unit Test ها را مطالعه کنید .برای نوشتن InstantExecutorExtension کلاس زیر را به پکیج تست خود اضافه کنید :import android.annotation.SuppressLint import androidx.arch.core.executor.ArchTaskExecutor import androidx.arch.core.executor.TaskExecutor import org.junit.jupiter.api.extension.AfterEachCallback import org.junit.jupiter.api.extension.BeforeEachCallback import org.junit.jupiter.api.extension.ExtensionContext class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback { @SuppressLint(&quotRestrictedApi&quot) override fun beforeEach(context: ExtensionContext?) { ArchTaskExecutor.getInstance() .setDelegate(object : TaskExecutor() { override fun executeOnDiskIO(runnable: Runnable) = runnable.run() override fun postToMainThread(runnable: Runnable) = runnable.run() override fun isMainThread(): Boolean = true }) } @SuppressLint(&quotRestrictedApi&quot) override fun afterEach(context: ExtensionContext?) { ArchTaskExecutor.getInstance().setDelegate(null) } } همچنین برای CoroutineTestExtension نیز به همین صورت عمل می کنیم :import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.TestCoroutineScope import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.setMain import org.junit.jupiter.api.extension.AfterEachCallback import org.junit.jupiter.api.extension.BeforeEachCallback import org.junit.jupiter.api.extension.ExtensionContext @ExperimentalCoroutinesApi class CoroutineTestExtension( private val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher() ) : BeforeEachCallback, AfterEachCallback, TestCoroutineScope by TestCoroutineScope(dispatcher) { override fun beforeEach(context: ExtensionContext?) { Dispatchers.setMain(dispatcher) } override fun afterEach(context: ExtensionContext?) { cleanupTestCoroutines() Dispatchers.resetMain() } } برای اضافه کردن این اکستنشن به دیپندنسی (کروتین - تست) احتیاج دارید :testImplementation(&quotorg.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3&quot)تا این جای کار اکستنشن ها را به برنامه خود اضافه کردیم حالا به سراغ MockitoExtension میرویم .این اکستنشن از دیپندنسی (org.mockito:mockito-junit-jupiter:4.x.x) که سابقا برای کانفیگ Junit5 به گردل اضافه کردیم در دسترس است .کاربرد این اکستنشن initialize کردن انوتیشن های ماکیتو است .اگر این اکستشن را به Junit اضافه نکنیم باید قطعه کد زیر را جایگزین آن کنیم:@BeforeEach fun setup(){ MockitoAnnotations.openMocks(this) }اکنون Junit آماده ی اجرای یونیت تست هاست .???در این جا لازم است به دو نکته توجه کنیم: برخی از انوتیشن های کاربردی در Junit5 تغییر کرده اند :بنابراین به جای استفاده از Before@ از BeforeEach@ استفاده می کنیم . متدی که با انوتیشن BeforeEach@ علامت گذاری می شود قبل از اجرای هر تست متد یکبار اجرا میشود .متدی که با انوتیشن AfterEach@ علامت گذاری می شود بعد از اجرای هر تست متد یکبار اجرا میشود.متدی که با انوتیشن BeforeAll@ علامت گذاری میشود قبل از اجرای همه ی تست متد ها یکبار اجرا میشود .متدی که با انوتیشن AfterAll@ علامت گذاری میشود بعد از اجرای همه ی تست متد ها یکبار اجرا میشود. دقت شود که هنگام استفاده از انوتیشن Test@ ، انوتیشن مربوط به jupiter api ایمپورت شود .اگر از Test@ موجود در ای پی ای junit4 (org.junit) استفاده شود .در صورتی که دیپندنسی Vintage را اضافه کرده باشیم تست ما با Junit4 اجرا خواهد شد . اما در غیر این صورت تست fail شده و از ما خواسته میشود تا Vintage را به دیپندنسی های خود اضافه کنیم .بنابراین اولین تست خود را به صورت زیر مینویسیم :import androidx.test.filters.SmallTest import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Assert import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.mockito.Mock import org.mockito.Mockito import org.mockito.junit.jupiter.MockitoExtension @SmallTest @ExperimentalCoroutinesApi @ExtendWith(MockitoExtension::class) class Junit5UnitTestExample { @Mock lateinit var list: List<String> companion object { const val fakeElement = &quotELEMENT&quot } @BeforeEach fun init(){ Mockito.`when`(list[0]).thenReturn(fakeElement) } @Test fun firstTest() { val firstItem:String=list[0] Assert.assertTrue( firstItem.startsWith(&quotE&quot) ) } } در این تستِ ساده با Mock@ یک لیست از نوع استرینگ را ماک کرده ایم و در متد init از ماکیتو خواسته ایم تا هر وقت عنصر 0 این ارایه خواسته شد استرینگ fakeElement را برای ما بازگرداند همچنین در firstTest بررسی کرده ایم که ایا fakeElement با کاراکتر E شروع شده است یا خیر .این تست به دلیل درستی شرط ( firstItem.startsWith("E") ) پاس میشود.نکته : در این تست کلاس ، اکستنشن های (CoroutineTestExtension و InstantExecutorExtension) را اضافه نکرده ایم زیرا از کروتین ها و یا کامپوننت هایی که ترد mian را کال میکنند استفاده نشده است. آشنایی با فیچر های Junit5 :حالا به سراغ بررسی امکانات جدید و جذاب Junit5 می رویم .1) Disabling Testsبه کمک انوتیشن Disabled@ میتوانیم تست کلاس ها یا تست متد های خود را ignore کنیم .کاربرد این انوتیشن در مواردی است که یک یا برخی از تست های ما به مشکل خورده اند اما تصمیم داریم تا آن هارا در آینده فیکس کنیم بنابراین به جای کامنت کردن تست ها آن هارا ignore میکنیم تا توسط Junit اجرا نشوند .همچنین در مواردی که از CI CD استفاده میکنیم ممکن است در برخی از موارد بخواهیم تست های مشکل دار را Skip کنیم تا بتوانیم ادامه ی pipeline را پیش برویم بنابراین میتوانیم از این امکان بهره ببریم.2) Conditional Test Executionگاهی از اوقات ممکن است احتیاج داشته باشیم تا برای اجرای تست ها شرایطی را تعیین کنیم تا درصورتی که آن شرط برقرار بود تست ما اجرا شود . Junit5 به طور پیشفرض برخی از این شرایط را تعریف کرده است :Operating System Conditionsبه کمک انوتیشن های EnableOnOs@ و یا DisableOnOs@ میتوانیم اجرای تست های خود را به یک یا چند سیستم عامل خاص محدود کنیم : Java Runtime Environment Conditionsبه کمک انوتیشن های EnabledOnJre@ و DisabledOnJre@ و EnabledForJreRange@ و DisabledForJreRange@ میتوانیم اجرا شدن تست ها را روی ورژن های مختلف Jre کنترل کنیم :Custom Conditionsدر Junit5 تست های شرطی دیگری نیز وجود دارند که به ذکر همه ی آن ها نپرداختیم ، اما نکته مهم این است که میتوانیم Custom Conditions های پیشفرض خود را بنویسم . به مثال زیر توجه کنید :3) Tagging and Filteringبه کمک انوتیشن Tag@ میتوانیم تست کلاس ها و تست متد های خود را تگ بزنیم . به کمک این تگ ها میتوانیم تست های خود را دسته بندی کنیم و یا حتی اجرای تست ها را روی تگ های خاص فیلتر کنیم .به کمک includeTags که به تسک Test در گردل اضافه شده است Junit فقط تست ها با تگ های مشخص شده را اجرا می نماید .4) Test Execution Orderگاهی ممکن است بخواهیم تا ترتیب مشخصی را برای اجرای تست متد های خود به کار ببریم ، به کمک انوتیشن TestMethodOrder(MethodOrderer.OrderAnnotation::class)@ در Junit5 این امکان فراهم شده است.انوتیشن های Order@ اولویت اجرای تست ها را مشخص میکنند . این امکان برای nested test ها نیز فراهم شده است فقط کافیست تا از انوتیشن (ClassOrder.OrderAnnotation::class)TestClassOrder@ استفاده کنیم. 5) Display Namesبه کمک این انوتیشن که برای تست کلاس ها و تست متد ها به کار می رود میتوانیم اسم تست ها را کاستومایز کنیم و یا کارکتر های خاص و ایموجی ها را به آن اضافه کنیم تا در ریپورت و اجرای تست ها نمایش داده شوند.از این امکان برای بالا بردن خوانایی و زیبایی تست های نوشته شده استفاده می شود . همچنین به کمک انوتیشن DisplayNameGeneration@ نیز میتوانیم اسامی تولید شده توسط تست ها را با شرایطی خاص کنترل کنیم .به طور نمونه در مثال زیر تمامی UnderScore ها را از اسامی تست ها با Space جایگزین کرده ایم :@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) public class Junit5_Diplay_Name_Generator_Test { @Test void test_Add() { assertEquals(5, MathUtil.add(3, 2)); } @Test void test_Multiply() { assertEquals(15, MathUtil.multiple(3, 5)); } @Test void test_Devide() { assertEquals(5, MathUtil.devide(25, 5)); } @Test void test_IsPrime() { assertTrue(MathUtil.isPrime(13)); } } همچنین Juint5 این امکان را فراهم کرده است تا بتوانیم DisplayNameGeneration ها را کاستومایز کرده و NameGeneration خودمان را بنویسیم .6) Assumptionsدر واقع Assumption ها ضمانتی بر اجرا یا عدم اجرای تست ها است . به کمک عبارات assumeTrue , assumeFalse , assumingThat درستی یک شرط را بررسی کرده ، اگر شرط برقرار بود تست اجرا میشود و در غیر این صورت تست fail نمیشود بلکه توسط جی یونیت abort می شودفرض کنید تستی را نوشته ایم که نباید در لوکال اجرا شود و فقط باید در سرور های CI اجرا شود.به کمک Assumptions میتوانیم اجرای این تست را مدیریت کنیم :@Test void testOnlyOnCiServer() { assumeTrue(&quotCI&quot.equals(System.getenv(&quotENV&quot))); // remainder of test } این تست فقط زمانی اجرا می شود که شرط داخل assumeTrue برقرار شود.اما آیا به کمک Custom Conditions ها که بالاتر به معرفی آن پرداختیم نمی توانستیم اجرای این تست را کنترل کنیم ؟بله می توانستیم !اما مثال زیر را در نظر بگیرید :میخواهیم تستی بنویسیم تا اگر در محیط سرور CI بودیم یک Assertation خاص را به اضافه Assertation ای که در سایر محیط ها اجرا می شود نیز اجرا کند .در این صورت فقط میتوانیم از امکان Assumption و متد assumingThat استفاده کنیم .7) Nested Testsبه کمک انوتیشن Nested@ می توانیم در یک تست کلاس ، تست کلاس های دیگری را (Nested) ایجاد کنیم.کاربرد این انوتیشن در مواردی است که میخواهیم تست های مشابه را گروه بندی کنیم و برای آن ها ساختار مشخصی ایجاد کنیم . به مثال زیر توجه کنید :8) Repeated Testsیکی از قابلیت های جالب Junit5 تست های تکرار شونده است. به کمک انوتیشن RepeatedTest@ میتوان یک تست را به تعداد دفعات مشخص شده تکرار کرد .این نوع از تست ها در مواردی که ممکن است یک عمل پس از چند بار اجرا ، نتیجه ی متفاوتی داشته باشد و یا در مواردی که اجرای تست ها به زمان سیستم وابستگی دارد کاربرد بسیار زیادی دارد.@RepeatedTest(5) public void testRepeatedTest() { System.out.println(&quotHello World&quot); }9) Parameterized Testsاین قابلیت کاربردی Junit5 باعث میشود تا بتوانیم یک تست را با آرگومان های متفاوت تکرار کنیم ??.برای ایجاد این تست ها باید تست متد را به جای Test@ با انوتیشن ParameterizedTest@ علامت گذاری کنیم .همچنین آرگومان های مورد نظر را با انوتیشن ValueSource@ مشخص می کنیم:@ValueSource(strings = { &quotracecar&quot, &quotradar&quot, &quotable was I ere I saw elba&quot })انوتیشن ValueSource از type های زیر پشتیبانی میکند :• short • byte • int • long • float • double • char • boolean • java.lang.Stringهمچنین باید در نظر داشته باشید که تست متد های ما در Parameterized Test ها باید ارگومانی از جنس type مشخص شده برای ValueSource داشته باشند :@ValueSource(ints = { 1, 2, 3 }) void testWithValueSource(int argument)@ValueSource(strings = { &quotracecar&quot, &quotradar&quot, &quotable was I ere I saw elba&quot }) void palindromes(String candidate)حالا به مثال زیر توجه کنید :این تست که palindrome بودن string ها را چک می کند ، 3 بار با مقادیر مشخص شده در ValueSource اجرا میشود .اگر بخواهیم تست های خود را با ارگومان های بیش تری اجرا کنیم به جای استفاده از ValueSource از CsvSource استفاده میکنیم .در CsvSource ارگومان ها در یک دابل کوتیشن و با علامت کاما از هم متمایز میشوند . به مثال زیر توجه کنید : در این مثال در هر اجرای تست متد ، آرگومان های زیر به fruit , rank داده میشود .10) Dynamic Testsتست های استانداردی که با انوتیشن Test@ علامت گذاری می شوند استاتیک هایی هستند که در زمان کامپایل به طور کامل مشخص می شوند .اما DynamicTest ها که با انوتیشن TestFactory@ علامت گذاری میشوند در زمان RunTime جنریت میشوند.هر تست متدی که با این انوتیشن علامت گذاری می شود باید یک Stream, Collection, Iterable, Iterator ای از نوع DynamicTest را به عنوان خروجی بر گرداند هر نوع خروجی ، غیر از موارد مشخص شده سبب وقوع یک JUnitException میشود زیرا خروجی های غیر مجاز در زمان کامپایل قابل شناسایی نیستند.اما کاربرد این گونه از تست ها چیست ؟ داینامیک تست ها در مقایسه با تست های استاندارد از انعطاف پذیری بالاتری در نحوه تولید ورودی ها و همچنین نحوه اجرای تست ها برخوردار است .به کمک این نوع از تست ها می توانیم در یک تست متد ، تست های متفاوت دیگری را اجرا کنیم .همچنین در مواردی که احتیاج داریم تا زمان اجرای تست ها را از زمان کامپایل به ران تایم ببریم از این تست ها استفاده میکنیم .بهره گیری از این تست ها سبب بهبود کیفیت تست ها و همچنین پرهیز از تکرار تست متد های مشابه می شود .?نحوه ی تعریف تست های داینامیک :در گام اول تست متد را به جای Test@ با TestFactory@ علامت می گذاریم .در گام دوم خروجی تست متد را ازtype های مجاز برای داینامیک تست ها مشخص میکنیم . به طور مثال :List<DynaimcTest>Collection<DynaimcTest>Stream<DynaimcTest> ....در گام سوم از متد استاتیک org.junit.jupiter.api.DynamicTest.dynamicTest برای ساختن داینامیک تست ها استفاده می کنیم . هر متد dynamicTest شامل دو قسمت است : یک String که نام تست را مشخص میکند دوم یک Executable که برای تعریف assertation به کار می رود .اکنون برای درک بیش تر موارد گفته شده مثال زیر را بررسی می کنیم :در این مثال در ابتدا از انوتیشن TestFactory برای علامت گذاری تست متد استفاده کرده ایم .سپس خروجی متد را لیستی از DynamicTest مشخص کرده ایم .حالا یک لیست شامل دو DynamicTest را بر گردانده ایم .هر داینامیک تست شامل یک اسم و یک کد اجرایی جهت مشخص کردن نتیجه تست است .بعد از اجرای تست متد ()testPointsDynamically ، دو داینامیک تست مخشص شده اجرا میشود و نتیجه ی آن ها در IDE نمایش داده میشود .در واقع با اجرای یک تست متد ، دو تست متفاوت با نتیجه های متفاوت را اجرا کردیم .حالا مثال های زیر را بررسی کنید :مراقب لایف سایکل DyanamicTest ها باشید? :لایف سایکل اجرای داینامیک تست ها با تست های استاندارد کاملا متفاوت است.به ازای هر dynamic test موجود در یک تست متد ، callback های لایف سایکل وجود ندارد .این بدان معناست که متد های BeforeEach@ و AfterEach@ فقط یکبار به ازای تست متدی که با انوتیشن TestFactory@ مشخص شده است اجرا می شوند نه به ازای هر dynamicTest به کار رفته در آن !الگوی اجرای تست های استاندارد و داینامیک به صورت زیر است :11) Timeoutsبه کمک انوتیشن Timeout@ می توانیم زمان مشخصی را برای اجرای تست ها مشخص کنیم تا اگر زمان اجرا از حد مجاز فراتر رفت تست fail شود.واحد زمان برای این انوتیشن به طور پیشفرض ثانیه است که این مورد به کمک پارامتر unit قابل تغییر است .مشخصا بررسی کامل Parameterized Tests و Dynamic Tests نیازمند دو مقاله جداگانه است ، اما در این جا جهت آشنایی و داشتن یک دید کلی از این فیچر ها و کاربردشان آن ها را بررسی کردیم .همچنین برای بررسی سایر امکانات به کار رفته در Junit5 به راهنمای کاربری آن مراجعه کنید. در این مقاله نیز برای بسیاری از مثال های به کار رفته از فیچر های Junit5 از مثال های راهنمای کاربری آن استفاده شد.اگر که هنوز از Junit4 استفاده می کنید امیدوارم تا با خوندن این مقاله علاقه مند به استفاده از Junit5 شده باشید .مرسی که تا آخر مقاله همراه من بودید .?