工具与 monkey 不同,monkey 是发送随机 seed 给你随机乱点,然后记录日志,而自动遍历是将 app 一层一层点开,每个空间都能遍历到!
回归测试,遍历基本的界面,了解主要界面的可用性. 比如兼容性,基本功能;
利用遍历获取 app 的加载时间和性能数据,需要借助其他的性能数据抓取工具;
利用遍历验证 app 的内存泄漏、Crash、Anr 等功能
抓取接口请求 辅助验证一些模块基本接口,并辅助分析接口调用流程,为接口测试做准备;
目前主要用于验证 app 是否发生 Crash(其它价值基本有其他工具或者方法可以获取,暂时不在其他方面投入精力)
1、google 的 crawler
谷歌官方发布应用遍历工具 App Crawler:https://testerhome.com/topics/20545
缺点:相关资料比较少,只有一个 Jar 包,只支持安卓端遍历,通用性不好
优点:google 出品的,对安卓兼容性较好
2、思寒的 appcrawler
github 地址:https://github.com/seveniruby/AppCrawler
自动化遍历测试分析报告
缺点:代码有三年多没更新了,代码是 scala 写的,学习一门新语言成本较高,安卓和 iOS 都支持(底层是 appium)
优点:执行报告可视化较好
3、UICrawler
UICrawler:https://github.com/lgxqf/UICrawler
缺点:执行报告不好看
优点:安卓和 iOS,都支持(底层是 appium),代码是 Java 写的(可以方便的对其修改以达到自己需要的功能),5 个月之前还有较多代码更新
最终采用在 UICrawler 基础上来对快驴 app 进行自动遍历测试
判断 Crash 的方法:
发生 Crash 后会退出待测 app,Crash 前后所处的包名不相同
Android:当前所处的包名是否有效(有效:当前待测 app 的包名及白名单包名)
iOS:实时的在 Crash Report 中判断当前是否包含字段 IOS_IPA_NAME 的值 (这也导致了 iOS 的慢,实时获取设备日志,判断是否发生 Crash)
前提:在本地使用 appium 能正常执行 app 自动化
1、快速的在自己业务线中接入遍历测试,主要对配置文件做一些自定义的配置即可满足大部分需求:
配置文件是遍历运行的核心,用于对具体业务场景做定制。可以对遍历范围、遍历顺序、运行时长、平台类型、报告等进行配置
工程中得 readme 包含较多配置文件中可能需要修改的字段的值的解释
2、可以借鉴到 app 自动化 or 稳定性测试的点(后续 APP 自动化、稳定性测试可以做的)
日志收集
在执行 app 自动化时,可以增加代码判断是否发生 Crash 并收集相关日志,利用 libimobiledevice 中的 idevicecrashreport 工具可以导出真机 crash 日志和设备日志
idevicecrashreport -u 57aff9d7339e56fcb36e1a49929cde55b7d78578 /Users/xiaoheng/Downloads/meituan/crawler/crawler_output/ //-u 指的是设备uuid,收集iOS的crash日志
if(Util.isAndroid()){
cmd.add("adb -s " + ConfigUtil.getUdid() + " logcat > " + logName);//收集Android的设备日志
}else{
cmd.add("idevicesyslog -u " + ConfigUtil.getUdid() + " > " + logName);//收集iOS的设备日志
}
截图中的描点功能
在 PictureUtil 类中的 takeAndModifyScreenShot 方法,可以将其用到 app 自动化中,以便更好和更直观的查看设备执行过程
static String takeAndModifyScreenShot(List<Point> pointList,int radius,String ext){
String img = Driver.takeScreenShot();
drawPoint(img,pointList,radius,radius,Color.RED);//以对控件点击的位置为圆心,radius为半径画了一个圆
File file = new File(img);
if(pointList.size() > 1) {
img = img.replace(".png", "_" + ext + ".png");
}else{
Point point = pointList.get(0);
img = img.replace(".png", "_X-" + point.x + "-Y-" + point.y + "-" + ext + ".png");
}
File newFile = new File(img);
file.renameTo(newFile);
log.info("Screen shot is renamed to: " + newFile.getAbsolutePath());
return img;
}
录屏功能
利用所有的有序截图生成一个录屏,以便更好和更直观的查看设备执行过程,在 PictureUtil 类中 picToVideo 方法中
public static void picToVideo(String fileName, List<String> list){
SeekableByteChannel out = null;
AWTSequenceEncoder encoder = null;
int size = list.size();
int index = 0;
try {
out = NIOUtils.writableFileChannel(fileName);
encoder = new AWTSequenceEncoder(out, Rational.R(1, 1));
for(String file : list) {
index ++;
BufferedImage image = ImageIO.read(new File(file));
image = rotateImage(image);
encoder.encodeImage(image);
if((index % 5) == 0){
log.info("Video generation complete " + (index*100/size) + "%");
}
}
}catch (Exception e){
log.error("Fail to generate video!");
e.printStackTrace();
} finally {
try {
encoder.finish();
}catch (Exception e){
e.printStackTrace();
}
NIOUtils.closeQuietly(out);
}
}
监控是否处于登录态
if (ConfigUtil.isAutoLoginEnabled()) {
try {
log.info("userLoginCount:"+userLoginCount);
log.info("userLoginInterval:"+userLoginInterval);
log.info("0 == userLoginCount % userLoginInterval:"+(0 == userLoginCount % userLoginInterval));
if (0 == userLoginCount % userLoginInterval) { //这里默认是页面ui每次刷新都要判断是否登录,userLoginCount=0,userLoginInterval=5
log.info("Processing login operation");
xml = userLogin(xml);
}
} catch (Exception e) {
e.printStackTrace();
log.error("Fail to log in!");
}
userLoginCount++;
}
1、无 Crash
2、有 Crash
下图中 Crash 目录中得图片是崩溃前的几次操作,以及相应的操作录屏