《Android编程权威指南》第 20 章第二篇,补充完 BeatBox 应用的单元测试啦。
第一篇地址:
https://juejin.cn/post/7033347707473231879
八、编写测试函数
测试函数将用到 @Test 注解。
@Test
fun exposesSoundNameAsTitle(){
assertThat(subject.title,`is`(sound.name))
}
assertThat(...) 选 org.junit 库里的 Assert.assertThat(...) 函数,is(...) 选 org.hamcrest 库里的 Is.is 函数。
上面代码意思是断定测试对象获取标题函数和 sound 的获取文件名函数返回相同的值。如果不同,单元测试失败。
接下来测试 SoundViewModel 和 BeatBox.play(Sound) 的交互。
@Test
fun callsBeatBoxPlayOnButtonClicked(){
subject.onButtonClicked()
}
为了测试 SoundViewModel 不让它跟 BeatBox 绑太死,不依赖 BeatBox 对象,就在此测试案例中模拟出 BeatBox 对象。
class SoundViewModelTest {
...
private lateinit var beatBox: BeatBox
@Before
fun setUp() {
beatBox = mock(BeatBox::class.java)
...
}
...
}
Mockito 的 verify(Object) 可以确认,要测试的函数是否都按预期被调用了。
@Test
fun callsBeatBoxPlayOnButtonClicked(){
...
verify(beatBox).play(sound)
}
然后按照书中过程补充修正一下,把 BeatBox 传给 SoundViewModel,修正 SoundHolder 中的错误,在测试类里提供模拟板 BeatBox,实现 onButtonClicked() 函数。具体代码略了,见 Demo。
九、数据绑定回调
在布局文件里,添加数据绑定 lambda 表达式,让按钮对象和 SoundViewModel.onButtonClicked() 函数关联起来。
运行应用,点击按钮,就能听到奇怪的喊叫声了,可以自行体验下。
十、释放音频
音频播放完毕,应调用 SoundPool.release() 函数释放 SoundPool。
在 BeatBox 中添加方法:
fun release(){
soundPool.release()
}
然后在 MainActivity 销毁的时候调用它:
override fun onDestroy() {
super.onDestroy()
beatBox.release()
}
十一、深入学习:整合测试
单元测试中,受测对象是单个类。保证各个类单元正确运行,相互之间的交互符合预期。
整合测试中,受测对象是应用的一部分,包括协同工作的众多对象。验证受测各部分已正确整合在一起,按预期发挥作用。在 Android 平台上,整合测试通常还是指 UI 级别的测试(和UI部件交互,验证它们的行为表现是否符合预期)。常以 instrumentation 测试来实施。
Espresso 是 Google 开发的一个 UI 测试框架,可用来测试 Android 应用。通常新项目都会自动的引用这个依赖。
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
可用它来测试某个 activity 的行为。
断定屏幕上某个视图显示了第一个 sample_sounds 受测文件的文件名:
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@get:Rule
val activityRule = ActivityTestRule(MainActivity::class.java)
@Test
fun showsFirstFileName(){
onView(withText("65_cjipie"))
.check(matches(isDisplayed()))
}
}
@RunWith(AndroidJUnit4.class) 表明,这是一个Android工具测试,需要 activity 和其他 Android 运行时环境支持。
activityRule 上的 @get:Rule 注解告诉 JUnit,运行测试前,要启动一个 MainActivity 实例。
onView(withText("65_cjipie")) 会找到显示 “65_cjipie” 的视图,对其执行测试。
check(matches(isDisplayed())) 用来判定视图在屏幕上看得见。
有关 Espresso 详情参考:
https://developer.android.com/training/testing/espresso
十二、深入学习:模拟对象与测试
模拟对象假扮成其他不相干的组件,为的就是隔离受测对象。对于单元测试来说,能快速创建模拟对象的 Mockito 非常有用。
但是整合测试时,最好避免使用像 Mockito 这样的自动模拟测试框架,因为模拟太重了,需要很多整合测试共享,太繁琐。
基本原则:模拟对象的效用不应超出受测组件的边界。应着重关注测试范围,防止测试越界。
十三、挑战练习:播放进度控制
给 BeatBox 应用添加播放进度控制功能,在MainActivity中,使用SeekBar部件控制SoundPool的play(Int, Float, Float, Int, Int, Float)函数的播放速率参数值。
参考 Demo,指不定啥时候就更新了。O(∩_∩)O哈哈~
十四、挑战练习:设备旋转问题
给 BeatBox 应用添加一个 Jetpack 版 ViewModel,实现在设备旋转时保存BeatBox对象。
参考 Demo,指不定啥时候就更新了。O(∩_∩)O哈哈~
其他
BeatBox 项目 Demo 地址:
https://github.com/visiongem/AndroidGuideApp/tree/master/BeatBox