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

  http://robolectric.org/     

  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内置的类

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.代码下载

  https://gitee.com/xi/utest  

  基于mvvm 

 

posted @ 2021-10-10 23:22  f9q  阅读(528)  评论(0编辑  收藏  举报