android 单元测试(2)依赖android框架测试:Robolectric
1.官方文档
https://developer.android.google.cn/training/testing/unit-testing?hl=zh-cn
https://github.com/android/testing-samples/tree/master/unit/BasicUnitAndroidTest
https://github.com/robolectric/robolectric
当测试代码对android系统有依赖时,可使用 AndroidX Test 提供的 Robolectric库完成任务。Robolectric 在本地 JVM 或真实设备上执行真实的 Android 框架代码和原生框架代码的虚拟对象。它会模拟 Android 4.1(API 级别 16)或更高版本的运行时环境,并提供由社区维护的虚假对象。通过此功能,您可以测试依赖于框架的代码,而无需使用模拟器或模拟对象。Robolectric 支持 Android 平台的以下几个方面:
- 组件生命周期
- 事件循环
- 所有资源
2.依赖android测试步骤
2.1 设置gradle
1 android { 2 testOptions { 3 unitTests { 4 includeAndroidResources = true 5 } 6 } 7 } 8 9 dependencies { 10 testImplementation 'org.robolectric:robolectric:4.6' 11 }
android studio 3.3以前 要在 gradle.properties 里添加
android.enableUnitTestBinaryResources=true
2.2 .编写被测试类、测试代码
1 @RunWith(RobolectricTestRunner::class) 2 class RobolectricTest { 3 lateinit var app : UnitApp 4 lateinit var context: Context 5 6 val FAKE_STRING = "Utest" 7 8 @Test 9 fun readStringFromContext_LocalizedString() { 10 val result: String = context.resources.getString(R.string.app_name) 11 assertThat(result).isEqualTo(FAKE_STRING) 12 } 13 @Test 14 fun clickingButton_shouldChangeMessage() { 15 val activity: MainActivity = Robolectric.setupActivity(MainActivity::class.java) 16 activity.binding.button.performClick() 17 assertThat(activity.binding.message.getText()).isEqualTo("Robolectric Rocks!") 18 } 19 @Before 20 fun setup(){ 21 app = RuntimeEnvironment.application as UnitApp 22 context = ApplicationProvider.getApplicationContext<UnitApp>() 23 println("setup application ") 24 } 25 }
Robolectric内置的类
ApplicationInfo
PackageInfo
MotionEvent
MotionEvent.PointerCoords
MotionEvent.PointerProperties
- Parcelable
3.robolectric常用api
http://robolectric.org/androidx_test/
3.1 application
robolectric
1 lateinit var app : UnitApp 2 3 @Before 4 fun robolectric_setup(){ 5 app = RuntimeEnvironment.application as UnitApp 6 println("setup application ") 7 }
amdroidx_test
1 lateinit var context: Context 2 3 4 @Before 5 fun androidx_setup(){ 6 context = ApplicationProvider.getApplicationContext<UnitApp>() 7 println("setup application ") 8 }
3.2 activity
3.2.1 简单启动
Robolectric.setupActivity() 启动一个activity到 resumed 状态
1 @Test 2 fun setupActivity(){ 3 Robolectric.setupActivity(MainActivity::class.java) 4 }
3.2.2 控制activity生命期
Robolectric版本
1 @RunWith(RobolectricTestRunner::class) 2 class RobolectricActivity { 3 4 @Test 5 fun setupActivity(){ 6 Robolectric.setupActivity(MainActivity::class.java) 7 } 8 9 @Test 10 fun test_activity_lifecycle(){ 11 12 // GIVEN 13 val controller = Robolectric.buildActivity(MainActivity::class.java).setup() 14 val activity = controller.get() 15 16 // WHEN 17 controller.pause().stop() 18 19 // THEN 20 println("value = $activity.lifeCycle") 21 assertTrue(activity.lifeCycle == 5) 22 } 23 }
android x test 版本 ,ActivityScenario.launch 在import androidx.test.core.app 内
1 @RunWith(AndroidJUnit4::class) 2 class AndroidXActivity{ 3 4 @Test 5 fun test_activity_lifecycle(){ 6 // GIVEN 7 val scenario = ActivityScenario.launch(MainActivity::class.java) 8 9 // WHEN 10 scenario.moveToState(Lifecycle.State.CREATED) 11 12 // THEN 13 scenario.onActivity { activity -> 14 assertTrue(activity.lifeCycle == 0) 15 } 16 } 17 }
3.3 Fragment
使用androidX test api更方便,主要使用:
- launchFragment 系列重载函数 [ 把fragment 寄生 在一个空的FragmentActivity上]
- launchFragmentInContainer 系列重载函数。「把fragment 寄生在 空FragmentActivity's root view container `android.R.id.content` 上」
- 这两类函数都不能在主线程中被调用。
1 @RunWith(AndroidJUnit4::class) 2 class AndroidXTestFragment{ 3 4 @Test 5 fun frgmt_test1(){ 6 7 // GIVEN 8 val args = Bundle().apply { 9 putString("name","test") 10 putString("class","05") 11 putInt("age",18) 12 putString("email","hi@test.com") 13 } 14 15 // WHEN 16 //This method cannot be called from the main thread 17 val scenario = launchFragment<MainFragment>( 18 fragmentArgs = args, 19 themeResId = R.style.FragmentScenarioEmptyFragmentActivityTheme, 20 initialState = Lifecycle.State.RESUMED, 21 factory = null) //null to use default factory 22 23 // THEN 24 scenario.onFragment{ mainFrgmt -> 25 Truth.assertThat(mainFrgmt.lifeCycle > 0) 26 assertTrue(mainFrgmt.map["name"] == "test") 27 } 28 } 29 30 @Test 31 fun frgmt_test2(){ 32 33 // GIVEN 34 val args = Bundle() 35 args.putInt("value",2) 36 37 // WHEN 38 val scenario = launchFragmentInContainer<MainFragment>(args) 39 scenario.moveToState(Lifecycle.State.CREATED) 40 41 // THEN 42 scenario.onFragment{ mainFrgmt -> 43 Truth.assertThat(mainFrgmt.lifeCycle == 2) 44 } 45 } 46 @Test 47 fun frgmt_test3(){ 48 49 // GIVEN 50 val txt = "result" 51 52 // WHEN 53 val scenario = launchFragmentInContainer<ResultFrgmt>() 54 55 // THEN 56 scenario.onFragment{ fragment -> 57 fragment.parentFragmentManager.setFragmentResultListener("requestKey",fragment.viewLifecycleOwner) { key, bundle -> 58 val result : String? = bundle.getString("resultKey") 59 Truth.assertThat(result === txt) 60 } 61 } 62 } 63 }
其中第17行和第38、53行使用不同方式启动fragment 。
3.4 view
Robolectric 操作view 时,主要使用view自带api.
1 @RunWith(RobolectricTestRunner::class) 2 class RobolectricView { 3 @Test 4 fun testView(){ 5 val controller = Robolectric.buildActivity(MainActivity::class.java).setup() 6 val activity = controller.get() 7 val button : Button = activity.findViewById(R.id.button) 8 val txt = button.getText().toString() 9 assertThat(txt == """test button""") 10 } 11 }
android x test
1 @RunWith(AndroidJUnit4::class) 2 class AndroidXTestView{ 3 fun testView(){ 4 // GIVEN 5 val contactName = "Test User" 6 val scenario = ActivityScenario.launch(MainActivity::class.java) 7 8 // WHEN 9 Espresso.onView(withId(R.id.contact_name_text)).perform(typeText(contactName)) 10 // Destroy and recreate Activity 11 scenario.recreate() 12 13 // THEN 14 // Check contact name was preserved. 15 Espresso.onView(withId(R.id.contact_name_text)).check(matches(withText(contactName))) 16 } 17 }
3.5 Service
1 @RunWith(RobolectricTestRunner::class) 2 class RobolectricService { 3 @Test 4 fun test_service(){ 5 6 // given 7 val sc : ServiceController<MainService> = Robolectric.buildService(MainService::class.java) 8 val service = sc.get() 9 10 // when 11 sc.rebind() 12 //... 13 sc.bind() 14 //... 15 sc.startCommand(Service.START_FLAG_REDELIVERY,++service.id) 16 17 // then 18 Truth.assertThat(service.id > 0) 19 //... 20 service.stopSelf() 21 } 22 @Test 23 fun test_intent_service(){ 24 25 // given 26 val sc : IntentServiceController<MainIntentService> = Robolectric.buildIntentService(MainIntentService::class.java) 27 val service = sc.get() 28 29 // when 30 sc.handleIntent() 31 32 // then 33 Truth.assertThat(service.status) 34 service.stopSelf() 35 36 } 37 }
4.代码下载
基于mvvm
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 字符编码:从基础到乱码解决
2017-10-10 AIDL(2):服务端回调客户端