Android Instrumentation框架简单说明
本帖已被设为精华帖!,
提到android自动化测试的时候经常会提到Instrumentation,但实际上Instrumentation是什么呢,很多人可能认为Instrumentation就是android的测试框架,实际上当启动一个app的时候都会实例化一个Instrumentation对象,且Instrumentation在每个Activity跳转的时候都会用到且其内部类ActivityMonitor会监控activity的,,只是我们不直接使用它;另外Activity的生命周期方法也是通过它来调用的:

在自动化测试过程中我们不是直接使用Instrumentation而且使用其子类InstrumentationTestRunner,在测试工程的AndroidManiFest.xml里面配置如下:
<instrumentation
    android:name="android.test.InstrumentationTestRunner"
    android:label="InstrumentationApp"
    android:targetPackage="com.instrumentation.app" >
</instrumentation>
上面两种方式在应用启动的时候初始化的Instrumentation对象是不同的;点击app图标启动app初始化的是默认值Instrumentation的对象,通过adb shell instrument方式启动app的时候初始化的是AndroidManiFest.xml里面配置的InstrumentationTestRunner对象,以上两种初始化方式都可以在阅读android源码的时候看到,这里提供一种直观简单的方式来验证我们的猜想。
获取Instrumentation对象
查看Activity.java源码可知在Activity中存在Instrumentation的成员变量:
private Instrumentation mInstrumentation;
所以我们大致步骤就是通过反射获取mInstrumentation成员变量。
 首先新建一个Android project, 包名这里是:com.instrumentation.app,创建一个名为MainActivity的Activity,
package com.instrumentation.app;
import java.lang.reflect.Field;
import android.app.Activity;
import android.util.Log;
public class MainActivity extends Activity {
    private String LOG_STR = "debug";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Field mInstrumentation;
        Object value = null;
        try {
            mInstrumentation = this.getClass().getSuperclass().getDeclaredField("mInstrumentation");
            mInstrumentation.setAccessible(true);
            value = mInstrumentation.get(this);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        Log.d(LOG_STR, "Instrumentation: "+value.getClass().getName());
    }
}
打包工程安装到设备,点击app图标打开此时查看ddms的log显示:

此时显示的是默认的Instrumentation对象,当我们在此工程的AndroidManiFest.xml中配置如下:
<instrumentation
    android:name="android.test.InstrumentationTestRunner"
    android:label="InstrumentationApp"
    android:targetPackage="com.instrumentation.app" >
</instrumentation>
此工程中新建一个测试类:AppDemoTest
import android.test.ActivityInstrumentationTestCase2;
public class AppDemoTest extends ActivityInstrumentationTestCase2<MainActivity>{
    public AppDemoTest() {
        super(MainActivity.class);
    }
    public void testApp(){
        getActivity();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
打包工程安装到设备,通过命令行启动case:
adb shell am instrument -e class com.instrumentation.app/AppDemoTest com.instrumentation.app/android.test.InstrumentationTestRunner
查看ddms的log显示:

另外对比InstrumentationTestRunner可以发现其主要实现了Instrumentation的onCreate()和onStart()方法,用于解析命令行传入的参数和执行case。所以当我们想实际测试报告的输出也只要继续扩展InstrumentationTestRunner就可以了。
扩展:关于robotium框架对Instrumentation的利用
通过robotium获取当前的Activity对象
在看robotium框架源码之前需要分析Instrumentation启动activity的过程:
public void addMonitor(ActivityMonitor monitor) {
    synchronized (mSync) {
        if (mActivityMonitors == null) {
            mActivityMonitors = new ArrayList();
        }
        mActivityMonitors.add(monitor);
    }
}
public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    IApplicationThread whoThread = (IApplicationThread) contextThread;
    if (mActivityMonitors != null) {
        synchronized (mSync) {
            final int N = mActivityMonitors.size();
            for (int i=0; i<N; i++) {
                final ActivityMonitor am = mActivityMonitors.get(i);
                if (am.match(who, null, intent)) {
                    am.mHits++;
                    if (am.isBlocking()) {
                        return requestCode >= 0 ? am.getResult() : null;
                    }
                    break;
                }
            }
        }
    }
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess();
        int result = ActivityManagerNative.getDefault()
            .startActivity(whoThread, who.getBasePackageName(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()),
                    token, target != null ? target.mEmbeddedID : null,
                    requestCode, 0, null, options);
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
    }
    return null;
}
public void callActivityOnResume(Activity activity) {
    activity.mResumed = true;
    activity.onResume();
    if (mActivityMonitors != null) {
        synchronized (mSync) {
            final int N = mActivityMonitors.size();
            for (int i=0; i<N; i++) {
                final ActivityMonitor am = mActivityMonitors.get(i);
                am.match(activity, activity, activity.getIntent());
            }
        }
    }
}
以上通过addMonitor()方法将指定的ActivityMonitor对象添加到list中,在启动一个activity的过程中(代码仅供参考,实际流程复杂的多)会通过for循环对列表所有的ActivityMonitor对象调用match方法对其成员变量进行赋值操作,因此当此时调用ActivityMonitor的getLastActivity方法就可以获取刚启动的activity对象;
 再看Robotium的solo.getCurrentActivity()最终实际调用在robotium框架中的ActivityUtils的getCurrentActivity(true, true);
