اگر از اون دسته اندروید دولوپر هایی باشید که تست مینویسن ، احتمالا دارید از Junit4 برای تست های خودتون استفاده میکنید ، نمیدونم ! شاید هم به Junit5 مهاجرت کرده باشید ، به هر حال اگر که میخواهید درباره Junit5 بیش تر بدونید تو این مقاله با من همراه باشید .
راستی اگر مقاله من درمورد unit test نویسی رو هم نخوندید یه نگاهی بهش بندازید .?
در ابتدا اجازه دهید تا تعریفی از Junit داشته باشیم :
جی یونیت یک فریمورک اپن سورس است که برای نوشتن و اجرای تست ها در زبان جاوا به کار میرود.
به کمک Junit framework میتوانیم با روش علامت گذاری (annotation) ، تست متد های خود را بنویسیم و همچنین با استفاده از test runner های فراهم شده توسط فریمورک آن ها را اجرا کنیم .
از مزایای این فریمورک میتوان به :
همونطور که میدونید Junit ورژن های مختلفی داره که هر کدوم از این ورژن ها با سایر ورژن ها تفاوت هایی داره . جدید ترین نسخه این فریمورک نسخه 5 آن است به گفته توسعه دهندگان آن یک نسخه programmer-friendly از این فریمورک است.
اگر بخواهیم بین Junit4 و Junit5 مقایسه ای انجام بدهیم به طور کلی :
در Junit5 برخی از انوتیشن های کاربردی اضافه شده اند که در آینده به بررسی آن ها خواهیم پرداخت.
در Juint4 همه چیز در یک jar file باندل شده است (در واقع شامل یک معماری مونولیتیک است)
در حالی که Junit5 از یک معماری مدرن چند لایه بهره برده است و شامل 3 زیر ماژول زیر است:
JUnit Jupiter - JUnit Platform - JUnit Vintage
در Junit4 به کمک رانر ها میتوانستیم فیچر ها و فانکشنالیتی های مد نظر خودمان را به Junit اضافه کنیم (مانند MockitoRunner یا SpringRunner)
رانر ها در Junit4 از امکان رفلکشن استفاده می کردند .
اما در Junit5 رانر ها به اکستنشن تبدیل شده اند .
رول ها کامپوننت هایی هستند که به کمک آن ها میتوانیم قبل یا بعد از اجرای یک تست یک عمل خاص را انجام بدهیم . (مانند InstantTaskExecutorRule
)
رول ها نیز در Junit5 به اکستنشن تبدیل شده اند .
Junit 4 requires Java 5 or higher.
Junit 5 requires Java 8 or higher.
در Junit5 تعدادی assertion جدید نیز اضافه شده است .
مهم ترین دلیلی که میتونیم برای پاسخ به این سوال بیاریم اینه که در Junit5 امکاناتی مانند Repeated Tests
, Dynamic Tests , Assumptions , Parameterized Tests , ... اضافه شدند که باعث سهولت ، کارایی و افزایش کیفیت تست ها میشن و کمک میکنن تا از تست نویسی لذت بیش تری ببریم ?
همچنین معماری ماژولار به کار رفته در Junit5 باعث میشه تا این فریمورک از قابلیت بسط پذیری و کاستومایز بسیار بالاتری نسبت به ورژن های قبلی برخوردار بشه .
به کمک همین معماری ماژولار است که Junit 5 میتواند تست های نوشته شده با Junit 4 را اجرا و از آن ها پشتیبانی کند(backward compatibility) که این مورد باعث میشود تا مهاجرت به آن نیز آسان شود.
در Juint5 سعی شده تا به محدودیت ها و مشکلات Junit4 تا حد امکان توجه بشه و برای برطرف ساختن اون ها برنامه ریزی شده.
تمامی این دلایل سبب شده تا Junit5 به یکی از ورژن های محبوب این فریمورک تبدیل بشه و تجربه ی جذابی رو برای توسعه دهنده هایی که ازش استفاده میکنن فراهم کنه.
یکی از موارد مهم در Juint5 آشنایی با معماری آن است . Junit5 از سه لایه (ماژول) تشکیل شده است:
این ماژول یک زیر ساخت بنیانی برای اجرا و لانچ تست ها در jvm فراهم می کند.
به کمک Api های ارائه شده ، این ماژول به عنوان یک واسط بین Junit و client هایی مانند (build tools ها و ide ها) عمل میکند. در صورتی که در Juint4 کلاینت ها مجبور به استفاده از رفلکشن برای دسترسی به Junit بودند.
همچنین این ماژول امکان کاستومایز کردن Test Engine را بر روی Junit Platform میدهد تا لایبرری های شخص ثالث مانند Spock, Cucumber, FitNesse بتوانند مستقیما با Junit در ارتباط باشند و Test Engine های مختص خودشان را ارائه دهند.
تست انجین(Test Engine) برای کشف و اجرای تست ها به کار میرود.
فیچر ها ، اکستنشن ها و انوتیشن های جدیدِ Junit5 در این ماژول فراهم شده است.
همچنین این ماژول به طور پیشفرض از Test Engine ارائه شده در ماژول platform استفاده میکند.
به کمک Engine ارائه شده در این ماژول میتوانیم تست ها نوشته شده با junit3 و Junit4 را نیز اجرا کنیم (backward compatibility)
بنابراین اگر از تست های Junit3 یا Junit4 استفاده نمیکنیم نیازی نیست تا dependency این ماژول را به پروژه خود اضافه کنیم.
برای آشنایی بیشتر با ماژول ها و زیرماژول های Junit5 لایبرری آن را بررسی کنید.
برای اجرای تست های Junit5 در اندروید از یک گردل پلاگین استفاده میکنیم .
ابتدا دیپندنسی پلاگین را به گردل (سطح پروژه) خود اضافه میکنیم :
dependencies { classpath("de.mannodermaus.gradle.plugins:android-junit5:1.8.2.0") }
همچنین در گردل (سطح ماژول) :
plugins { id("de.mannodermaus.android-junit5") } dependencies { // (Required) Writing and executing Unit Tests on the JUnit Platform testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") // (Optional) If you need "Parameterized Tests" testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.2") // (Optional) If you also have JUnit 4-based tests testImplementation("junit:junit:4.13.2") testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.8.2") // (Required) For Mockito in the JUnit Platform testImplementation("org.mockito:mockito-junit-jupiter:4.3.1") }
حالا به توضیح دیپندنسی ها خواهیم پرداخت:
به کمک jupiter api می توانیم از امکانات و انوتیشن های جدید Junit5 استفاده کنیم .
برای کشف و اجرای تست ها به انجین Junit5 احتیاج داریم .
برای استفاده از پارامترایز تست ها که بعدا به توضیح آن خواهیم پرداخت به این دیپندنسی احتیاج داریم . اگر از این نوع تست ها استفاده نمیکنیم میتوانیم این دیپندنسی را اضافه نکنیم .
در این جا دو سناریو مطرح است :
اگر تست های خود را از ابتدا با Junit5 می نویسید احتیاجی به Junit4 و Vintage ندارید پس دیپندنسی های آن را اضافه نکنید .
اما اگر تست های Junit4 را در کنار Junit5 مینویسید برای استفاده از امکانات Junit5 و همچنین اجرای تست های Junit4 به دیپندنسی Vintage احتیاج دارید .
اگر در پروژه تان از ماکیتو استفاده می کنید برای initialize کردن انوتیشن های ماکیتو به MockitoExtension احتیاج دارید . که این اکستنشن در این لایبرری موجود است .
سایر دیپندنسی های ماکیتو :
testImplementation("org.mockito:mockito-inline:x.x.x") testImplementation("org.mockito:mockito-core:x.x.x")
حالا پروژه را سینک میکنیم تا دیپندنسی ها 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("RestrictedApi") 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("RestrictedApi") 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("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.3")
تا این جای کار اکستنشن ها را به برنامه خود اضافه کردیم حالا به سراغ MockitoExtension میرویم .
این اکستنشن از دیپندنسی (org.mockito:mockito-junit-jupiter:4.x.x) که سابقا برای کانفیگ Junit5 به گردل اضافه کردیم در دسترس است .
کاربرد این اکستنشن initialize کردن انوتیشن های ماکیتو است .
اگر این اکستشن را به Junit اضافه نکنیم باید قطعه کد زیر را جایگزین آن کنیم:
@BeforeEach fun setup(){ MockitoAnnotations.openMocks(this) }
اکنون Junit آماده ی اجرای یونیت تست هاست .???
در این جا لازم است به دو نکته توجه کنیم:
بنابراین به جای استفاده از Before@ از BeforeEach@ استفاده می کنیم .
متدی که با انوتیشن BeforeEach@ علامت گذاری می شود قبل از اجرای هر تست متد یکبار اجرا میشود .
متدی که با انوتیشن AfterEach@ علامت گذاری می شود بعد از اجرای هر تست متد یکبار اجرا میشود.
متدی که با انوتیشن BeforeAll@ علامت گذاری میشود قبل از اجرای همه ی تست متد ها یکبار اجرا میشود .
متدی که با انوتیشن AfterAll@ علامت گذاری میشود بعد از اجرای همه ی تست متد ها یکبار اجرا میشود.
اگر از 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 = "ELEMENT" } @BeforeEach fun init(){ Mockito.`when`(list[0]).thenReturn(fakeElement) } @Test fun firstTest() { val firstItem:String=list[0] Assert.assertTrue( firstItem.startsWith("E") ) } }
در این تستِ ساده با Mock@ یک لیست از نوع استرینگ را ماک کرده ایم و در متد init از ماکیتو خواسته ایم تا هر وقت عنصر 0 این ارایه خواسته شد استرینگ fakeElement را برای ما بازگرداند
همچنین در firstTest بررسی کرده ایم که ایا fakeElement با کاراکتر E شروع شده است یا خیر .
این تست به دلیل درستی شرط ( firstItem.startsWith("E") ) پاس میشود.
نکته : در این تست کلاس ، اکستنشن های (CoroutineTestExtension و InstantExecutorExtension) را اضافه نکرده ایم زیرا از کروتین ها و یا کامپوننت هایی که ترد mian را کال میکنند استفاده نشده است.
حالا به سراغ بررسی امکانات جدید و جذاب Junit5 می رویم .
به کمک انوتیشن Disabled@ میتوانیم تست کلاس ها یا تست متد های خود را ignore کنیم .
کاربرد این انوتیشن در مواردی است که یک یا برخی از تست های ما به مشکل خورده اند اما تصمیم داریم تا آن هارا در آینده فیکس کنیم بنابراین به جای کامنت کردن تست ها آن هارا ignore میکنیم تا توسط Junit اجرا نشوند .
همچنین در مواردی که از CI CD استفاده میکنیم ممکن است در برخی از موارد بخواهیم تست های مشکل دار را Skip کنیم تا بتوانیم ادامه ی pipeline را پیش برویم بنابراین میتوانیم از این امکان بهره ببریم.
گاهی از اوقات ممکن است احتیاج داشته باشیم تا برای اجرای تست ها شرایطی را تعیین کنیم تا درصورتی که آن شرط برقرار بود تست ما اجرا شود . Junit5 به طور پیشفرض برخی از این شرایط را تعریف کرده است :
به کمک انوتیشن های EnableOnOs@ و یا DisableOnOs@ میتوانیم اجرای تست های خود را به یک یا چند سیستم عامل خاص محدود کنیم :
به کمک انوتیشن های EnabledOnJre@ و DisabledOnJre@ و EnabledForJreRange@ و DisabledForJreRange@ میتوانیم اجرا شدن تست ها را روی ورژن های مختلف Jre کنترل کنیم :
در Junit5 تست های شرطی دیگری نیز وجود دارند که به ذکر همه ی آن ها نپرداختیم ، اما نکته مهم این است که میتوانیم Custom Conditions های پیشفرض خود را بنویسم . به مثال زیر توجه کنید :
به کمک انوتیشن Tag@ میتوانیم تست کلاس ها و تست متد های خود را تگ بزنیم . به کمک این تگ ها میتوانیم تست های خود را دسته بندی کنیم و یا حتی اجرای تست ها را روی تگ های خاص فیلتر کنیم .
به کمک includeTags که به تسک Test در گردل اضافه شده است Junit فقط تست ها با تگ های مشخص شده را اجرا می نماید .
گاهی ممکن است بخواهیم تا ترتیب مشخصی را برای اجرای تست متد های خود به کار ببریم ، به کمک انوتیشن TestMethodOrder(MethodOrderer.OrderAnnotation::class)@ در Junit5 این امکان فراهم شده است.
انوتیشن های Order@ اولویت اجرای تست ها را مشخص میکنند .
این امکان برای nested test ها نیز فراهم شده است فقط کافیست تا از انوتیشن (ClassOrder.OrderAnnotation::class)TestClassOrder@ استفاده کنیم.
به کمک این انوتیشن که برای تست کلاس ها و تست متد ها به کار می رود میتوانیم اسم تست ها را کاستومایز کنیم و یا کارکتر های خاص و ایموجی ها را به آن اضافه کنیم تا در ریپورت و اجرای تست ها نمایش داده شوند.
از این امکان برای بالا بردن خوانایی و زیبایی تست های نوشته شده استفاده می شود .
همچنین به کمک انوتیشن 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 خودمان را بنویسیم .
در واقع Assumption ها ضمانتی بر اجرا یا عدم اجرای تست ها است . به کمک عبارات assumeTrue , assumeFalse , assumingThat درستی یک شرط را بررسی کرده ، اگر شرط برقرار بود تست اجرا میشود و در غیر این صورت تست fail نمیشود بلکه توسط جی یونیت abort می شود
فرض کنید تستی را نوشته ایم که نباید در لوکال اجرا شود و فقط باید در سرور های CI اجرا شود.
به کمک Assumptions میتوانیم اجرای این تست را مدیریت کنیم :
@Test void testOnlyOnCiServer() { assumeTrue("CI".equals(System.getenv("ENV"))); // remainder of test }
این تست فقط زمانی اجرا می شود که شرط داخل assumeTrue برقرار شود.
اما آیا به کمک Custom Conditions ها که بالاتر به معرفی آن پرداختیم نمی توانستیم اجرای این تست را کنترل کنیم ؟
بله می توانستیم !
اما مثال زیر را در نظر بگیرید :
میخواهیم تستی بنویسیم تا اگر در محیط سرور CI بودیم یک Assertation خاص را به اضافه Assertation ای که در سایر محیط ها اجرا می شود نیز اجرا کند .
در این صورت فقط میتوانیم از امکان Assumption و متد assumingThat استفاده کنیم .
به کمک انوتیشن Nested@ می توانیم در یک تست کلاس ، تست کلاس های دیگری را (Nested) ایجاد کنیم.
کاربرد این انوتیشن در مواردی است که میخواهیم تست های مشابه را گروه بندی کنیم و برای آن ها ساختار مشخصی ایجاد کنیم . به مثال زیر توجه کنید :
یکی از قابلیت های جالب Junit5 تست های تکرار شونده است. به کمک انوتیشن RepeatedTest@ میتوان یک تست را به تعداد دفعات مشخص شده تکرار کرد .
این نوع از تست ها در مواردی که ممکن است یک عمل پس از چند بار اجرا ، نتیجه ی متفاوتی داشته باشد و یا در مواردی که اجرای تست ها به زمان سیستم وابستگی دارد کاربرد بسیار زیادی دارد.
@RepeatedTest(5) public void testRepeatedTest() { System.out.println("Hello World"); }
این قابلیت کاربردی Junit5 باعث میشود تا بتوانیم یک تست را با آرگومان های متفاوت تکرار کنیم ??.
برای ایجاد این تست ها باید تست متد را به جای Test@ با انوتیشن ParameterizedTest@ علامت گذاری کنیم .
همچنین آرگومان های مورد نظر را با انوتیشن ValueSource@ مشخص می کنیم:
@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
انوتیشن 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 = { "racecar", "radar", "able was I ere I saw elba" }) void palindromes(String candidate)
حالا به مثال زیر توجه کنید :
این تست که palindrome بودن string ها را چک می کند ، 3 بار با مقادیر مشخص شده در ValueSource اجرا میشود .
اگر بخواهیم تست های خود را با ارگومان های بیش تری اجرا کنیم به جای استفاده از ValueSource از CsvSource استفاده میکنیم .
در CsvSource ارگومان ها در یک دابل کوتیشن و با علامت کاما از هم متمایز میشوند . به مثال زیر توجه کنید :
در این مثال در هر اجرای تست متد ، آرگومان های زیر به fruit , rank داده میشود .
تست های استانداردی که با انوتیشن 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 به کار می رود .
اکنون برای درک بیش تر موارد گفته شده مثال زیر را بررسی می کنیم :
در واقع با اجرای یک تست متد ، دو تست متفاوت با نتیجه های متفاوت را اجرا کردیم .
حالا مثال های زیر را بررسی کنید :
لایف سایکل اجرای داینامیک تست ها با تست های استاندارد کاملا متفاوت است.
به ازای هر dynamic test موجود در یک تست متد ، callback های لایف سایکل وجود ندارد .
این بدان معناست که متد های BeforeEach@ و AfterEach@ فقط یکبار به ازای تست متدی که با انوتیشن TestFactory@ مشخص شده است اجرا می شوند نه به ازای هر dynamicTest به کار رفته در آن !
الگوی اجرای تست های استاندارد و داینامیک به صورت زیر است :
به کمک انوتیشن Timeout@ می توانیم زمان مشخصی را برای اجرای تست ها مشخص کنیم تا اگر زمان اجرا از حد مجاز فراتر رفت تست fail شود.
واحد زمان برای این انوتیشن به طور پیشفرض ثانیه است که این مورد به کمک پارامتر unit قابل تغییر است .
مشخصا بررسی کامل Parameterized Tests و Dynamic Tests نیازمند دو مقاله جداگانه است ، اما در این جا جهت آشنایی و داشتن یک دید کلی از این فیچر ها و کاربردشان آن ها را بررسی کردیم .
همچنین برای بررسی سایر امکانات به کار رفته در Junit5 به راهنمای کاربری آن مراجعه کنید.
در این مقاله نیز برای بسیاری از مثال های به کار رفته از فیچر های Junit5 از مثال های راهنمای کاربری آن استفاده شد.
اگر که هنوز از Junit4 استفاده می کنید امیدوارم تا با خوندن این مقاله علاقه مند به استفاده از Junit5 شده باشید .
مرسی که تا آخر مقاله همراه من بودید .?