راهنمای آزمایش Hilt

یکی از مزایای استفاده از چارچوب‌های تزریق وابستگی مانند 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() در ترکیب‌پذیرهایتان به‌دست می‌آید نیز صدق می‌کند. در آزمون‌های واحد، ViewModel را مستقیماً با داده‌های جعلی بسازید. برای کسب اطلاعات درباره نحوه جریان یافتن وضعیت از ViewModel به composable، به وضعیت و Jetpack Compose و محل بالا بردن وضعیت مراجعه کنید.

آزمایش‌های سرتاسر

برای آزمایش‌های ادغام، 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 را برای استفاده در آزمایش‌ها ارائه می‌دهد. اگر آزمایش‌هایتان به برنامه پایه دیگری نیاز دارد، برنامه سفارشی برای آزمایش‌ها را ببینید.

باید برنامه آزمایشی‌تان را طوری تنظیم کنید که در آزمایش‌های ابزاردار یا آزمایش‌های Robolectric اجرا شود. دستورالعمل‌های زیر مختص Hilt نیستند، بلکه دستورالعمل‌های کلی درباره نحوه مشخص کردن برنامه سفارشی برای اجرا در آزمایش‌ها هستند.

تنظیم برنامه آزمایشی در آزمایش‌های ابزاری

برای استفاده از برنامه آزمایشی Hilt در آزمایش‌های ابزاری، باید یک مجری آزمایش جدید پیکربندی کنید. این کار باعث می‌شود Hilt برای همه آزمایش‌های ابزاردار در پروژه‌تان کار کند. مراحل زیر را انجام دهید:

  1. کلاس سفارشی‌ای ایجاد کنید که AndroidJUnitRunner را در پوشه androidTest گسترش دهد.
  2. تابع 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 خود پیکربندی کنید، همان‌طور که در راهنمای آزمون واحد ابزاردار شرح داده شده است. مطمئن شوید از مسیر کلاس کامل استفاده می‌کنید:

android {
    defaultConfig {
        // Replace com.example.android.dagger with your class path.
        testInstrumentationRunner = "com.example.android.dagger.CustomTestRunner"
    }
}
تنظیم برنامه آزمایشی در آزمایش‌های Robolectric

اگر از 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 بگویید که از اتصالی که در کد تولید استفاده کرده است استفاده نکند و به‌جای آن از اتصال دیگری استفاده کند. برای جایگزین کردن یک اتصال، باید ماژول حاوی اتصال را با یک ماژول آزمایشی حاوی اتصال‌هایی که می‌خواهید در آزمایش استفاده کنید جایگزین کنید.

برای مثال، فرض کنید کد تولید شما اتصالی را برای 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
}

Because composables typically consume these dependencies indirectly through a ViewModel obtained with hiltViewModel(), replacing the binding in Hilt is enough. ترکیب‌پذیر تحت آزمایش به‌طور خودکار داده‌های جعلی را دریافت می‌کند.

جایگزین کردن اتصال در آزمایشی تکی

برای جایگزین کردن اتصال در یک آزمایش به‌جای همه آزمایش‌ها، بااستفاده از حاشیه‌نویسی @UninstallModules، یک ماژول Hilt را از آزمایش حذف نصب کنید و یک ماژول آزمایش جدید درون آزمایش ایجاد کنید.

با دنبال کردن مثال AnalyticsService از نسخه قبلی، با گفتن به Hilt برای نادیده گرفتن واحد تولید بااستفاده از حاشیه‌نویسی @UninstallModules در کلاس آزمایش شروع کنید:

@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 برای آزمایش‌های ابزاری قرار دهید. توصیه می‌شود هروقت ممکن بود از @TestInstallIn استفاده کنید.

درحال اتصال مقادیر جدید

از حاشیه‌نویسی @BindValue استفاده کنید تا به‌راحتی فیلدها را در آزمایشتان به نمودار وابستگی Hilt متصل کنید. فیلدی را با @BindValue حاشیه‌نویسی کنید تا تحت نوع فیلد اعلام‌شده با هرگونه توصیف‌کننده‌ای که برای آن فیلد وجود دارد محدود شود.

در مثال AnalyticsService، می‌توانید AnalyticsService را بااستفاده از @BindValue با یک جعلی جایگزین کنید:

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsScreenTest {

  @BindValue @JvmField
  val analyticsService: AnalyticsService = FakeAnalyticsService()

  ...
}

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

‫@BindValue با توصیف‌کننده‌ها و دیگر حاشیه‌نویسی‌های آزمایشی کار می‌کند. برای مثال، اگر از کتابخانه‌های آزمایشی مانند Mockito استفاده می‌کنید، می‌توانید از آن در آزمایش Robolectric به‌صورت زیر استفاده کنید:

...
class SettingsScreenTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

اگر لازم است چنداتصالی اضافه کنید، می‌توانید از حاشیه‌نویسی‌های @BindValueIntoSet و @BindValueIntoMap به‌جای @BindValue استفاده کنید. @BindValueIntoMap از شما می‌خواهد که فیلد را با حاشیه‌نویسی کلید نقشه نیز حاشیه‌نویسی کنید.

موارد خاص

Hilt همچنین ویژگی‌هایی برای پشتیبانی از موارد استفاده غیراستاندارد ارائه می‌دهد.

برنامه سفارشی برای آزمایش‌ها

اگر نمی‌توانید از HiltTestApplication استفاده کنید چون برنامه آزمایشی شما باید برنامه دیگری را گسترش دهد، کلاس یا میانای جدیدی را با @CustomTestApplication حاشیه‌نویسی کنید و مقدار کلاس پایه‌ای را که می‌خواهید برنامه Hilt تولیدشده گسترش دهد ارسال کنید.

‫@CustomTestApplication یک کلاس Application آماده برای آزمایش با Hilt تولید می‌کند که برنامه‌ای را که به‌عنوان پارامتر ارسال کرده‌اید گسترش می‌دهد.

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

در این مثال، Hilt یک Application به‌نام HiltTestApplication_Application تولید می‌کند که کلاس BaseApplication را گسترش می‌دهد. به‌طور کلی، نام برنامه تولیدشده همان نام کلاس حاشیه‌نویسی‌شده است که _Application به آن اضافه شده است. باید برنامه آزمایشی Hilt تولیدشده را برای اجرا در آزمایش‌های ابزاردار یا آزمایش‌های Robolectric همان‌طور که در برنامه آزمایشی شرح داده شده است تنظیم کنید.

چندین شیء TestRule در آزمایش ابزاردار شما وجود دارد

آزمایش‌های Compose UI ازقبل 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.
}

استفاده از نقطه ورودی قبل‌از دردسترس بودن مؤلفه تک‌قلو

حاشیه‌نویسی @EarlyEntryPoint یک راه فرار ارائه می‌دهد وقتی که یک نقطه ورودی Hilt باید قبل‌از اینکه مؤلفه تک‌قلو در یک آزمایش Hilt دردسترس باشد، ایجاد شود.

اطلاعات بیشتر درباره @EarlyEntryPoint در اسناد Hilt.

منابع بیشتر

برای کسب اطلاعات بیشتر درباره آزمایش، منابع اضافی زیر را ببینید:

اسناد

محتوا را مشاهده می‌کند