本文是 MAD Skills 系列 中无关 Hilt 的第二篇文章。这次咱们聚焦如何应用 Hilt 编写测试,以及一些须要留神的最佳实际。
如果您更喜爱通过视频理解此内容,能够 点击此处 查看.
Hilt 的测试理念
因为 Hilt 是一个有特定解决准则的框架,所以它的测试 API 是基于一些特定指标创立的。理解 Hilt 用于测试的办法有助于您应用和了解它的 API。如需进一步理解测试理念的更多信息,请参阅: Hilt 的测试理念。
Hilt 测试 API 的一个外围指标,便是在测试中缩小对不必要的虚伪或模仿对象的应用,同时尽可能地应用实在对象。实在对象能够减少测试的覆盖率,并且绝对于虚伪或模仿的对象也更经得起日后的变动。当实在对象执行开销低廉的工作 (例如 IO 操作) 时,虚伪或模仿的对象便很有用。但它们常常被适度应用,很多人会用它来解决那些在概念上齐全能够在测试中实现的问题。
一个相干例子是,如果应用了 Dagger 而没有用 Hilt, 测试时就会十分麻烦。为测试设置 Dagger 组件可能须要大量的工作和模板代码,但如果不必 Dagger 并手动实例化对象又会导致适度应用模仿对象。上面让咱们看看为什么会这样。
手动实例化 (测试时不应用 Hilt)
让咱们通过一个例子来理解为什么在测试中手动实例化对象会导致模仿对象的适度应用。
在上面的代码中,咱们对含有一些依赖项的 EventManager 类进行测试。因为不想为这样简略的测试配置 Dagger 组件,所以咱们间接手动实例化该对象。
class EventManager @Inject constructor( dataModel: DataModel, errorHandler: ErrorHandler) {}@RunWith(JUnit4::class)class EventManagerTest { @Test fun testEventManager() { val eventManager = EventManager(dataModel, errorHandler) // 测试代码 }}
一开始,因为咱们只是像 Dagger 一样调用了构造函数,所以所有看起来都非常简略。但当咱们须要解决如何取得 DataModel与 ErrorHandler 实例的问题时,麻烦就来了:
@RunWith(JUnit4::class)class EventManagerTest { @Test fun testEventManager() { // 呃...changeNotifier 要怎么解决? val dataModel = DataModel(changeNotifier) val errorHandler = ErrorHandler(errorConfig) val eventManager = EventManager(dataModel, errorHandler) // 测试代码 }}
咱们也能够间接实例化这些对象,然而如果这些对象同样蕴含依赖,那么继续下去可能会过于深刻。在进行理论测试前,咱们最终可能会调用很多个构造函数。另外,这些构造函数的调用也会使测试变得软弱。任何一个构造函数的扭转都会毁坏测试,即便它们在生产环境中没有毁坏任何内容。本应为 "无操作" 的更改,例如在 @Inject 构造函数中扭转参数程序,或者通过 @Inject 构造函数为某个类增加依赖,都会毁坏测试且难以对其进行更新。
为了防止这一问题,人们常常只是模仿对 DataModel 与 ErrorHandler 的依赖。但这同样是一个问题,因为引入这些模仿对象并不是为了防止测试中的任何低廉操作,而只是为了解决测试的设置模板代码而已。
应用 Hilt 进行测试
应用 Hilt 时,它会帮您设置好 Dagger 组件,这样您便无需手动实例化对象,也能防止在测试中配置 Dagger 而产生模版代码。更多测试内容请参阅 残缺的测试文档。
若要在您的测试中配置 Hilt,您须要:
- 为您的测试增加 @HiltAndroidTest 注解
- 增加测试规定 HiltAndroidRule
- 为 Application 类应用 HiltTestApplication
对于第三步来说,如何应用 HiltTestApplication 取决于您测试的类型:
- 对于 Robolectric 测试,请查阅 文档。
- 对于插桩测试,请查阅 文档。
配置实现后,您便能够为您的测试增加 @Inject
字段来拜访绑定。这些字段会在您调用 HiltAndroidRule
的 inject()
后赋值,所以您能够在您的 setup 办法中实现这一操作。
@HiltAndroidTest@RunWith(AndroidJUnit4::class)class EventManagerTest { @get:Rule val rule = HiltAndroidRule(this) @Inject lateinit var eventManager: EventManager @Before fun setup() { rule.inject(this) } @Test fun testEventManager() { // 应用注入的 eventManager 进行测试 }}
须要留神的是,注入的对象必须来自 SingletonComponent。如果您须要来自 ActivityComponent 或 FragmentComponent 的对象,则须要应用惯例 Android 测试 API 来创立一个 Activity 或 Fragment 并从中获取依赖。
随后您便能够开始编写测试了。您所注入的字段 (在本例中是咱们的 EventManager
类) 将会像在生产环境中一样由 Dagger 为您结构。您无需放心治理依赖所产生的任何模版代码。
TestInstallIn
当您在测试中遇到须要替换依赖的状况,比方实在对象会做诸如调用服务器这样的低廉操作时,您能够应用 TestInstallIn 来进行替换。
不过您无奈间接在 Hilt 中替换某个绑定,但您能够通过 TestInstallIn 替换模块。TestInstallIn 的工作模式与 InstallIn 相似,不同之处在于它还容许您指定须要被替换的模块。被替换的模块将不会被 Hilt 应用,而任何退出 TestInstallIn 模块的绑定都会被应用。与 InstallIn 模块类似,TestInstallIn 模块会利用于所有依赖它们的测试 (例如 Gradle 模块中的所有测试)。
@Module@TestInstallIn( components = [SingletonComponent::class], replaces = [BackendModule::class])object FakeBackendModule { @Singleton @Provides fun provideBackend(): BackendClient { return FakeBackend.inMemoryBackendBuilder( /* ...虚构后盾数据... */ ).build() }}
UninstallModules
当您遇到须要只在单个测试中替换依赖的状况时,能够应用 UninstallModules。您能够间接在测试上增加 UninstallModules 注解,并通过它指定 Hilt 不应应用哪些模块。
@HiltAndroidTest@RunWith(AndroidJUnit4::class)@UninstallModules(BackendModule::class)class DataFetcherTest { @BindValue val fakeBackend = FakeBackend.inMemoryBackendBuilder(...).build() ...}
在测试中,您能够应用 @BindValue 或通过定义嵌套组件来间接增加绑定。
TestInstallIn vs UninstallModules
您兴许会纳闷: 应该应用两者中的哪一个呢?上面咱们对两者进行一些比照:
TestInstallIn
- 利用于全局
- 便于配置
- 利于晋升构建速度
UninstallModules
- 只针对单个测试
- 非常灵活
- 不利于构建速度
通常,咱们举荐从 TestInstallIn
开始,因为它有利于晋升构建速度。当您的确须要独自的配置时,依然能够应用 UninstallModules
,然而咱们建议您仅在特地须要时审慎应用。
TestInstallIn/UninstallModules 影响构建速度的起因
对于每个用于测试的不同模块组,Hilt 须要创立一组新的组件。这些组件最终可能会十分大,当您依赖了大量生产代码中的模块时尤其如此。
△ 为不同模块组生成的组件
UninstallModules
的每次应用都会增加一组必须被构建的新组件,组件的数量可能会基于您的测试数量而成倍增加 。而因为 TestInstallIn
作用于全局,所以它会退出一组组件的默认汇合,而该汇合能够在多个测试中共享。如果您能够通过扭转测试而使其不用应用 UninstallModules
,那么就能够缩小一组须要构建的组件。
但有时测试还是须要应用 UninstallModules
。没关系!只有留神衡量并尽可能默认应用 TestInstallIn
即可。
测试依赖
另一种能够放慢测试构建速度的形式是缩小拉入测试的模块和入口点。这个局部会在每次应用 UninstallModules
时翻倍。有时候,您测试的理论覆盖范围很小,却可能依赖了所有的生产环境代码。因为 Hilt 在编译时无奈确定您将在运行时测试什么,因而 Hilt 必须构建一个能够通过您的依赖关系找到每个模块和入口点的组件。这些模块和入口点可能会很多,并且可能会产生很大的 Dagger 组件,从而导致构建工夫的减少。
如果您能够缩小这些依赖项,那么新增的 UninstallModules
可能不会产生太多耗费,从而能够让您在配置测试时更为灵便。
一种缩小依赖的办法是组织您的 Gradle 模块,您能够在此过程中将大量测试从主利用的 Gradle 模块拆散至依赖库的 Gradle 模块中,从而缩小所需的依赖。
△ 尽可能将测试组织到依赖库 Gradle 模块中
组织 Hilt 模块
要时刻记得思考如何组织您的 Hilt,这也有助于您编写测试。咱们经常可能看到非常微小且领有许多绑定的 Dagger 模块,然而对于 Hilt 来说,因为您须要替换整个模块而不是独自的绑定,那些能够做许多事的大型模块只会让测试变得更加艰难。
在应用 Hilt 模块时,您须要尽可能地放弃它们的繁多目的性,为此甚至能够只退出一个公开的绑定。这有助于进步可读性,并在须要时能够更简略的在测试中替换它们。
更多资源
利用上述这些实际内容并理解更多其中衡量的思路,将会帮忙您更轻松的编写 Hilt 测试。对于其中的一些 API 来说,您抉择哪种形式很大水平上取决于您利用、测试以及构建零碎的设置形式。
无关应用 Hilt 进行测试的更多信息,请查阅:
- 残缺文档
- 内含更多示例的测试指南文档
以上便是无关 Hilt 测试的全部内容,咱们行将推出更多 MAD Skills 文章,敬请关注。
欢迎您 点击这里 向咱们提交反馈,或分享您喜爱的内容、发现的问题。您的反馈对咱们十分重要,感谢您的反对!