AutoClick:基于 Robotium 的自动遍历方案

JadeZZXE 8年前
   <h3>前言</h3>    <p>​ 做这个的初衷是发现项目中的崩溃问题(即稳定性)。Monkey达不到全覆盖,也试过思寒的 <strong>AppCrawler</strong> ,无奈速度上不太理想。我需要的是更快的反馈结果,于是乎着手自己写一个方案,也当做是提高编码能力,或者说对Android有更深入的理解。</p>    <h2>解决了什么</h2>    <p>​ 初期目标是想替代Monkey,众所周知Monkey的随机点击,以及不可控性,并不能做到完整的遍历。所以当下最主要的功能是 <strong>发现崩溃问题</strong> (如兼容性、混淆、代码问题导致的崩溃),额外可以做的是发现无数据时的空白布局(配合接口工具,启用快速模式验证)、发现无网络时是否显示无网络的布局(关闭网络,启用快速模式)等等。</p>    <h2>使用效果</h2>    <p>​ 在我们的产品上,启用爬虫模式试跑了几个小时发现了5个崩溃问题。当然发现第一个崩溃时自动遍历就停止了,它依赖于被测应用,被测应用崩溃,它也会一同退出,这是接下来要解决的问题( <strong>增加重启机制</strong> )。当崩溃问题不予修复时,继续遍历,还是会走到第一个崩溃( <strong>可复现性</strong> ),此时可以把崩溃的Activity加入忽略列表。</p>    <p>崩溃问题:</p>    <ul>     <li> <p>发现的崩溃问题都是正常操作的,非异常操作(举例:非人类手速的点击等等)。换句话说就是用户也会遇到该问题</p> </li>     <li> <p>擅长发现异步请求导致的崩溃问题</p>      <ul>       <li>异步请求拿到数据后更新UI,由于UI被销毁导致的崩溃</li>       <li>为什么说是擅长?</li>       <li>遍历逻辑基于Activity,点击View跳出本Activity后按返回键回到遍历Activity</li>       <li>当网络不稳定时,数据返回延时加长,View销毁了数据才回来,如果此时代码没处理好就会发生崩溃</li>       <li>建议遍历时切换到弱网环境</li>      </ul> <p>​</p> </li>    </ul>    <h2>特性</h2>    <ul>     <li> <p>可跨应用</p>      <ul>       <li>补上Robotium不支持跨应用的短板</li>       <li>自动遍历时不会有具体的跨应用操作,唯一出现的地方在Android 6.0以上版本启动应用时的授权操作(可能存在兼容性问题)</li>       <li>跨应用应用在单独写用例时</li>       <li>跨应用详情</li>      </ul> </li>     <li> <p>多种模式</p>      <ul>       <li>快速模式:只启动Activity,快速检测崩溃问题(如兼容性、混淆、代码问题导致的崩溃),一般几分钟可完成。依赖于Params.json文件,该文件可由录制模式产生。</li>       <li>迭代模式:启动Activity并点击每个View。依赖于Params.json文件,该文件可由录制模式产生。</li>       <li>爬虫模式:通过迭代主页并记录新开Activity,迭代完毕后读取新开Activity,循环往复,直至无新的Activity。</li>       <li>录制模式:需人工操作应用,记录每个新开的Activity,供快速模式、迭代模式使用。录制模式可在功能测试阶段使用,录制模式默认休眠1个小时,期间操作应用打开的Activity都将被记录下来。</li>      </ul> </li>     <li> <p>智能输入</p>      <ul>       <li>根据输入框支持的输入类型和最大长度进行输入</li>       <li>支持手机号、邮箱、普通文本等类型</li>      </ul> </li>     <li> <p>红点标记</p>      <ul>       <li>将要被点击的View会以红点标记保存为截图</li>       <li>如果发现截图没有红点或者红点位置明显错误时,不用惊讶,那一定是隐藏的View被点击了</li>       <li>没有红点:隐藏的View坐标不在屏幕范围</li>       <li>红点标记错误:点击到被遮挡的View,通常发生在ViewPager布局</li>      </ul> </li>     <li> <p>无惧遮挡</p>      <ul>       <li>被遮挡的View也可以点击到,因此无需滑动操作。</li>       <li>举例:列表一次性加载10条数据,屏幕只显示了5条,剩下5条没有显示的也可以点击到。</li>      </ul> </li>     <li> <p>完全遍历</p>      <ul>       <li>应用所有Activity都可以遍历到,360°无死角。</li>       <li>快速模式、迭代模式覆盖度最高可达100%,通过爬虫模式 + 录制模式组合产生的Params.json文件,或单独录制模式产生的Params.json文件。</li>       <li>爬虫模式亦可达到很高的覆盖度,不同应用覆盖度不一致,依赖Activity关联度。</li>       <li>提高爬虫模式覆盖度的方法:采用划分模块的方法,比如主页模块、个人模块等等</li>      </ul> </li>     <li> <p>一触即达</p>      <ul>       <li>只需一步就能打开应用内任何Activity</li>       <li>举例:在已经登录的情况下,想去到登录页面,一般可能的做法是在主页点击我,去到个人中心,个人中心滑动到最底部,点击退出登录,来到登录页面。一触即达只要知道登录页面的名称、启动参数就能直接打开登录页面。</li>      </ul> </li>     <li> <p>可复现性</p>      <ul>       <li>在数据相对不变的情况下,遍历Activity中View的顺序是一致的,因此具备一定的复现可能性,可理解为Monkey中的种子</li>      </ul> </li>     <li> <p>多重跟踪</p>      <ul>       <li>多重跟踪能在出现崩溃的情况下,更好的定位、复现、分析问题。</li>       <li>截图跟踪:每个点击操作都将被记录,根据截图顺序可知进行了何等操作</li>       <li>日志跟踪:崩溃日志抓取,供研发使用</li>       <li>接口跟踪:配合Fiddler等抓包工具,可知发生崩溃时请求了哪些接口,从而更好的定位问题</li>       <li>元素跟踪:点击的View信息以操作日志形式记录在SD card,包含包名、类名、资源ID、屏幕位置、文本等等信息</li>      </ul> </li>     <li> <p>支持Hybrid</p>      <ul>       <li>除了支持Native遍历,亦支持Hybrid</li>      </ul> <p>​</p> </li>    </ul>    <h2>技术细节(局部)</h2>    <p>关于跳转的处理</p>    <ol>     <li> <p>每次点击后都会判断是否离开遍历Activity(未离开则进入下一个点击事件)</p> </li>     <li> <p>如果跳转到本应用其他Activity(则按下返回键返回,返回后回不到遍历Activity则重启该Activity并重新遍历剩余View)</p> </li>     <li> <p>如果跳转到其他应用去了(如相机)则直接重启该Activity并重新遍历剩余View</p> </li>     <li> <p>如果跳转到登录页面则登录后继续操作(可能存在遍历时点击到退出登录按钮)</p> <p>​</p> </li>    </ol>    <p>关于直接启动Activity的处理</p>    <p>通过监听Activity的启动,拿到Activity实例并获取传入参数,看下流程图可能好理解:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/333eb823eec0b353cb766927cd2f7db3.jpg"></p>    <h2>配置说明</h2>    <p>参数描述:</p>    <pre>  <code class="language-java">// Activity截图开关,默认为true。启动Activity首先会截取一张图保存在sdcard/AutoClick/Screenshots/Activities文件夹  public boolean activityScreenShots = true;  // Activity迭代截图开关,默认为true。每次点击View会截取一张图保存在sdcard/AutoClick/Screenshots/对应Activity文件夹  public boolean iterationScreenShots = true;  /**           * 迭代模式           快速模式:只启动Activity,快速检测崩溃问题(如兼容性、混淆、代码问题导致的崩溃),一般几分钟可完成。依赖于Params.json文件,该文件可由录制模式产生。           迭代模式:启动Activity并点击每个View。依赖于Params.json文件,该文件可由录制模式产生。           爬虫模式:通过迭代主页并记录新开Activity,迭代完毕后读取新开Activity,循环往复,直至无新的Activity。           录制模式:需人工操作应用,记录每个新开的Activity,供快速模式、迭代模式使用。录制模式可在功能测试阶段使用,录制模式默认休眠1个小时,期间操作应用打开的Activity都将被记录下来。           */  public Mode mode;  // 被测应用主页,必填项  public String homeActivity;  // 被测应用登录页,必填项  public String loginActivity;  // 被测应用登录账户,必填项  public String loginAccount;  // 被测应用登录密码,必填项  public String loginPassword;  // 被测应用登录页面登录按钮资源ID,必填项  public String loginId;  // 被测应用包名,必填项  public String PACKAGE;  // 忽略的Activities数组,此数组内的Activity不会遍历  public String[] ignoreActivities;  // 忽略的Views数组,此数组内的View不会遍历,需填写完整的资源ID,如com.xx:id/iv_fpc_back  public String[] ignoreViews;  // Activities截图保留开关,默认为true,如果为false,Activity遍历完成后,截图将会被清理,Activity发生崩溃时,截图不会被清理。  public boolean keepActivitiesScreenShots = true;</code></pre>    <p>示例:</p>    <pre>  <code class="language-java">package application.iteration;    import android.test.ActivityInstrumentationTestCase2;    import com.robotium.solo.Solo;    import org.junit.After;  import org.junit.Before;      @SuppressWarnings({"rawtypes", "deprecation"})  public class Iteration extends ActivityInstrumentationTestCase2 {        /**       * 被测应用包名       */      private static final String PACKAGE = "被测应用包名";        /**       * 被测应用Activity入口       */      private static final String LAUNCHER_ACTIVITY = "被测应用Activity入口";        private static Class<?> launcherActivityClass;        static {          try {              launcherActivityClass = Class.forName(LAUNCHER_ACTIVITY);          } catch (ClassNotFoundException e) {              throw new RuntimeException(e);          }      }        @SuppressWarnings("unchecked")      public Iteration() {          super(PACKAGE, launcherActivityClass);      }        private Solo solo;        @Before      public void setUp() throws Exception {          Solo.Config config = new Solo.Config();          // 遍历模式          config.mode = Solo.Config.Mode.REPTILE;          config.homeActivity = "被测应用主页Activity";          config.loginActivity = "被测应用登录Activity";          config.loginAccount = "登录帐号";          config.loginPassword = "登录密码";          config.loginId = "登录按钮ID";          // 被测应用包名          config.PACKAGE = PACKAGE;          config.ignoreActivities = new String[]{"忽略的Activity,此数组中的Activity将不会被遍历"};          config.ignoreViews = new String[]{"忽略的View,此数组中的View将不会被点击,需填入完整的资源ID"};            solo = new Solo(getInstrumentation(), config, getActivity());          super.setUp();      }        @After      public void tearDown() throws Exception {          solo.finishOpenedActivities();          super.tearDown();      }          /**       * 自动遍历入口       * @throws Exception 抛出异常       */      public void test_iteration() throws Exception {          solo.startIteration();      }    }</code></pre>    <p>AndroidManifest.xml配置</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  <manifest xmlns:android="http://schemas.android.com/apk/res/android"      package="被测应用包名.test"      android:versionCode="1"      android:versionName="1.0">        <uses-permission android:name="android.permission.GET_TASKS" />        <uses-sdk           android:minSdkVersion="18"          android:targetSdkVersion="24" />        <instrumentation          android:name="android.test.InstrumentationTestRunner"          android:targetPackage="被测应用包名" />        <application          android:icon="@drawable/ic_launcher"          android:label="@string/app_name" >          <uses-library android:name="android.test.runner" />      </application>    </manifest></code></pre>    <h2>怎么运行</h2>    <p>相关配置都到位了只要运行 test_iteration() 方法即可。</p>    <h2>相关截图</h2>    <p>跨应用</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ba9637aa0a80325894fb9b301cc70555.gif"></p>    <p>智能输入</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0b1956a2d7ab894ad7a11e372e815683.gif"></p>    <p>红点标记</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c729dbe3983746cd6bd551e1d1dc3b14.gif"></p>    <p>一触即达</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/68fbd20c265b8d73b49211b3cf161d84.gif"></p>    <h2>FAQ</h2>    <p>Q: 跳转到其他应用回不去怎么办?</p>    <p>A:可能存在机型兼容问题,如果遇到该情况可以把该View加入忽略数组。</p>    <p>Q:遍历时出现object not found怎么办?</p>    <p>A:Object文件是记录类似序列化的传入参数,记录在 sdcard/AutoClick/Object/ 目录下,务必保证它的存在。</p>    <p>Q:迭代模式下,一会就退出了,并没有遍历?</p>    <p>A:请检查 sdcard/AutoClick/Params.json 是否存在,或者该文件没有数据?</p>    <p>Q:自动遍历启动不了是什么情况?</p>    <p>A:请根据错误日志检查是否配置文件缺少必备参数,或者签名不一致?</p>    <p>Q:程序中途终止了?</p>    <p>A:确保数据线是连接状态,遍历需要用到adb</p>    <p>Q:Android 6.0及以上版本时,卡在授权界面?</p>    <p>A:如果第三方厂商更改过底层代码,可能出现兼容性问题(如小米),此时需要在 Permission.java 类中增加相应的包名及授权资源id,通过 uiautomatorviewer 查看授权界面信息。</p>    <h2> </h2>    <p>来自:https://testerhome.com/topics/7413</p>    <p> </p>