Android中使用Robolectric测试点击事件(不需要手机)
文章目录
- 一、前言
- 二、简单示例
- 三、注意事项
- 四、另一种写法
- 五、拓展
- 六、参考文档
一、前言
Robolectric 是一个由 Google 维护的开源 Android 测试框架,它允许你以 Android 运行时环境运行单元测试。
Robolectric 提供了一个模拟 Android 运行时环境,允许你测试你的代码是否正确地使用 Android API。
所以在不依赖于手机的情况下可以对android项目进行测试。当然也可以在有手机的时候对Android项目进行测试
二、简单示例
以下代码源自官方文档,并进行简单完善。
文件位于src/app/test/
下面
import android.content.Intent
import android.widget.Button
import com.example.myapplication.R
import com.example.myapplication.hilt.App
import com.example.myapplication.material.MaterialTestActivity
import com.example.myapplication.roll.PaintedScrollActivity
import junit.framework.TestCase.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(application = App::class, sdk = [28])
class RobolectricTest {
// @get:Rule
// @JvmField
// val executorRule = InstantTaskExecutorRule()
@Test
fun clickingLogin_shouldStartLoginActivity() {
Robolectric.buildActivity(MaterialTestActivity::class.java).use { controller ->
controller.setup() // Moves the Activity to the RESUMED state
val activity = controller.get()
activity.findViewById<Button>(R.id.button).performClick()
val expectedIntent = Intent(activity, PaintedScrollActivity::class.java)
val actual = shadowOf(RuntimeEnvironment.getApplication()).nextStartedActivity
//新版写法使用如下方式
//val actual = shadowOf(androidx.test.core.app.ApplicationProvider.getApplicationContext<App>()).nextStartedActivity
println("YM----->,actual--> ${actual.component?.className}---->expectedIntent.name:${expectedIntent.component?.className}")
assertEquals(expectedIntent.component, actual.component)
}
}
}
这里需要注意的是需要添加
@Config(application = App::class, sdk = [28])
其中App
是应用的Application
文件。否则会有各种问题,最主要是提示不是主线程的问题。如果没有Application的话,可以使用另外一种方式。这种方式需要添加如下依赖
testImplementation "androidx.arch.core:core-testing:2.1.0"
然后添加以下代码
@get:Rule
@JvmField
val executorRule = InstantTaskExecutorRule()
不过@Config
也需要添加版本
@Config(sdk = [28])
三、注意事项
后面又经过测试,发现上述代码即使不在Config中添加App也可以运行,如
@Config(application = App::class, sdk = [28]) // X error
//改为
@Config(sdk = [28]) // 也是可以运行的
同时下面代码也没有
// @get:Rule
// @JvmField
// val executorRule = InstantTaskExecutorRule()
根据官网来说,如果不设置App的话,会自动使用程序定义的Application,但是之前代码确实一直运行失败,这里留作记录。
参考链接
四、另一种写法
这里提供另外一个测试示例,需要注意的是,其中断言可以使用以下任意一种库
testImplementation 'com.google.truth:truth:1.1.3'
testImplementation 'org.assertj:assertj-core:3.24.2'
以下是不同断言的区别:
断言库 | 适用场景 | 优势 | 适用语言 |
---|---|---|---|
JUnit Assertions | 最基础的测试 | 轻量级,适合简单测试 | Java, Android |
Google Truth | Android 开发 | 可读性好,官方推荐 | Java, Android |
AssertJ | Java 后端、复杂测试 | 功能最强大,链式断言 | Java |
Hamcrest | 传统 Java | JUnit 4 时代流行 | Java |
Kotest/Strikt | Kotlin 测试 | Kotlin DSL 语法更友好 | Kotlin |
其中使用AssertJ的话需要额外依赖Junit库
@Test
fun locationListenerShouldBeUnregisteredInCreatedState() {
// GIVEN
val controller = Robolectric.buildActivity<MaterialTestActivity>(MaterialTestActivity::class.java)
controller.setup()
// WHEN
controller.pause().stop()
// THEN
assertThat(controller.get().locationListener).isNull()
}
@Test
fun locationListenerShouldBeUnregisteredInCreatedState2() {
// GIVEN
val scenario = ActivityScenario.launch<MaterialTestActivity>(MaterialTestActivity::class.java)
// WHEN
scenario.moveToState(Lifecycle.State.CREATED)
// THEN
scenario.onActivity { activity ->
assertThat(activity.locationListener).isNull()
}
}
五、拓展
关于mock的含义,mock的作用的在测试过程中对某些功能进行模拟,保证流程能够执行下去,但是数据并不对。比如说假如自己写的一个类,需要传递Context才能保证不崩溃,那么可以使用mock进行模拟。
六、参考文档
- Robolectric 策略
- robolectric
- 构建本地单元测试
- Robolectric 4.0
- AndroidX Test
- mockito
- mockito-kotlin