/**
 * Returns the current {@code Activity}.
 *
 * @param shouldSleepFirst whether to sleep a default pause first
 * @param waitForActivity whether to wait for the activity
 * @return the current {@code Activity}
 */
public Activity getCurrentActivity(boolean shouldSleepFirst, boolean waitForActivity) {
    if(shouldSleepFirst){
        sleeper.sleep();
    }
    if(!config.trackActivities){
        return activity;
    }
    if(waitForActivity){
        waitForActivityIfNotAvailable();
    }
    if(!activityStack.isEmpty()){
        activity=activityStack.peek().get();
    }
    return activity;
}
/**
 * Waits for an activity to be started if one is not provided
 * by the constructor.
 */
private final void waitForActivityIfNotAvailable(){
    if(activityStack.isEmpty() || activityStack.peek().get() == null){
        if (activityMonitor != null) {
            Activity activity = activityMonitor.getLastActivity();
            while (activity == null){
                sleeper.sleepMini();
                activity = activityMonitor.getLastActivity();
            }
            addActivityToStack(activity);
        }
        else if(config.trackActivities){
            sleeper.sleepMini();
            setupActivityMonitor();
            waitForActivityIfNotAvailable();
        }
    }
}
/**
 * This is were the activityMonitor is set up. The monitor will keep check
 * for the currently active activity.
 */
private void setupActivityMonitor() {
    if(config.trackActivities){
        try {
            IntentFilter filter = null;
            activityMonitor = inst.addMonitor(filter, null, false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
大概流程(不画图了 )
 )
- robotium中通过Instrumentation对象初始化一个ActivityMonitor对象,activityMonitor = inst.addMonitor(filter, null, false);
- 一旦有activity启动则activityMonitor就会调用match方法给其成员变量赋值,此时调用activityMonitor.getLastActivity()方法获取最新的activity对象并保存到Stack中;
- 调用ActivityUtils的getCurrentActivity(true, true),从取出栈顶的对象即为最新的activity对象;
扩展:关于Instrumentation对uiautomator的利用
Instrumentation是无法跨应用的,因此跨应用方案会单独采用uiautomator框架来做,但是Google在API>=18中通过Instrumentation提供获取UiAutomation对象来进行跨应用操作:
public UiAutomation getUiAutomation() {
    if (mUiAutomationConnection != null) {
        if (mUiAutomation == null) {
            mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(),
                    mUiAutomationConnection);
            mUiAutomation.connect();
        }
        return mUiAutomation;
    }
    return null;
}
示例:Instrumentation中调用uiautomation对象进行跨应用操作,回到桌面点击“设置”进入设置界面。
public void testApp(){
    getActivity();
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
    //回到桌面
    uiAutomation.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME);
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //获取当前界面的顶层AccessibilityNode信息
    AccessibilityNodeInfo accessibilityNodeInfo = uiAutomation.getRootInActiveWindow();
    List<AccessibilityNodeInfo> list = accessibilityNodeInfo.findAccessibilityNodeInfosByText("设置");
    int size = list.size();
    Log.d("debug", "size: "+size);
    if(size != 0){
        //获取“设置”控件信息
        AccessibilityNodeInfo settingAccessibilityNodeInfo = list.get(0);
        //执行点击操作
        settingAccessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
    }
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
扩展:解决通过Instrumentation进行自动化测试依赖被测应用权限问题
主要是利用AIDL提供远程调用,具体源码参考:https://github.com/hao-shen/AndToolsTest
* 注:本文来自网络投稿,不代表本站立场,如若侵犯版权,请及时知会删除
