实战 - 如何测试 Service ?

2,309 阅读2分钟
原文链接: greens1995.com

本文为 一旬一题写作计划 单元测试 专题内文章,读完本篇大约需要5分钟。


技术点

Service 涉及的 Android 类很多,所以要用设备测试(Instrumentation Test),也就是测试代码要放在 module-name/src/androidTest/java/。 本文的测试方案会用到 RestrictTo + Junit4 + AndroidJUnitRunner。 下面做个简单介绍:

RestrictTo注解

android.support.annotation 包下定义的一个注解: > Denotes that the annotated element should only be accessed from within a specific scope (as defined by RestrictTo.Scope).

用来表示被注解的元素只能在限定范围内(library,tests,子类)被访问。 比如只在测试中有效的方法,可以这样写。

@RestrictScope(TESTS)
public abstract int getUserId();

遗憾的是,目前这个注解只起到提示作用,即便不在限定范围内访问,也不会发生什么。不过即将到来的AndroidStudio 2.3 版本中这方面会加强,在Lint的时候会提出警告。期待期待~

AndroidJUnitRunner

设备测试本身不支持JUnit4。AndroidJUnitRunner 类是一个 JUnit 测试运行器,可让您在 Android 设备上运行 JUnit 3 或 JUnit 4 样式测试类。

  1. 添加依赖

    dependencies {
                compile 'com.android.support:support-annotations:22.2.0'
                androidTestCompile 'com.android.support.test:runner:0.4.1'
                }
            
  2. 指定 testInstrumentationRunner:

     android {
                defaultConfig {
                    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
                }
                }
            
  3. 给测试类加上注解 :

    @RunWith(AndroidJUnit4.class)
            
  4. 然后就可以写Junit4测试代码了。

实战

比如项目中的一个模块( 比如下面的 TheSDK ),初始化完成的时候,应该要启动了某个Service。这个逻辑要怎么测试?

public class TheSDK {
    private static void init(@NonNull final Context context) {
        // .....
        startServices(context);
        
        // .....
    }
    private static void startServices(final @NonNull Context context) {
        context.startService(new Intent(context, Service1.class));
    }
    public static class Builder {
        // .....
        public void init() {
            TheSDK.init(...);
        }
    }
    }

首先用RestrictTo对Service1做一点改造,具体看注释:

public class Service1 extends Service {
    @RestrictTo (RestrictTo.Scope.TESTS)
    static boolean started;//增加一个只在测试中用的标志位
    private SomeBroadCastReceiver receiver;
    public Service1() {
    }
    @Override
    public IBinder onBind(Intent intent) {
        throw null;
    }
    @Override
    public int onStartCommand(final Intent intent, final int flags, final int startId) {
        return START_STICKY;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        started = true;//标记启动完成
        //...init broadcast receiver and whatnot
    }
    @Override
    public void onDestroy() {
        unregisterReceiver(receiver);
        super.onDestroy();
    }
    }

然后给测试类加上注解,指定运行器为 AndroidJUnit4,写测试断言,搞定!:

@RunWith (AndroidJUnit4.class) // <<----Make sure you add this!!!!
public class TheSDKTest {
    @Test
    public void test_services_started_on_init() throws TimeoutException {
        final Context context = InstrumentationRegistry.getTargetContext();
        new TheSDK.Builder(context).init();
        assertTrue(Service1.started);
    }
    }

(Tips: Service的测试改造代码可以封装在BaseService中,就不用重复手写了)

class BaseService extends Service {
    @RestrictTo (RestrictTo.Scope.TESTS)
    private static boolean isServiceStarted;
    @Override
    public IBinder onBind(Intent intent) {
        throw null;
    }
    @Override
    public int onStartCommand(final Intent intent, final int flags, final int startId) {
        return START_STICKY;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        isServiceStarted = true;
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        isServiceStarted = false;
    }
    public static boolean isServiceStarted() {
        return isServiceStarted;
    }
    }
/**
而且Service可以拎出来单独测试
**/
@RunWith (AndroidJUnit4.class)
public class BaseServiceTest {
    private Context context;
    @Before
    public void setUp() {
        context = InstrumentationRegistry.getTargetContext();
    }
    @Test (expected = NullPointerException.class)
    public void onBind() throws Exception {
        final BaseService myService = new BaseService();
        context.startService(new Intent(context, BaseService.class));
        myService.onBind(new Intent());
    }
    @Test
    public void onStartCommand() throws Exception {
        final BaseService myService = new BaseService();
        context.startService(new Intent(context, BaseService.class));
        assertEquals(Service.START_STICKY, BaseService.onStartCommand(new Intent(), 0, 0));
    }
    @Test
    public void onCreate() throws Exception {
        final BaseService myService = new BaseService();
        context.startService(new Intent(context, BaseService.class));
        myService.onCreate();
        assertTrue(BaseService.isServiceStarted());
    }
    @Test
    public void onDestroy() throws Exception {
        final BaseService myService = new BaseService();
        context.startService(new Intent(context, BaseService.class));
        myService.onDestroy();
        assertFalse(BaseService.isServiceStarted());
    }
    }

参考资料与扩展阅读

Google官方测试支持库文档

Why is JUnit 4 on Android not working?

Lint in Studio 2.3

Android — How to test a Service