• 【Android测试】【第十七节】Instrumentation——App任你摆布(反射技术的引入)


     ◆版权声明:本文出自胖喵~的博客,转载必须注明出处。

      转载请注明出处:http://www.cnblogs.com/by-dream/p/5569844.html

    前言


      学习了上节之后,大家是否已经感觉到Instrumentation的洪荒之力了,的确,比起之前的UI自动化,它可以干很多事情了。但是有的时候呢,可能暴露给你的一些变量,或者函数不够你用,你还是想去调用它的私有函数,或者你直接去重新在测试脚本中组织它的代码,完成和它App相同的功能,这个时候你就需要借助Java的反射机制了。

      另外还是建议学习Instrumentation章节的同学能有一些Java编程基础和Android开发的基础,否则看起来会非常吃力。

    概念解释


      既然要用到反射机制,那么先了解下反射机制是什么?

      JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。——摘自**百科《Java反射机制》

      简单通俗的来讲,为什么我们要在测试代码中使用反射呢?是因为我们需要通过反射机制,在拿到一个类对象的时候,去调用它的私有函数,或者是得到使用其私有变量,更复杂的就是调用其私有类对象变量的私有函数。是不是有点懵了?没事,下面我会慢慢的用代码实例讲明白的。

      这里纠结了很久,就是不知道该用什么样的源代码做为讲解的例子,用上一节写的例子吧,有些不太适合,因为基本的方法就可以满足正常的测试了,用反射的话会多此一举。而我当年之所以用到了反射技术,是因为我项目比较特殊,软件需要配合硬件一起使用,而硬件又必须在指定的场景下才能使用,并且项目代码特别的复杂,不是简单的A->B()就能完成一个功能的,里面嵌套着各种组合,聚合等关系,而UI自动化根本不能满足我的测试需求,于是最终选了这条路,但是项目代码涉及保密性又不能在这里贴出来。于是我还是决定用上一节计算器的例子来讲反射吧,只讲反射的用法,至于使用场景,大家只能自己体会了。

    反射技术


      因为我们在测试过程中需要的就是拿到私有变量,或者是去调用私有函数。因此我这里直接讲获得方法。

      私有变量

      如果上一节当中的我们在源代码中定义 EditText num1 为 private,且在测试类中不通过findViewById() ,我们使用反射获取,需要如下方式:

       @Override
        protected void setUp() throws Exception
        {
            //...
            mainActivity = (MainActivity) getInstrumentation().startActivitySync(intent);    
            
            // 反射获得
            
            /* 这里num1是mainActivity里面申明的变量,如果是mainActivity父类中的变量,
                   则可以使用mainActivity.getSuperclass().getDeclaredField("num1") */
            /* 这里还有一坑是如果 ***.getClass()获取失败,可以试试Class(**)强转 */
            Field fields = mainActivity.getClass().getDeclaredField("num1");
            /* 这里必须设置true */
            fields.setAccessible(true);
            /* 拿到变量 */ 
            num1 = (EditText)fields.get(mainActivity);
            
            //...
        }

      这样,num1就可以像上一节当中那样使用了。

      私有函数

      上面我们拿到了num1,这个时候我们需要调用它的setText方法,我们假设这个方法是也是私有的,那应该怎么调用呢?看下面:

      mainActivity.runOnUiThread(new Runnable()
      {            
           @Override
           public void run()
           {        
                // 反射调用函数,并且传参
                try
                {
                    /* 当然自己类内部的也可以使用getDeclaredMethod()*/
                    Method method = num1.getClass().getMethod("setText", new Class[]{CharSequence.class});
                    method.setAccessible(true);  
                    /* 这里的返回值就是函数返回值 */
                    Object  object = method.invoke(num1, new Object[] {"21"});
                        
                } catch (NoSuchMethodException e){
                    e.printStackTrace();
                } catch (IllegalAccessException e){
                    e.printStackTrace();
                } catch (IllegalArgumentException e){
                    e.printStackTrace();
                } catch (InvocationTargetException e){    
                    e.printStackTrace();
                }          
                //上面代码代替num1.setText("21");                
            }
       });    

      这里需要注意的是,一定要使用num做为object去获取函数,另外传参的时候,参数的类型一定得写对,例如这里setText的类型是:

      

      之前就因为一个Int和Integer类型让我查问题查了半天。

      看完这两个用法之后,基本上可以解决所有的问题了,由于当时踩了反射不少的坑,因此自己封装了一些API,有需要的可以自取,源代码贴这里了:

    package common;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.Calendar;
    
    import Result.ResultDataHelper;
    import android.app.Activity;
    import android.app.ActivityManager;
    import android.content.Context;
    import android.content.Intent;
    import android.graphics.Bitmap;
    import android.os.Bundle;
    import android.os.SystemClock;
    import android.test.InstrumentationTestCase;
    import android.util.Log;
    
    public class CommonMethod
    {
        InstrumentationTestCase main;
    
        public CommonMethod(InstrumentationTestCase m)
        {
            main = m;
            
            File picFile = new File(CommonVar.RESULT_PATH);
            if (!picFile.exists())
            {
                picFile.mkdir();
            }
        }
    
        /**
         * 根据一个类去找这个类中是否存在一个变量,如果没有就去他的父类中找,直到找到或者找不到
         * 
         * @param cls
         *            子类的Class对象
         * @param fieldname
         *            要找的变量的名字
         * 
         * @return 找到变量的Field 或者是null
         */
        public Field GetClassField(Class cls, String fieldname)
        {
            Field fields = null;
            while (cls != null)
            {
                try
                {
                    fields = cls.getDeclaredField(fieldname);
                    fields.setAccessible(true);
                    return fields;
                } catch (NoSuchFieldException e)
                {
                    // NoSuchFieldException 异常代表 如果没有找到指定名称的字段。
                    e.printStackTrace();
                    cls = cls.getSuperclass();
                }
            }
            BryanLog("没有获取到" + fieldname + "的field", CommonVar.LOG_DEBUG);
            return fields;
        }
    
        /**
         * 从任何一个类obj中找到一个名字为fieldname的变量,并返回
         * 
         * @param obj
         *            任何类,要找的类
         * @param fieldname
         *            要找的变量的名字
         * 
         * @return 找到变量的Object 或者是null
         */
        public Object GetFileldObjectFromClassObject(Object obj, String fieldname)
        {
            Object object = null;
            Field fields = null;
            
            if (obj == null)
            {
                BryanLog(" 获取 " + fieldname + " ,传递过来的父obj 为null", CommonVar.LOG_DEBUG);
                return object;
            }        
            if (obj.getClass() != null)
            {
                fields = GetClassField(obj.getClass(), fieldname);
            }        
            if (fields != null)
            {
                try
                {
                    object = fields.get(obj);
                    //BryanLog("从 "+obj.getClass().getSimpleName() +" 获取 " + fieldname + " 的obj 成功", CommonVar.LOG_DEBUG);
                    return object;
                } catch (IllegalAccessException e)
                {
                    e.printStackTrace();
                } catch (IllegalArgumentException e)
                {
                    e.printStackTrace();
                }
            }
            else 
            {
                BryanLog("从 "+obj.getClass().getSimpleName() +" 获取 " + fieldname + " 的obj 中 obj.getClass() 失败,换了直接拿类的方法", CommonVar.LOG_DEBUG);
                // 支持 这样的传参 " 类名.class" ObdFullScreenActivity.class
                
                fields = GetClassField((Class)obj, fieldname);
                if (fields != null)
                {
                    try
                    {
                        object = fields.get(obj);
                        BryanLog("从 "+obj.getClass().getSimpleName() +" 获取 " + fieldname + " 的obj 成功", CommonVar.LOG_DEBUG);
                        return object;
                    } catch (IllegalAccessException e)
                    {
                        e.printStackTrace();
                    } catch (IllegalArgumentException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
            BryanLog("从 "+obj.getClass().getSimpleName() +" 获取 " + fieldname + " 的obj失败", CommonVar.LOG_DEBUG);
            return object;
        }
    
        /**
         * 调用一个类中的私有函数,可以传参
         * 
         * @param obj
         *            要调用的函数所在的类,传递类的对象
         * @param function
         *            函数的名称的字符串
         * @param parameterTypes
         *            要传递的参数的类型 new Class[]{Intent.class, Integer.class} 也可多个
         * @param paramterValue
         *            要传递的参数的真实值,需要和上面类型对应new Object[] {intent, i}
         * 
         * @return Object 返回值的对象
         */
        public Object CallClassPrivateFun(Object obj, String function, Class[] parameterTypes, Object... paramterValue)
        {
            Object object = null;
            BryanLog("调用 " + obj.getClass().getSimpleName() + " 的 " + function, CommonVar.LOG_DEBUG);
            try
            {
                Class clas = obj.getClass();
                Method method;
                try
                {
                    method = clas.getMethod(function, parameterTypes);
                } catch (NoSuchMethodException e)
                {
                    method = clas.getDeclaredMethod(function, parameterTypes);
                }
                method.setAccessible(true);
                object = method.invoke(obj, paramterValue);
            } catch (IllegalAccessException e)
            {
                e.printStackTrace();
            } catch (IllegalArgumentException e)
            {
                e.printStackTrace();
            } catch (InvocationTargetException e)
            {
                e.printStackTrace();
            } catch (NoSuchMethodException e)
            {
                BryanLog("没有找到" + function, CommonVar.LOG_DEBUG);
                e.printStackTrace();
            }
            return object;
        }
    
        /**
         * 在UI线程中 调用一个类中的私有函数,可以传参
         * 
         * @param obj
         *            要调用的函数所在的类,传递类的对象
         * @param function
         *            函数的名称的字符串
         * @param parameterTypes
         *            要传递的参数的类型 new Class[]{Intent.class, Integer.class} 也可多个
         * @param paramterValue
         *            要传递的参数的真实值,需要和上面类型对应new Object[] {intent, i}
         * 
         * @return Object 返回值的对象
         */
        public Object RunOnUITreadCallClassPrivateFun(final Object obj, final String function, final Class[] parameterTypes, final Object... paramterValue)
        {
            final Object object[] = new Object[1];
            try
            {
                main.runTestOnUiThread(new Runnable()
                {
                    public void run()
                    {
                        BryanLog("在ui线程中调用:", CommonVar.LOG_DEBUG);
                        object[0] = CallClassPrivateFun(obj, function, parameterTypes, paramterValue);
                    }
                });
            } catch (Throwable e)
            {
                e.printStackTrace();
            }
            return object[0];
        }
    
        /**
         * 从一个Context 对象跳转到另一个Activity,并且返回Activity的对象
         * 
         * @param ctx
         *            当前页面的context
         * @param cls
         *            要找的变量的名字
         * @param bundle
         *            传递的参数
         * 
         * @return 启动的activity的实例
         */
        public Activity startActivityReturnObject(Context ctx, Class<?> cls, Bundle bundle)
        {
            BryanLog("从 " + ctx.getClass().getSimpleName() + " 调转到 " + cls.getSimpleName(), CommonVar.LOG_DEBUG);
            Activity act = null;
            Intent intent = new Intent(ctx, cls);
            if (bundle != null)
            {
                intent.putExtras(bundle);
            }
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            act = (Activity) main.getInstrumentation().startActivitySync(intent);
            return act;
        }
        
        public String getRunningActivityName(Context context)
        {          
            ActivityManager activityManager=(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);  
            String runningActivity=activityManager.getRunningTasks(1).get(0).topActivity.getClassName();  
            return runningActivity;                 
        }
        
    }

      里面有一些虽然和反射关系不大,但是在Android测试使用反射的过程中,一定也会对你有所帮助的。 

  • 相关阅读:
    [Luogu P4178]Tree 题解(点分治+平衡树)
    [20190725NOIP模拟测试8]题解
    暑假集训考试反思+其它乱写
    [bzoj2752]高速公路 题解(线段树)
    bzoj1211树的计数 x bzoj1005明明的烦恼 题解(Prufer序列)
    [CQOI2014]数三角形 题解(找规律乱搞)
    [Catalan数三连]网格&有趣的数列&树屋阶梯
    [NOIP模拟测试7]visit 题解(组合数学+CRT+Lucas定理)
    [7.22NOIP模拟测试7]方程的解 题解(扩展欧几里得)
    leetcode371
  • 原文地址:https://www.cnblogs.com/by-dream/p/5569844.html
Copyright © 2020-2023  润新知