测试历险记 App 自动遍历测试_UICrawler

笑哼 · 2020年12月29日 · 3987 次阅读

一、背景

工具与 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)

五、将 app 接入 crawler

前提:在本地使用 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 目录中得图片是崩溃前的几次操作,以及相应的操作录屏

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册