Jetpack系列(十) — 测试 + 总结
开头的话
这是我写的第十篇关于
Jetpack
的文章,内容相对比较简单,基本上都是一些相关控件的基本使用,但是本着先使用后理解的原则也算是把大部分Jetpack
的知识点过了一遍,至于更加深入的部分希望在接下来的学习当中逐步地学习。当然也有一些Jetpack
知识点没有放在系列文章当中,比如DataStore
、StarUp
等,其中Jetpack Compose
内容较多,应该后面单独写一个系列,其他的也希望在后续文章当中有所体现。作为本系文章的最后一篇,希望整理一下测试相关的知识点。本文中重点是记录
Jetpack
相关的测试,包括Room
、ViewModel
、LiveData
、Hilt
等,至于相关的测试库比如Espresso
、Mockito
等,希望可以在后面具体的项目当中有所体现。
测试相关
典型的Android Studio项目包含两个用于放置测试的目录:
androidTest
目录应包含在真实或虚拟设备上运行的测试。此类测试包括集成测试、端到端测试,以及仅靠JVM
无法完成应用功能验证的其他测试。test
目录应包含在本地计算机上运行的测试,如单元测试。
测试金字塔说明了应用应如何包含三类测试
- 小型测试是指单元测试,用于验证应用的行为,一次验证一个类。
- 中型测试是指集成测试,用于验证模块内堆栈级别之间的互动或相关模块之间的互动。
- 大型测试是指端到端测试,用于验证跨越了应用的多个模块的用户操作流程。
基本使用
简单单元测试
-
鼠标放在对应的方法上,右键
Generata..
,选择Test...
,自动生成Test类class BaseUtilsTest { @Test fun add() { assertEquals(4, BaseUtils.add(2, 2)) } } 复制代码
-
我这里遇到一个小坑,就是一直包
class not find
,这里要把before launch
加上
-
Hamcrest
语义化// 导入 Hamcrest // testImplementation 'org.hamcrest:hamcrest-all:1.3' class StatisticsUtilsKtTest : TestCase() { @Test fun testGetActiveAndCompletedStats() { val tasks = listOf<Task>( Task("title", "desc", isCompleted = false) ) val result = getActiveAndCompletedStats(tasks) assertThat(result.completedTasksPercent, `is`(0f)) assertThat(result.activeTasksPercent, `is`(100f)) } } 复制代码
Room 测试
- 在
androidTest
文件夹下新建测试类@RunWith(AndroidJUnit4::class) class WordDaoTest { private lateinit var database: AppDataBase private lateinit var wordDao: WordDao private val word1 = Word("hello") private val word2 = Word("world") @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() @Before fun createDb() = runBlocking { val context = InstrumentationRegistry.getInstrumentation().targetContext database = Room.inMemoryDatabaseBuilder(context, AppDataBase::class.java).build() wordDao = database.wordDao() wordDao.insertAll(listOf(word1, word2)) } @After fun closeDb() { database.close() } @Test fun testGetAlphabetizedWords() = runBlocking { val wordList = wordDao.getAlphabetizedWords().first() Assert.assertThat(wordList.size, CoreMatchers.equalTo(2)) Assert.assertThat(wordList[0], CoreMatchers.equalTo(word1)) Assert.assertThat(wordList[1], CoreMatchers.equalTo(word2)) } @Test fun testDeleteAll() = runBlocking { wordDao.deleteAll() val wordList = wordDao.getAlphabetizedWords().first() Assert.assertThat(wordList.size, CoreMatchers.equalTo(0)) } } 复制代码
ViewModel 测试
-
ViewModel
没有参数时,可以直接实例化,ViewModel
有参数时可以参考Hilt
@RunWith(AndroidJUnit4::class) class HomeViewModelTest { private lateinit var viewModel: HomeViewModel private val coroutineRule = MainCoroutineRule() private val instantTaskExecutorRule = InstantTaskExecutorRule() @get:Rule val rule = RuleChain .outerRule(instantTaskExecutorRule) .around(coroutineRule) @Before fun setUp() { viewModel = HomeViewModel() } @Test fun testDefaultValues() = coroutineRule.runBlockingTest { viewModel.updateTaps() val value = viewModel.userName.getOrAwaitValue() assertThat(value, `is`("aa bb")) } } 复制代码
-
这里主要是观测
LiveData
的变化,所以可用协程的通道来实现数据的观察// 官方代码 @VisibleForTesting(otherwise = VisibleForTesting.NONE) fun <T> LiveData<T>.getOrAwaitValue( time: Long = 2, timeUnit: TimeUnit = TimeUnit.SECONDS, afterObserve: () -> Unit = {} ): T { var data: T? = null val latch = CountDownLatch(1) // 一次计数 val observer = object : Observer<T> { override fun onChanged(o: T?) { data = o latch.countDown() this@getOrAwaitValue.removeObserver(this) } } this.observeForever(observer) try { afterObserve.invoke() // Don't wait indefinitely if the LiveData is not set. if (!latch.await(time, timeUnit)) { throw TimeoutException("LiveData value was never set.") } } finally { this.removeObserver(observer) } @Suppress("UNCHECKED_CAST") return data as T } 复制代码
Hilt 测试
-
有一点需要注意的是,
Hilt
需要提供Application
,所以这里可以自定义一个AndroidJUnitRunner
,作为测试的启动项class MainTestRunner : AndroidJUnitRunner() { override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application { return super.newApplication(cl, HiltTestApplication::class.java.name, context) } } 复制代码
-
在
module
的build.gradle
文件当中修改testInstrumentationRunner
defaultConfig { ... testInstrumentationRunner "com.huang.myapplication.MainTestRunner" ... } 复制代码
-
用
@HiltAndroidTest
注解测试类@HiltAndroidTest class MainViewModelTest { private lateinit var viewModel: MainViewModel private val hiltRule = HiltAndroidRule(this) private val instantTaskExecutorRule = InstantTaskExecutorRule() private val coroutineRule = MainCoroutineRule() @get:Rule val rule = RuleChain .outerRule(hiltRule) .around(instantTaskExecutorRule) .around(coroutineRule) @Inject lateinit var repo: WordRepository @Inject lateinit var manager: WorkManager @Before fun setUp() { hiltRule.inject() viewModel = MainViewModel(repo, manager) } @Test fun testDefaultValues() = coroutineRule.runBlockingTest { val value = viewModel.taps.getOrAwaitValue() assertThat(value, `is`("0 taps")) } } 复制代码
相关链接
参考资料
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END