场景
-
在做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 {
IBinder binder = serviceRule.bindService(intent);
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;
}
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 {
IBinder binder = serviceRule.bindService(intent);
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);
Instrumentation.ActivityResult result = scenario.getResult();
System.out.println("test delete image: "+result.getResultCode()+"");
}
public static Intent startService(Intent 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
参考
-
测试单个应用的界面 -
构建插桩单元测试 -
测试服务 -
测试服务例子 -
ActivityScenario -
Android-开发原生应用-1
|