[Android]_[中级]_[如何对服务和Activity进行单元测试]

Posted infoworld

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Android]_[中级]_[如何对服务和Activity进行单元测试]相关的知识,希望对你有一定的参考价值。

场景

  1. 在做android开发时,会使用Service来做一些后台工作。触发Service启动可能需要经过几次步骤,那么如果每次测试都需要手动点击这几个步骤无疑是很浪费时间的。那么如何使用单元测试来测试Service?

  2. 单元测试有需要启动Activity做一些接收Broadcast的消息,那么单元测试时如何启动指定的Activity

  3. 最后就是单元测试时可以编码自动化测试点击界面的按钮吗?

说明

配置设备单元测试

  1. Android上的设备单元测试最重要的需要依赖以下4种库, 需要在模块的build.gradle里添加以下的测试库,注意mockioMock库,而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'
    
  2. 还要在模块build.gradledefaultConfig添加设备单元测试的运行环境androidx.test.runner.AndroidJUnitRunner

    defaultConfig 
            applicationId "com.example.myapplication"
            minSdkVersion 23
            targetSdkVersion 30
            versionCode 1
            versionName "1.0"
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        
    
  3. 注意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/' 
    
  4. 在项目的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. 官方文档上[1]使用ActivityTestRule已经是过时了,需要改为使用ActivityScenario或者ActivityScenarioRule来启动Activity.

  2. android的中文开发文档上,对设备的单元测试称为仪器单元测试,所以两种称呼都可以。

  3. 对于本地mock单元测试的可以参考我写的学院课程 Android-开发原生应用-1

  4. 测试bind服务,需要创建一个服务测试套件ServiceTestRule, 因为单元测试默认都是4秒运行结束,如果你想运行长时间的单元测试,需要添加注解@LargeTest。 获取产品AppContext,可以通过ApplicationProvider.getApplicationContext()或者InstrumentationRegistry.getInstrumentation().getTargetContext();获得。

    @RunWith(AndroidJUnit4ClassRunner.class)
    @LargeTest
    public class MyFirstServiceTest 
    
  5. 通过ServiceTestRule的实例调用bindService来启动bind的服务,或者通过contextstartService来启动非绑定服务。

    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;
    
    
  6. Activity的启动需要通过ActivityScenario启动,返回一个ActivityScenario<MainActivity>。注意,无法通过ActivityScenario实例来获取启动的Activity对象,和服务测试一样,测试库默认启动4秒自动退出,这也是为了让测试能迅速结束。如果想长时间测试,需要调用scenario.getResult();来等待Activity结束。这时候需要手动退出Activity单元测试才会结束,这个等待看源码默认是45s

    ActivityScenario<MainActivity> scenario = ActivityScenario.launch(
                    MainActivity.class, null);
    
  7. 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());
  1. 为了测试调用方便,我把启动服务和Activity放在一个ComponentTestUtils类里。

  2. 单元测试时打印不要使用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

参考

  1. 测试单个应用的界面

  2. 构建插桩单元测试

  3. 测试服务

  4. 测试服务例子

  5. ActivityScenario

  6. Android-开发原生应用-1

以上是关于[Android]_[中级]_[如何对服务和Activity进行单元测试]的主要内容,如果未能解决你的问题,请参考以下文章

[Android]_[中级]_[如何对服务和Activity进行单元测试]

[JavaScript]_[中级]_[动态创建表单并提交到新页面_实现后台内容预览]

[JavaScript]_[中级]_[动态创建表单并提交到新页面_实现后台内容预览]

[JavaScript]_[中级]_[动态创建表单并提交到新页面_实现后台内容预览]

[JavaScript]_[中级]_[动态创建表单并提交到新页面_实现后台内容预览]

[WTL/ATL]_[中级]_[自定义TrackBar]