前排提醒 :文章稍微长了点,文字占比也高,但是读完你会感觉花费的时间是值得的!
做这个的初衷是发现项目中的崩溃问题(即稳定性)。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
查看授权界面信息。
非常强,666666666666666666666
很实用!!6666666
黑妞开挂的节奏!
感谢分享
谢谢分享,慢慢细品中~
挂都不是这么开的。。。
不错,速度有多快呢?
—— 来自 TesterHome 官方 安卓客户端
学习一下
厉害,晚上引入项目试用一下
#7 楼 @erickyang 正常操作 app 的速度,大概 1.5-2s 点击一次,嫌不够快可以把休眠时间设置小一点。
你看上面那张跨应用的 gif 图就知道有多快的,秒点
能不能避免退出页面后很难回到该页面的问题?比如点了退出登录
老司机开车了
录制 intent 然后回放。楼主想法很给力,star 下。
1.如果 activity 通过非 intent 方式 就没法遍历到了吧?
2.遍历覆盖率取决于录制的操作?
#17 楼 @zhangzhao_lenovo 绝大部分都是通过 intent 启动的,好像那种插件化以代理的形式代理全部生命周期的就不是通过 intent,不是很了解哈。
文中有提到 activity 的关联度,当然录制也是有关系的。
来个全功能测试,然后全部界面就有了吧,嘿嘿
#19 楼 @erickyang 可以达到 monkey 的速度,我手动休眠了 1.5s 而已,你看一下文中的图,跨应用那个 gif 图,够不够快
你好,请问一下 ,LAUNCHER_ACTIVITY、homeActivity... 这些是只需要配置成 Actvitiy 的名称么(如:MainActviity)?执行时发现报错:Caused by: java.lang.ClassNotFoundException:,配置的 LAUNCHER_ACTIVITY 未找到
#21 楼 @Tonyzhangcanon 看被测应用的是什么就是什么了,比如有的是:com.xx.homeactivity 有的是 com.xx.MainActviity
Q:Android 6.0 及以上版本时,卡在授权界面?
这个问题可以通过解析AndroidManifest
文件中的permission
声明,应用安装之后启动之前再使用 adb shell pm grant <package name> android.permission.*
指令解决。
当然,这样并不符合真实用户的使用场景。
#23 楼 @Tonyzhangcanon 写全,需要完整的路径
#24 楼 @xubin98246 谢谢你提供的参考,应该有人用得上
你们的开发应该在膜拜你吧😂😂😂,黑妞出品,给力
—— 来自 TesterHome 官方 安卓客户端
how new be....
xue xi zhong...
不错
加精理由:设计优秀 工具实用强大 介绍详细
在你的 github 上加上社区的链接吧 帮社区宣传下 今年的第三届中国移动互联网测试大会 有个优秀开源项目颁奖 你这个项目已经进入提名了 好好搞起来
这个对于 webview 是否友好?另外如果客户端 session 容易超时怎么办呢
#27 楼 @hu_qingen 这倒没有,我请教过他们很多问题,应该感激他们才对
#32 楼 @seveniruby 好,谢谢
#33 楼 @Lihuazhang webview 没有过多的尝试,只是试过百度和自家产品的 web 页面,session 过时问题可以重新执行一次登录操作?或者由客户端本身控制吧
不错的实用工具
这个要装 robotium、fiddler 么?可以跨平台么?像 fiddler 不支持 mac
Error:ProGuard: [AutoClick] java.io.IOException: Can't read /temp/proguard_input.jar
版本不兼容咋个弄- -
非常不错,mark
怎么做到无惧遮挡的 appium 与 robotium 的差距么?
#56 楼 @junewang 这个通过跨应用解决的,具体看
https://testerhome.com/topics/7273
public void test_iteration() 之前不需要加@Test吗? 否则怎么 run 起来?
在 Gradle 环境中又修改了几个 Error 之后已经可以运行
遇到的问题
1.首次启动时需要滑动的 Guide 页似乎没有操作,手动初始化之后启动了 loginActivityy
2.REPTILE 模式,登录成功后在首页一只没有操作,Log 有在打印(D/StrictMode:,好像似乎想先遍历 loginActivity 中的控件?一直在打印 restart activity : loginActivity)
可以提供一些 Log 中的 Tag 和关键字 ,方便确定下在做什么操作,遇到什么问题之类的
#66 楼 @Tonyzhangcanon 首页的滑动不会滑,直接跳过 来到登录页面,restart activity 是因为这个 activity 启动不了,你们的登录页面启动是否需要参数?因为我默认是没用参数启动的。日志过滤用 Robotium 可以看到所有操作日志
#70 楼 @Tonyzhangcanon 你去除掉每次启动自动登录就好了
#70 楼 @Tonyzhangcanon 加我企鹅 335827476,请教你点问题
我想问下,像微信这种下面 4 个切换页面的,元素只不过是隐藏了么?辅助功能能直接无视这种结构去点击么??
厉害。留着慢慢学习!
(1) bin 下面一直会生成 AutoClick.apk, 然后安装这个 apk,我指定的 apk 怎么没安装啊?
(2) 我在 Project 竟然搜不到"Uploading AutoClick-master.apk onto device..." 这句话. 打开方式不对?
很好很强大,感觉好东西都是 java 写的。
传送门是 home 的地址,赞一个。
我看了下,挺不多,很符合。但我不是很懂,我先赞下,再来琢磨,学习,运用到项目上。感谢。
@heyniu 请问这是什么原因
这个 test runner 要重新用编译器改成 com.heyniu.auto.InstxxxxRunner
小白还不太会用,先收藏,再学习
@heyniu 改成自己的 keystore 后,报错如下 ,是需要哪里设置密码吗?
Error:Android Packager: [AutoClick] java.io.IOException: Keystore was tampered with, or password was incorrect
@heyniu ,有些应用不需要登录的,有跳过的配置?
AutoClick 是单独的一个 module 吗? 配置了 LAUNCHER_ACTIVITY 是被测 app 的 PackageName+ActivityName 还是提示 java.lang.ClassNotFoundException。应该怎么配置?
暂时放弃了,以后有机会再细细的读一遍,赞一个!~
一切技术都为解决实际问题服务,我更感兴趣的是关于多重跟踪这块,希望有更多的图文描述,以供借鉴。
我之前就说了哪个测试工具或者框架能解决环境搭建问题,基本就成功了 80%
在sdcard/AutoClick/package/Crash
目录下会产生一个Crash
日志
假设日志信息如下
一般日志中有提到哪个界面出现崩溃
mapping.txt
查找如果没有提到哪里出现崩溃则在sdcard/AutoClick/package
目录下打开Activities.txt
查看最后打开的界面是哪个
通过以上步骤能拿到最后的一个遍历的界面,再到sdcard/AutoClick/package/Screenshots
目录下查找该界面的文件夹进入,方可看到遍历该界面的步骤截图
sdcard/AutoClick/package/Log
目录下最新的 log 文件,里面记录着以下日志
Crash
来源Crash
发生的时间逐一排查对应的截图、日志、接口信息尝试手动操作复现Crash
后打开对应的源码,尝试修复
是的,因为太多人都没多少基础,但是又需要短时间内用你这个工具,比如说我。我看过思寒的 AppCrawler,有视频和文档指引,就很容易上手,但那工具略慢....
楼主,这玩意怎么用,有 demo 介绍吗,实在看不懂,但是又想用。。。
弄了好些时间还是跑不起来,测试启动后被测应用界面闪了下后报错:Test run failed: Instrumentation run failed due to 'java.lang.NullPointerException'
logcat 的日志是:
E/AndroidRuntime( 7643): java.lang.RuntimeException: Unable to start activity ComponentInfo{PACKAGENAME/PACKAGENAME.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'android.support.v4.app.TaskStackBuilder android.support.v4.app.TaskStackBuilder.addParentStack(android.app.Activity)' on a null object reference
E/AndroidRuntime( 7643): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2298)
E/AndroidRuntime( 7643): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2360)
E/AndroidRuntime( 7643): at android.app.ActivityThread.access$800(ActivityThread.java:144)
E/AndroidRuntime( 7643): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1278)
E/AndroidRuntime( 7643): at android.os.Handler.dispatchMessage(Handler.java:102)
E/AndroidRuntime( 7643): at android.os.Looper.loop(Looper.java:135)
E/AndroidRuntime( 7643): at android.app.ActivityThread.main(ActivityThread.java:5221)
E/AndroidRuntime( 7643): at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime( 7643): at java.lang.reflect.Method.invoke(Method.java:372)
E/AndroidRuntime( 7643): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
E/AndroidRuntime( 7643): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
E/AndroidRuntime( 7643): Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.support.v4.app.TaskStackBuilder android.support.v4.app.TaskStackBuilder.addParentStack(android.app.Activity)' on a null object reference
E/AndroidRuntime( 7643): at android.support.v7.app.AppCompatActivity.onCreateSupportNavigateUpTaskStack(AppCompatActivity.java:353)
E/AndroidRuntime( 7643): at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:74)
E/AndroidRuntime( 7643): at PACKAGENAME.MainActivity.onCreate(MainActivity.java:64)
E/AndroidRuntime( 7643): at android.app.Activity.performCreate(Activity.java:5933)
E/AndroidRuntime( 7643): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105)
E/AndroidRuntime( 7643): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2251)
E/AndroidRuntime( 7643): ... 10 more
W/ActivityManager( 562): Error in app PACKAGENAME running instrumentation ComponentInfo{PACKAGENAME.test/com.heyniu.auto.InstrumentationTestRunner}:
W/ActivityManager( 562): java.lang.NullPointerException
W/ActivityManager( 562): java.lang.NullPointerException: Attempt to invoke virtual method 'android.support.v4.app.TaskStackBuilder android.support.v4.app.TaskStackBuilder.addParentStack(android.app.Activity)' on a null object reference
请问是哪里出问题了?对 robotium 不熟悉..
V4 包没有这个方法,你看下升级下 V4 包,问下你们技术 V4 包版本多少的 替换掉我的,我的是 23.3.0
下面是我执行前的环境配置,给需要的同学个参考:
1\使用 eclipse 导入工程:imports as android project;
2\更改 project build target 为 7.1.1;
3\更改 jdk complier compliance level 为 1.7(本地的 java -version 为 1.8);
4\处理些报错提示,主要是添加 final;
5\manifest 里面替换被测应用包名;
6\Iteration.java 文件中,填写 LAUNCHER_ACTIVITY 和 homeActivity,登录相关的因为用不到用" "代替,其他用不到部分清空;
7\因为项目没有登录页面,屏蔽 test_00_login 的代码;
8\被测项目用的 v4 版本更新,更新 support-v4-23.3.0(删除自带的 support-v4 后通过 android tools 的 add support library 更新);
9\安装后使用命令执行测试:adb shell am instrument -w -r -w -e debug false -e class application.iteration.Iteration#test_01_iteration 被测应用包名.test/com.heyniu.auto.InstrumentationTestRunner
使用 eclipse 直接执行会报 Process crashed
@heyniu 拉了代码实验了下,对部分控件可能支持不是很好,例如 spinner,不过思路还是不错
主要是来学习老司机的思路。
今天想用下你的自动遍历方案,导入工程后,很多文件错误,想问下这是什么原因导致的?首先我认为你的源码应该是没问题的,但却有很多红叉,是因为我缺少什么吗?比如
按你这设置后还真少了很多红叉,但还有些红叉,如下提示不知怎么处理,麻烦了啊
[2017-03-29 09:56:45 - AutoClick-power] Android requires compiler compliance level 5.0 or 6.0. Found '1.8' instead. Please use Android Tools > Fix Project Properties.
我现在没有红叉文件了,但提示 Android requires compiler compliance level 5.0 or 6.0. Found '1.8' instead. Please use Android Tools > Fix Project Properties.,,,请问你说的 “把 lolipop 的都改成 21”,我看了半天没找到位置
妞,我现在没有红叉文件了,但提示 Android requires compiler compliance level 5.0 or 6.0. Found '1.8' instead. Please use Android Tools > Fix Project Properties.,,,请问你说的 “把 lolipop 的都改成 21”,我看了半天没找到位置
请问下,签名文件放在哪个目录下啊?运行报错:Test run failed: Permission Denial: starting instrumentation ComponentInfo{com.qshealthcare.com.wtmd.test/com.heyniu.auto.InstrumentationTestRunner} from pid=16063, uid=16063 not allowed because package com.qshealthcare.com.wtmd.test does not have a signature matching the target com.qshealthcare.com.wtmd
@heyniu 用 szhomebbs.keystore 重签名我的应用,需要用户名和密码么?
授权问题,appium 可以解决啊,同时也可以不用滑屏吧 获取 android 常见控件名称,开启多线程,不停获取可以点击的控件,然后点击就可以了吧
弄了两天了,一直运行不成功,楼主能写个环境搭建的流程吗,毕竟每个人环境不一样,都快放弃 了
你好,我这边弄下来提示找不到依赖了,是什么更新了吗请问,需要如何解决
junit.framework.AssertionFailedError: Exception in constructor: test_00_login (java.lang.ClassNotFoundException: com.qianxun.comic.apps.WelcomeActivity
环境都配置好了,报这个错,我是 android studio 上面弄的,请问大神,我这边是上面步骤没弄好吗
打开 Run/Debug Configurations,看看是否已 android junit 启动了?
改成以 Android Instrumented tests 方式启动就好了
我也是用的 android studio
http://stackoverflow.com/questions/2422378/intellij-idea-with-junit-4-7-junit-version-3-8-or-later-expected
The latest Android Studio might have this issue with slightly different case, the test class/method should be fall under Android Tests instead of JUnit in Run/Debug Configurations. See my answer below.
@heyniu 想请问一下,对于已经安装在系统里的应用是否可以遍历?无 APK。
@heyniu 楼主好,我把你的遍历工程导入到 Android Studio 上。在 Android Studio 可以运行,但是想通过断点的方式看看整体的执行流程,却发现不能 debug。网上找了好久也没解决,请问需要什么配置么。
错误告警如下:
Error running Iteration:
Cannot debug application from module AutoClick on device lge-nexus_5-055846c6f0df5ffb.
This application does not have the debuggable attribute enabled in its manifest.
If you have manually set it in the manifest, then remove it and let the IDE automatically assign it.
If you are using Gradle, make sure that your current variant is debuggable.
@heyniu ,为什么启动 app 时会出现 java.lang.IncompatibleClassChangeError,好像是 support 包冲突,只使用 robotium 可以启动 app,一旦添加了 support 包,就会出现这个错误,该怎么解决啊?