前排提醒 :文章稍微长了点,文字占比也高,但是读完你会感觉花费的时间是值得的!
做这个的初衷是发现项目中的崩溃问题(即稳定性)。Monkey 达不到全覆盖,也试过思寒的AppCrawler,无奈速度上不太理想。我需要的是更快的反馈结果,于是乎着手自己写一个方案,也当做是提高编码能力,或者说对 Android 有更深入的理解。
初期目标是想替代 Monkey,众所周知 Monkey 的随机点击,以及不可控性,并不能做到完整的遍历。所以当下最主要的功能是发现崩溃问题(如兼容性、混淆、代码问题导致的崩溃),额外可以做的是发现无数据时的空白布局(配合接口工具,启用快速模式验证)、发现无网络时是否显示无网络的布局(关闭网络,启用快速模式)等等。
在我们的产品上,启用爬虫模式试跑了几个小时发现了 5 个崩溃问题。当然发现第一个崩溃时自动遍历就停止了,它依赖于被测应用,被测应用崩溃,它也会一同退出,这是接下来要解决的问题(增加重启机制 )。当崩溃问题不予修复时,继续遍历,还是会走到第一个崩溃(可复现性 ),此时可以把崩溃的 Activity 加入忽略列表。
崩溃问题:
发现的崩溃问题都是正常操作的,非异常操作(举例:非人类手速的点击等等)。换句话说就是用户也会遇到该问题
擅长发现异步请求导致的崩溃问题
可跨应用
多种模式
智能输入
红点标记
无惧遮挡
完全遍历
一触即达
可复现性
多重跟踪
支持 Hybrid
关于跳转的处理
每次点击后都会判断是否离开遍历 Activity(未离开则进入下一个点击事件)
如果跳转到本应用其他 Activity(则按下返回键返回,返回后回不到遍历 Activity 则重启该 Activity 并重新遍历剩余 View)
如果跳转到其他应用去了(如相机)则直接重启该 Activity 并重新遍历剩余 View
如果跳转到登录页面则登录后继续操作(可能存在遍历时点击到退出登录按钮)
关于直接启动 Activity 的处理
通过监听 Activity 的启动,拿到 Activity 实例并获取传入参数,看下流程图可能好理解:
参数描述:
// 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;
示例:
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();
}
}
AndroidManifest.xml 配置
<?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>
相关配置都到位了只要运行test_iteration()
方法即可。
跨应用
智能输入
红点标记
一触即达
Q: 跳转到其他应用回不去怎么办?
A:可能存在机型兼容问题,如果遇到该情况可以把该 View 加入忽略数组。
Q:遍历时出现 object not found 怎么办?
A:Object 文件是记录类似序列化的传入参数,记录在sdcard/AutoClick/Object/
目录下,务必保证它的存在。
Q:迭代模式下,一会就退出了,并没有遍历?
A:请检查sdcard/AutoClick/Params.json
是否存在,或者该文件没有数据?
Q:自动遍历启动不了是什么情况?
A:请根据错误日志检查是否配置文件缺少必备参数,或者签名不一致?
Q:程序中途终止了?
A:确保数据线是连接状态,遍历需要用到 adb
Q:Android 6.0 及以上版本时,卡在授权界面?
A:如果第三方厂商更改过底层代码,可能出现兼容性问题(如小米),此时需要在Permission.java
类中增加相应的包名及授权资源 id,通过uiautomatorviewer
查看授权界面信息。