场景
-
在做
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.AndroidJUnitRunnerdefaultConfig { 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/' }
使用设备单元测试库
-
官方文档上[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);
}
}
下载
晚点补上
参考
-
测试单个应用的界面
-
构建插桩单元测试
-
测试服务
-
测试服务例子
-
ActivityScenario
-
Android-开发原生应用-1










