[Android]_[中级]_[如何对服务和Activity进行单元测试]
Posted infoworld
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Android]_[中级]_[如何对服务和Activity进行单元测试]相关的知识,希望对你有一定的参考价值。
场景
-
在做
android
开发时,会使用Service
来做一些后台工作。触发Service
启动可能需要经过几次步骤,那么如果每次测试都需要手动点击这几个步骤无疑是很浪费时间的。那么如何使用单元测试来测试Service
? -
单元测试有需要启动
Activity
做一些接收Broadcast
的消息,那么单元测试时如何启动指定的Activity
? -
最后就是单元测试时可以编码自动化测试点击界面的按钮吗?
说明
配置设备单元测试
-
Android
上的设备单元测试最重要的需要依赖以下4
种库, 需要在模块的build.gradle
里添加以下的测试库,注意mockio
是Mock
库,而espresso
是界面的单元测试库。而androidTestImplementation
声明就是添加设备测试的库依赖,源码放在src/androidTest
下会自动识别为设备测试源码,不会打包到产品App
里。而设备单元测试会单独安装一个可动态更新的测试App
,所以如果更新测试代码再运行会很快,因为它不会更新产品App
。androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.28.0' androidTestImplementation 'org.mockito:mockito-core:2.28.2' androidTestImplementation 'androidx.test:core:' + rootProject.coreVersion; androidTestImplementation 'androidx.test.ext:junit:' + rootProject.extJUnitVersion; androidTestImplementation 'androidx.test:runner:' + rootProject.runnerVersion; androidTestImplementation 'androidx.test:rules:' + rootProject.rulesVersion; androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
-
还要在模块
build.gradle
的defaultConfig
添加设备单元测试的运行环境androidx.test.runner.AndroidJUnitRunner
defaultConfig applicationId "com.example.myapplication" minSdkVersion 23 targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-
注意
jcenter()
资源库即将失效,不要再使用,在项目的build.gradle
使用mavenCentral()
和阿里的jcenter
镜像代替。google() mavenCentral() maven url 'http://maven.aliyun.com/nexus/content/repositories/jcenter' maven url 'http://maven.aliyun.com/nexus/content/groups/public/'
-
在项目的
build.gradle
里增加一个ext
属性, 全局配置测试库的版本号.ext buildToolsVersion = "31.0.0" coreVersion = "1.4.1-alpha05" extJUnitVersion = "1.1.4-alpha05" runnerVersion = "1.5.0-alpha02" rulesVersion = "1.4.1-alpha05"
使用设备单元测试库
-
官方文档上[1]使用
ActivityTestRule
已经是过时了,需要改为使用ActivityScenario
或者ActivityScenarioRule
来启动Activity
. -
在
android
的中文开发文档上,对设备的单元测试称为仪器单元测试
,所以两种称呼都可以。 -
对于本地
mock
单元测试的可以参考我写的学院课程 Android-开发原生应用-1。 -
测试
bind
服务,需要创建一个服务测试套件ServiceTestRule
, 因为单元测试默认都是4
秒运行结束,如果你想运行长时间的单元测试,需要添加注解@LargeTest
。 获取产品App
的Context
,可以通过ApplicationProvider.getApplicationContext()
或者InstrumentationRegistry.getInstrumentation().getTargetContext();
获得。@RunWith(AndroidJUnit4ClassRunner.class) @LargeTest public class MyFirstServiceTest
-
通过
ServiceTestRule
的实例调用bindService
来启动bind
的服务,或者通过context
的startService
来启动非绑定服务。public static MyFirstService startBindService(ServiceTestRule serviceRule,Intent intent) throws TimeoutException // Bind the service and grab a reference to the binder. IBinder binder = serviceRule.bindService(intent); // Get the reference to the service, or you can call public methods on the binder directly. MyFirstService service = ((MyFirstService.LocalBinder) binder).getService(); return service;
-
Activity
的启动需要通过ActivityScenario
启动,返回一个ActivityScenario<MainActivity>
。注意,无法通过ActivityScenario
实例来获取启动的Activity
对象,和服务测试一样,测试库默认启动4
秒自动退出,这也是为了让测试能迅速结束。如果想长时间测试,需要调用scenario.getResult();
来等待Activity
结束。这时候需要手动退出Activity
单元测试才会结束,这个等待看源码默认是45s
。ActivityScenario<MainActivity> scenario = ActivityScenario.launch( MainActivity.class, null);
-
Activity
的模拟点击自动化测试使用Espresso
库,使用以下的方式来操作。
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
onView(withId(R.id.button_first)).perform(click());
-
为了测试调用方便,我把启动服务和
Activity
放在一个ComponentTestUtils
类里。 -
单元测试时打印不要使用
Log.x
, 因为这样会打印到logcat
里,在TestResult
窗口不会出现,需要使用System.out.println
来打印。
图1:
例子
ComponentTestUtils.java
package com.example.myapplication.common;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import androidx.lifecycle.Lifecycle;
import androidx.test.core.app.ActivityScenario;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.rule.ServiceTestRule;
import com.example.myapplication.MainActivity;
import com.example.myapplication.R;
import com.example.myapplication.service.MyFirstService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class ComponentTestUtils
private static ExecutorService executor;
public static ExecutorService getExecutor()
synchronized (ComponentTestUtils.class)
if(executor == null)
executor = Executors.newFixedThreadPool(1);
return executor;
/**
* 1. 创建30秒存活的`Service`.
*
* @return
*/
public static ServiceTestRule createServiceTestRule()
ServiceTestRule serviceRule = ServiceTestRule.withTimeout(30, TimeUnit.SECONDS);
return serviceRule;
public static void stopBindService(ServiceTestRule serviceRule)
serviceRule.unbindService();
public static MyFirstService startBindService(ServiceTestRule serviceRule,Intent intent) throws TimeoutException
// Bind the service and grab a reference to the binder.
IBinder binder = serviceRule.bindService(intent);
// Get the reference to the service, or you can call public methods on the binder directly.
MyFirstService service = ((MyFirstService.LocalBinder) binder).getService();
return service;
public static void sleep(long seconds)
try
Thread.sleep(seconds*1000);
catch (InterruptedException e)
e.printStackTrace();
public static ActivityScenario<MainActivity> launchMainActivity()
ActivityScenario<MainActivity> scenario = ActivityScenario.launch(
MainActivity.class, null);
return scenario;
public static void tryWaitResultOfActivity(ActivityScenario<MainActivity> scenario)
scenario.moveToState(Lifecycle.State.RESUMED);
// onActivityResult never be called after %d milliseconds [45000] 45s
Instrumentation.ActivityResult result = scenario.getResult();
System.out.println("test delete image: "+result.getResultCode()+"");
public static Intent startService(Intent intent)
// Create the service Intent.
Context context = ApplicationProvider.getApplicationContext();
ComponentName name = context.startService(intent);
System.out.println("ServiceName: "+name.getClassName());
return intent;
public static void stopService(Intent intent)
ApplicationProvider.getApplicationContext().stopService(intent);
MainActivityTest.java
package com.example.myapplication;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import androidx.test.core.app.ActivityScenario;
import androidx.test.filters.LargeTest;
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
import com.example.myapplication.common.ComponentTestUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4ClassRunner.class)
@LargeTest
public class MainActivityTest
@Test
public void testBroadcast()
ActivityScenario<MainActivity> scenario = ComponentTestUtils.launchMainActivity();
onView(withId(R.id.button_first)).perform(click());
onView(withId(R.id.button_second)).perform(click());
onView(withId(R.id.button_service)).perform(click());
ComponentTestUtils.tryWaitResultOfActivity(scenario);
MyFirstService.java
package com.example.myapplication;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
import androidx.test.rule.ServiceTestRule;
import com.example.myapplication.common.ComponentTestUtils;
import com.example.myapplication.service.MyFirstService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.TimeoutException;
@RunWith(AndroidJUnit4ClassRunner.class)
@LargeTest
public class MyFirstServiceTest
public final ServiceTestRule serviceRule = ComponentTestUtils.createServiceTestRule();
@Test
public void testStartService() throws TimeoutException
Context context = ApplicationProvider.getApplicationContext();
Intent intent = new Intent(context, MyFirstService.class);
ContactUserData cud = new ContactUserData();
String name = "infoworld";
cud.setName(name);
cud.setPhone("https://blog.csdn.net/infoworld");
intent.putExtra("contact",cud);
intent.putExtra("ip","192.168.0.1");
MyFirstService service = ComponentTestUtils.startBindService(serviceRule, intent);
System.out.println("name: "+service.getName());
Assert.assertEquals(service.getName(),name);
下载
https://download.csdn.net/download/infoworld/85287546
参考
以上是关于[Android]_[中级]_[如何对服务和Activity进行单元测试]的主要内容,如果未能解决你的问题,请参考以下文章
[Android]_[中级]_[如何对服务和Activity进行单元测试]
[JavaScript]_[中级]_[动态创建表单并提交到新页面_实现后台内容预览]
[JavaScript]_[中级]_[动态创建表单并提交到新页面_实现后台内容预览]
[JavaScript]_[中级]_[动态创建表单并提交到新页面_实现后台内容预览]