یکی از مزایای استفاده از چارچوبهای تزریق وابستگی مانند Hilt این است که تست کد شما را آسانتر میکند.
تستهای واحد
Hilt برای تستهای واحد ضروری نیست، زیرا هنگام تست کلاسی که از تزریق سازنده استفاده میکند، نیازی به استفاده از Hilt برای نمونهسازی آن کلاس ندارید. در عوض، میتوانید مستقیماً با ارسال وابستگیهای جعلی یا ساختگی، سازنده کلاس را فراخوانی کنید، درست همانطور که اگر سازنده حاشیهنویسی نشده باشد، این کار را انجام میدهید:
@ActivityScoped class AnalyticsAdapter @Inject constructor( private val service: AnalyticsService ) { ... } class AnalyticsAdapterTest { @Test fun `Happy path`() { // You don't need Hilt to create an instance of AnalyticsAdapter. // You can pass a fake or mock AnalyticsService. val adapter = AnalyticsAdapter(fakeAnalyticsService) assertEquals(...) } }
همین امر در مورد کلاسهای ViewModel که با فراخوانی hiltViewModel() در composableهای شما به دست میآیند نیز صدق میکند. در تستهای واحد، ViewModel را مستقیماً با fakes بسازید. برای اطلاعات در مورد نحوه جریان state از یک ViewModel به composableها، به State و Jetpack Compose و محل بالا بردن state مراجعه کنید.
آزمونهای پایان به پایان
برای تستهای یکپارچهسازی، Hilt وابستگیها را همانطور که در کد تولید شما تزریق میکند، تزریق میکند. تست با Hilt نیازی به نگهداری ندارد زیرا Hilt به طور خودکار مجموعهای جدید از اجزا را برای هر تست تولید میکند.
افزودن وابستگیهای تست
برای استفاده از Hilt در تستهای خود، وابستگی hilt-android-testing را در پروژه خود وارد کنید:
dependencies { // For Robolectric tests. testImplementation("com.google.dagger:hilt-android-testing:2.57.1") kspTest("com.google.dagger:hilt-android-compiler:2.57.1") // For instrumented tests. androidTestImplementation("com.google.dagger:hilt-android-testing:2.57.1") kspAndroidTest("com.google.dagger:hilt-android-compiler:2.57.1") // Compose UI test rule. androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-test-manifest") }
تنظیمات تست رابط کاربری
شما باید هر تست رابط کاربری که از Hilt استفاده میکند را با @HiltAndroidTest حاشیهنویسی کنید. این حاشیهنویسی مسئول تولید کامپوننتهای Hilt برای هر تست است.
همچنین، باید HiltAndroidRule به کلاس تست اضافه کنید. این کلاس وضعیت کامپوننتها را مدیریت میکند و برای انجام تزریق در تست شما استفاده میشود:
@HiltAndroidTest class SettingsScreenTest { @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) val composeRule = createAndroidComposeRule<HiltTestActivity>() // Compose UI tests here. }
در مرحله بعد، تست شما باید از کلاس Application که Hilt به طور خودکار برای شما تولید میکند، مطلع باشد.
برای اینکه Hilt بتواند وابستگیها را تزریق کند، باید یک اکتیویتی خالی به نام HiltTestActivity در مجموعه منبع androidTest خود ایجاد کنید و آن را با @AndroidEntryPoint حاشیهنویسی کنید. createAndroidComposeRule سپس از این اکتیویتی به عنوان میزبان محتوای قابل ترکیب شما استفاده میکند.
برنامه تست
شما باید تستهای ابزاری که از Hilt استفاده میکنند را در یک شیء Application که از Hilt پشتیبانی میکند، اجرا کنید. این کتابخانه HiltTestApplication برای استفاده در تستها ارائه میدهد. اگر تستهای شما به یک برنامه پایه متفاوت نیاز دارند، به Custom application for tests مراجعه کنید.
شما باید برنامه آزمایشی خود را طوری تنظیم کنید که در تستهای ابزار دقیق یا تستهای Robolectric اجرا شود. دستورالعملهای زیر مختص Hilt نیستند، اما دستورالعملهای کلی در مورد نحوه مشخص کردن یک برنامه سفارشی برای اجرا در تستها هستند.
تنظیم برنامه تست در تستهای ابزاری
برای استفاده از برنامه تست Hilt در تستهای ابزار دقیق ، باید یک اجراکننده تست جدید پیکربندی کنید. این کار باعث میشود Hilt برای تمام تستهای ابزار دقیق در پروژه شما کار کند. مراحل زیر را انجام دهید:
- یک کلاس سفارشی ایجاد کنید که
AndroidJUnitRunnerدر پوشهandroidTestارثبری کند. - تابع
newApplicationرا بازنویسی کنید و نام برنامه آزمایشی Hilt تولید شده را به آن بدهید.
// A custom runner to set up the instrumented application class for tests. class CustomTestRunner : AndroidJUnitRunner() { override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application { return super.newApplication(cl, HiltTestApplication::class.java.name, context) } }
در مرحله بعد، این اجراکننده تست را در فایل Gradle خود، همانطور که در راهنمای تست واحد instrumented توضیح داده شده است، پیکربندی کنید. مطمئن شوید که از classpath کامل استفاده میکنید:
android { defaultConfig { // Replace com.example.android.dagger with your class path. testInstrumentationRunner = "com.example.android.dagger.CustomTestRunner" } }
تنظیم برنامه تست در تستهای Roboelectric
اگر از Robolectric برای تست لایه رابط کاربری خود استفاده میکنید، میتوانید در فایل robolectric.properties مشخص کنید که از کدام برنامه استفاده کنید:
application = dagger.hilt.android.testing.HiltTestApplication
به عنوان یک روش جایگزین، میتوانید برنامه را در هر تست به صورت جداگانه با استفاده از حاشیهنویسی @Config در Robolectric پیکربندی کنید:
@HiltAndroidTest @Config(application = HiltTestApplication::class) class SettingsScreenTest { @get:Rule var hiltRule = HiltAndroidRule(this) // Robolectric tests here. }
ویژگیهای تست
زمانی که Hilt آماده استفاده در تستهای شما شد، میتوانید از چندین ویژگی برای سفارشیسازی فرآیند تست استفاده کنید.
تزریق انواع در تستها
برای تزریق انواع به یک تست، @Inject برای تزریق فیلد استفاده کنید. برای اینکه به Hilt بگویید فیلدهای @Inject را پر کند، hiltRule.inject() را فراخوانی کنید.
به مثال زیر از یک آزمایش ابزار دقیق توجه کنید:
@HiltAndroidTest class SettingsScreenTest { @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) val composeRule = createAndroidComposeRule<HiltTestActivity>() @Inject lateinit var analyticsAdapter: AnalyticsAdapter @Before fun init() { hiltRule.inject() } @Test fun settingsScreen_showsTitle() { composeRule.setContent { SettingsScreen() } composeRule.onNodeWithText("Settings").assertIsDisplayed() // analyticsRepository is available here. } }
یک اتصال را جایگزین کنید
اگر نیاز به تزریق یک نمونه جعلی یا ساختگی از یک وابستگی دارید، باید به Hilt بگویید که از اتصالی که در کد تولید استفاده کرده استفاده نکند و به جای آن از اتصال دیگری استفاده کند. برای جایگزینی یک اتصال، باید ماژولی را که شامل اتصال است با یک ماژول آزمایشی که شامل اتصالاتی است که میخواهید در تست استفاده کنید، جایگزین کنید.
برای مثال، فرض کنید کد عملیاتی شما یک binding برای AnalyticsService به صورت زیر تعریف میکند:
@Module @InstallIn(SingletonComponent::class) abstract class AnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( analyticsServiceImpl: AnalyticsServiceImpl ): AnalyticsService }
برای جایگزینی اتصال AnalyticsService در تستها، یک ماژول Hilt جدید در پوشه test یا androidTest با وابستگی جعلی ایجاد کنید و آن را با @TestInstallIn حاشیهنویسی کنید. در عوض، تمام تستهای موجود در آن پوشه با وابستگی جعلی تزریق میشوند.
@Module @TestInstallIn( components = [SingletonComponent::class], replaces = [AnalyticsModule::class] ) abstract class FakeAnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService }
از آنجا که composableها معمولاً این وابستگیها را بهطور غیرمستقیم از طریق ViewModel بهدستآمده با hiltViewModel() مصرف میکنند، جایگزینی اتصال در Hilt کافی است. composable تحت آزمایش، بهطور خودکار اتصال جعلی را دریافت میکند.
جایگزینی یک متغیر در یک تست واحد
برای جایگزینی یک متغیر در یک تست واحد به جای تمام تستها، یک ماژول Hilt را با استفاده از حاشیهنویسی @UninstallModules از تست حذف نصب کنید و یک ماژول تست جدید درون تست ایجاد کنید.
با پیروی از مثال AnalyticsService از نسخه قبلی، با استفاده از حاشیهنویسی @UninstallModules در کلاس تست، به Hilt بگویید که ماژول عملیاتی را نادیده بگیرد:
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { ... }
در مرحله بعد، باید اتصال را جایگزین کنید. یک ماژول جدید در کلاس تست ایجاد کنید که اتصال تست را تعریف کند:
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { @Module @InstallIn(SingletonComponent::class) abstract class TestModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService } // ... }
این فقط اتصال را برای یک کلاس تست جایگزین میکند. اگر میخواهید اتصال را برای همه کلاسهای تست جایگزین کنید، از حاشیهنویسی @TestInstallIn از بخش بالا استفاده کنید. به عنوان یک جایگزین، میتوانید اتصال تست را در ماژول test برای تستهای Robolectric یا در ماژول androidTest برای تستهای instrumented قرار دهید. توصیه میشود در صورت امکان @TestInstallIn استفاده کنید.
مقید کردن مقادیر جدید
از حاشیهنویسی @BindValue برای اتصال آسان فیلدهای موجود در تست خود به نمودار وابستگی Hilt استفاده کنید. یک فیلد را با @BindValue حاشیهنویسی کنید و آن فیلد تحت نوع فیلد اعلام شده با هر توصیفکنندهای که برای آن فیلد وجود دارد، محدود خواهد شد.
در مثال AnalyticsService ، میتوانید با استفاده از @BindValue AnalyticsService با یک متغیر جعلی جایگزین کنید:
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { @BindValue @JvmField val analyticsService: AnalyticsService = FakeAnalyticsService() ... }
این کار با فراهم کردن امکان انجام همزمان هر دو کار، جایگزینی یک متغیر و ارجاع به یک متغیر در تست شما را ساده میکند.
@BindValue با qualifierها و سایر حاشیهنویسیهای تست کار میکند. برای مثال، اگر از کتابخانههای تست مانند Mockito استفاده میکنید، میتوانید آن را در یک تست Robolectric به صورت زیر استفاده کنید:
... class SettingsScreenTest { ... @BindValue @ExampleQualifier @Mock lateinit var qualifiedVariable: ExampleCustomType // Robolectric tests here }
اگر نیاز به اضافه کردن multibinding دارید، میتوانید به جای @BindValue از حاشیهنویسیهای @BindValueIntoSet و @BindValueIntoMap استفاده کنید. @BindValueIntoMap شما را ملزم میکند که فیلد را با حاشیهنویسی کلید نقشه نیز حاشیهنویسی کنید.
موارد خاص
هیلت همچنین ویژگیهایی را برای پشتیبانی از موارد استفاده غیراستاندارد ارائه میدهد.
برنامه سفارشی برای آزمونها
اگر به دلیل نیاز برنامه آزمایشی خود به بسط برنامه دیگری، نمیتوانید از HiltTestApplication استفاده کنید، یک کلاس یا رابط جدید را با @CustomTestApplication حاشیهنویسی کنید و مقدار کلاس پایهای را که میخواهید برنامه Hilt تولید شده بسط دهد، به آن ارسال کنید.
@CustomTestApplication یک کلاس Application آماده برای تست با Hilt ایجاد میکند که از برنامهای که به عنوان پارامتر ارسال کردهاید، ارثبری میکند.
@CustomTestApplication(BaseApplication::class) interface HiltTestApplication
در این مثال، Hilt یک Application با نام HiltTestApplication_Application تولید میکند که کلاس BaseApplication ارثبری میکند. بهطورکلی، نام برنامه تولیدشده، نام کلاس حاشیهنویسیشدهای است که با _Application به آن اضافه شده است. شما باید برنامه تست Hilt تولیدشده را برای اجرا در تستهای instrumented یا تستهای Robolectric خود، همانطور که در Test application توضیح داده شده است، تنظیم کنید.
چندین شیء TestRule در تست ابزاری شما
تستهای رابط کاربری Compose از قبل HiltAndroidRule با یک قانون تست Compose مانند createAndroidComposeRule ترکیب میکنند. اگر اشیاء TestRule دیگری دارید، مطمئن شوید که HiltAndroidRule ابتدا اجرا میشود. ترتیب اجرا را با ویژگی order در @Rule اعلام کنید:
@HiltAndroidTest class SettingsScreenTest { @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) val composeRule = createAndroidComposeRule<HiltTestActivity>() @get:Rule(order = 2) val otherRule = SomeOtherRule() // UI tests here. }
به عنوان یک روش جایگزین، میتوانید قوانین را با RuleChain پوشش دهید و HiltAndroidRule به عنوان قانون بیرونی قرار دهید.
@HiltAndroidTest class SettingsScreenTest { @get:Rule var rule = RuleChain.outerRule(HiltAndroidRule(this)). around(SettingsScreenTestRule(...)) // UI tests here. }
قبل از در دسترس بودن کامپوننت singleton، از یک نقطه ورود استفاده کنید.
حاشیهنویسی @EarlyEntryPoint زمانی که نیاز به ایجاد یک نقطه ورود Hilt قبل از در دسترس قرار گرفتن کامپوننت singleton در تست Hilt باشد، یک روزنه فرار فراهم میکند.
اطلاعات بیشتر در مورد @EarlyEntryPoint در مستندات Hilt موجود است.
منابع اضافی
برای کسب اطلاعات بیشتر در مورد آزمایش، به منابع اضافی زیر مراجعه کنید: