Android性能自动化测试

开发找到我说:“反复进入阅读器存在内存泄漏最终导致内存溢出(out of memory,即 oom问题)你帮忙测试一下”。听到此需求有两点要解决,一个是长时间反复进入阅读器,一个是性能数据监控。测试时间有限,不可能一直手动翻页去看书,效率低不说,翻的手指头疼。内存怎么看呢?市面上有很多工具,其中prefdog是业界相对来说做的最好的,但是随着prefdog的收费,长时间的性能测试花费较大,于是就有了本篇文章的诞生。

本文主要实现通过monkey脚本和Airtest进行自动化重复操作,使用Python通过adb命令获取数据信息,通过PyQt5创建图形化界面,在PyQtGraph中实时显示数据,Excel保存数据。

两种实现APP自动化的方式

一、monkey脚本:

1.monkey简介

Monkey 是一个在您的模拟器或设备上运行的程序,它生成用户事件的伪随机流,例如点击、触摸或手势,以及许多系统级事件。您可以使用 Monkey 以随机但可重复的方式对您正在开发的应用程序进行压力测试

2.monkey自动化脚本命令

有关monkey事件、参数、日志管理网上有很多文章可参考,此文主要介绍monkey脚本中的命令。monkeyscript是monkey的脚本语言,能够被monkey识别的命令集合,可以实现一些固定的重复性动作。Monkey可以通过命令加载脚本来进行测试,简单方便。

脚本命令

  • LaunchActivity(pkg_name, cl_name): 启动应用,pkg_name包名,cl_name启动的activity名

  • DispatchPress(keycode): 系统按键,例如home键,back键;

  • UserWait:等待

  • Tap(x, y, tapDuration):模拟一次手指单击事件,x,y:坐标值,tapDuration:可选项,表示单击的持续时间

  • Drag(xStart, yStart, xEnd, yEnd) :在屏幕上滑动,坐标是从哪一点滑到哪一点

  • LongPress(): 长按2s

  • ProfileWait(): 等待5s

  • PressAndHold(x, y, pressDuration) :模拟长按

  • PinchZoom(x1Start, y1Start, x1End, y1End, x2Start, y2Start, x2End, y2End, stepCount): 模拟缩放

  • DispatchString(input): 输入字符串

  • RunCmd(cmd) :执行shell命令,比如截图 screencap -p /data/local/tmp/tmp.png

  • DispatchFlip(true/false) :打开或者关闭软键盘

  • DeviceWakeUp() :唤醒屏幕

示例反复进入阅读器脚本read.txt

#头文件信息
type=raw events
count=10
speed=1.0
start data >>
#具体的脚本内容
LaunchActivity(org.geometerplus.android.fbreader.FBReader)
UserWait(1000)
Tap(436,1425)
UserWait(1000)
DispatchPress(KEYCODE_BACK)

3.执行脚本

a. 编写脚本read.txt

b. 将脚本上传入手机

adb push D:\read.txt(电脑中的位置) /sdcard(手机中的位置)

c. 执行脚本

adb shell monkey -f sdcard/read.txt(手机中的位置) 1000(次数)

二、Airtest

1.Airtest的介绍

Airtest是一个跨平台的、基于图像识别的UI自动化测试框架,适用于游戏和App测试,支持平台有Windows、Android和iOS平台,原理:利用截图的方式,在已展示出的手机界面中寻找所匹配的图片。

有关Airtest安装、环境配置、连接移动设备,官网都有详细说明https://airtest.doc.io.netease.com/

2.Airtest中的事件介绍

在Airtest辅助窗悬停可点到事件参数的具体介绍和用法。

Airtest事件

  • touch:触摸

  • swipe:滑动

  • exists:存在什么样的元素

  • keyevent:手机按键点击,比如:HOME,BACK

  • text:输入文本

  • snapshot:截图功能

  • sleep:延迟几秒

  • assert_exists:断言截图是否存在于当前页面,后面文案是测试点

  • assert_not_exists:断言截图内容不存在与当前这个页面,后面文案是测试点

  • assert_equal:断言first和second是否相等,相等就通过

  • assert_not_equal:断言first和second是否相等,不相等就会通过

  • wait:超时

示例反复进入阅读器脚本read.air

# -*- encoding=utf8 -*-
__author__ = "admin"

from airtest.core.api import *
import datetime
auto_setup(__file__)

def repeated_entry_reader(num):
    """反复进入阅读器"""
    # 开始时间
    start_time = datetime.datetime.now()
    for i in range(num):   
        # 点击进入阅读器
        touch((400, 1200),duration=0.05)  
        # 返回
        keyevent("BACK") 
        # 当前时间
        over_time = datetime.datetime.now()
        # 已运行时间
        total_time = (over_time-start_time).total_seconds()
        # 转化成min,保留2位小数
        total_time_min = round(total_time/60, 2) 
        print("点击%s次"%(i+1),"剩余%s次"%(num-i-1))
        print('已运行%s分钟' % total_time_min)

if __name__ == '__main__':
    repeated_entry_reader(10)

性能数据动态展示

1.介绍

Android性能数据动态展示工具,可以绘制CPU、Memory、FPS信息,并保存数据。

2.软件架构

  • 工具python3.8编写

  • CPU、Memory、FPS信息是通过adb命令获得出的数据来解析的

  • GUI界面PyQt5创建图形化界面,在PyQtGraph中实时显示数据

  • 通过Excel保存数据

3.安装教程

a. 安装python3.8,官网下载地址https://www.python.org/downloads/

b. 安装PyQt5

pip install PyQt5

c. 安装PyQtGraph

pip install PyQtGraph

d.安装其他依赖库

4.实现代码monitoringdata.py

from PyQt5 import QtWidgets, QtCore, QtGui
import pyqtgraph as pg
import sys
import traceback
import csv, os, time, math

class MonitoringData(QtWidgets.QMainWindow):
    def __init__(self, pkg, device_name):
        super().__init__()
        self.pkg = pkg  # 包名
        self.device_name = device_name  # 设备名
        self.timer_interval(2000)  # 数据刷新时间间隔
        self.data_list = []
        self.cpu_data = []
        self.memory_data = []
        self.fps_data = []
        self.cpucsvfile = open('./CPU_' + time.strftime("%Y_%m_%d_%H_%M_%S") + '.csv', 'w', encoding='utf8', newline='')
        self.save_data('cpu', [('timestamp', 'CPU(%)')])  # 定义cpu数据列表title
        self.memcsvfile = open('./Memory_' + time.strftime("%Y_%m_%d_%H_%M_%S") + '.csv', 'w', encoding='utf8', newline='')
        self.save_data('mem', [('timestamp', 'Memory(MB)')])  # 定义Memory数据列表title
        self.fpscsvfile = open('./FPS_' + time.strftime("%Y_%m_%d_%H_%M_%S") + '.csv', 'w', encoding='utf8', newline='')
        self.save_data('fps', [('timestamp', 'FPS')])  # 定义FPS数据列表title
        # 创建监控窗口
        self.setWindowTitle("App性能数据显示")
        self.App_monitoring_data = QtWidgets.QWidget()  # 创建一个主部件
        self.setCentralWidget(self.App_monitoring_data)  # 设置窗口默认部件
        self.resize(800, 800)  # 设置窗口大小
        # 创建cpu监控图像
        self.cpu_image = QtWidgets.QGridLayout()  # 创建cpu网格布局
        self.App_monitoring_data.setLayout(self.cpu_image)  # 设置cpu的主部件为网格
        self.cpu_plot_widget = QtWidgets.QWidget()  # cpu的widget部件作为K线图部件
        self.plot_layout = QtWidgets.QGridLayout()  # cpu的网格布局层
        self.cpu_plot_widget.setLayout(self.plot_layout)  # 设置K线图部件的布局层
        self.cpu_plot_plt = pg.PlotWidget(title='CPU', left='CPU(%)')  # cpu的绘图部件
        self.cpu_plot_plt.showGrid(x=True, y=True)  # 显示cpu图形
        self.plot_layout.addWidget(self.cpu_plot_plt)  # 添加绘图部件到K线图部件的网格布局层
        self.cpu_image.addWidget(self.cpu_plot_widget, 2, 0, 3, 3)  # 将上述部件添加到布局层中
        self.cpu_plot_plt.setYRange(max=120, min=0)  # 设置cpu的纵坐标范围
        # 创建Memory监控图像
        self.mem_image = QtWidgets.QGridLayout()  # 创建memory网格布局
        self.App_monitoring_data.setLayout(self.mem_image)  # 设置memory主部件的布局为网格
        self.mem_plot_widget = QtWidgets.QWidget()  # memory的widget部件作为K线图部件
        self.mem_plot_layout = QtWidgets.QGridLayout()  # memory的网格布局层
        self.mem_plot_widget.setLayout(self.mem_plot_layout)  # 设置K线图部件的布局层
        self.mem_plot_plt = pg.PlotWidget(title='Memory', left='Pss Total(MB)')  # memory绘图部件
        self.mem_plot_plt.showGrid(x=True, y=True)  # 显示memory图形
        self.plot_layout.addWidget(self.mem_plot_plt)  # 添加绘图部件到K线图部件的网格布局层
        self.mem_image.addWidget(self.mem_plot_widget, 1, 0, 3, 3)  # 将上述部件添加到布局层中
        self.mem_plot_plt.setYRange(max=600, min=0)  # 设置memory的纵坐标范围
        # 创建FPS监控图像
        self.fps_image = QtWidgets.QGridLayout()  # 创建fps网格布局
        self.App_monitoring_data.setLayout(self.fps_image)  # 设置fps主部件的布局为网格
        self.fps_plot_widget = QtWidgets.QWidget()  # fps的widget部件作为K线图部件
        self.fps_plot_layout = QtWidgets.QGridLayout()  # fps的网格布局层
        self.fps_plot_widget.setLayout(self.fps_plot_layout)  # 设置K线图部件的布局层
        self.fps_plot_plt = pg.PlotWidget(title='FPS', left='FPS')  # fps绘图部件
        self.fps_plot_plt.showGrid(x=True, y=True)  # 显示fps图形网格
        self.plot_layout.addWidget(self.fps_plot_plt)  # 添加绘图部件到K线图部件的网格布局层
        self.fps_image.addWidget(self.fps_plot_widget, 1, 0, 3, 3)  # 将上述部件添加到布局层中
        self.fps_plot_plt.setYRange(max=70, min=0)  # 设置fps的纵坐标范围

    def timer_interval(self, timeinterval):
        """启动定时器 时间间隔秒"""
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.get_cpu_info)
        self.timer.timeout.connect(self.get_memory_info)
        self.timer.timeout.connect(self.get_fps_info)
        self.timer.start(timeinterval)

    def get_current_time(self):
         """获取当前时间"""
        currenttime = time.strftime("%H:%M:%S", time.localtime())
        return currenttime

    def get_cpu_info(self):
        """获取cpu数据"""
        try:
            result = os.popen("adb -s {} shell dumpsys cpuinfo | findstr {}".format(self.device_name, self.pkg))
            # result = os.popen("adb -s {} shell top -m 100 -n 1 -d 1 | findstr {}".format(self.device_name, self.pkg))  # 执行adb命令
            res = result.readline().split(" ")  # 将获取的行数据使用空格进行分割
            if res == ['']:  # 处理没有数据的情况
                print('no data')
                pass
            else:
                cpuvalue1 = list(filter(None, res))[2]  # 获取cpu
                cpuvalue = cpuvalue1.strip('%')  # 去除%号
                current_time = self.get_current_time()
                if cpuvalue == 'R':  # 过滤cpu等于R
                    pass
                else:
                    cpu = float(cpuvalue)
                    print("CPU:", cpu)
                    self.save_data('cpu', [(current_time, cpuvalue)])  # 将数据保存到Excel
                    self.data_list.append(cpu)   # 将数据写入列表
                    self.cpu_plot_plt.plot().setData(self.data_list, pen='g')  # 将数据载入图像中
        except Exception as e:
            print(traceback.print_exc())

    def get_memory_info(self):
        """获取Memory数据"""
        try:
            result = os.popen("adb -s {} shell dumpsys meminfo {}".format(self.device_name, self.pkg))  # 执行adb命令
            res = result.readlines()
            for line in res:
                if "TOTAL:" in line:  # 不同手机adb shell dumpsys meminfo packagename 获取的Pss Total 不同,有的手机是TOTAL:,有的是TOTAL PSS:,这里做了一下兼容
                    pss_total1 = line.split(" ")[18]  # 将获取的行数据使用空格进行分割并取出第 18个元素
                elif 'TOTAL PSS:' in line:
                    pss_total1 = line.split(" ")[15]  # 将获取的行数据使用空格进行分割并取出第 15个元素
                else:
                    continue
                pss_total = round(float(pss_total1) / 1024, 2)  # 单位换算成MB,保留2位小数
                current_time = self.get_current_time()
                print("Memory:", pss_total)
                self.save_data('mem', [(current_time, pss_total)])  # 将数据保存到Excel
                self.memory_data.append(pss_total)  # 将数据加入列表
                self.mem_plot_plt.plot().setData(self.memory_data, pen='y')  # 将数据载入图像中
        except Exception as e:
            print(traceback.print_exc())

    def get_fps_info(self):
        """获取fps数据"""
        try:
            result = os.popen("adb -s {} shell dumpsys gfxinfo {}".format(self.device_name, self.pkg))  # 执行adb命令
            res = result.readlines()  # 获取所有行数据
            frame_count = 0  # 定义frame_count初始值
            vsync_overtime_s = []  # 定义vsync_overtime_s列表
            jank_num = 0  # 定义jank_num初始值
            for line in res:  # 循环行
                if '\t' in line:  # 取出带\t的所有行
                    if '\tcom.kmxs.reader' in line:  # 过滤\tcom.kmxs.reader数据
                        r = False
                    elif '\tDraw' in line:  # 过滤\tDraw数据
                        r = False
                    elif '/android.view' in line:
                        r = False
                    else:
                        frame_count = frame_count + 1  # 循环次数
                        fps = line.split('\t')  # 分离数据
                        # print(fps)
                        Draw = float(fps[1])  # 取数据
                        Prepare = float(fps[2])  # 取数据
                        Process = float(fps[3])  # 取数据
                        Execute = float(fps[4].replace('\n', ''))  # 取数据
                        render_time = Draw + Prepare + Process + Execute  # 计算render_time
                        # print(render_time)
                        # print('Native Heap is ', Native_Heap_mem)
                        if render_time > 16.67:  # 大于16.67认为是一次卡顿
                            jank_num += 1  # 计算卡顿次数
                            vsync_overtime = math.ceil(render_time / 16.67) - 1  # 向上取整
                            vsync_overtime_s.append(vsync_overtime)  # 添加到列表
                else:
                    continue

            vsync_overtime_sum = sum(vsync_overtime_s)  # 计算列表中所有数据的和
            fps_sum = frame_count + vsync_overtime_sum
            if fps_sum == 0:
                fps = 0
                print("手机屏幕静止")
            else:
                fps = round(frame_count * 60 / fps_sum, 2)  # 计算fps,并保留2位小数
            current_time = self.get_current_time()
            self.save_data('fps', [(current_time, fps)])  # 将数据保存到Excel
            print("FPS:", fps)
            self.fps_data.append(fps)  # 将数据加入列表
            self.fps_plot_plt.plot().setData(self.fps_data, pen='m')  # 将数据载入图像中
        except Exception as e:
            print(traceback.print_exc())

    def save_data(self, data_type, cpudata):
        """保存数据到Excel"""
        if data_type == 'cpu':
            writer = csv.writer(self.cpucsvfile)  # 写入Excel
            writer.writerows(cpudata)  # 将数据写入Excel
        elif data_type == 'mem':
            writer = csv.writer(self.memcsvfile)
            writer.writerows(cpudata)
        elif data_type == 'fps':
            writer = csv.writer(self.fpscsvfile)
            writer.writerows(cpudata)
        else:
            print('data_type error!')
if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    data = MonitoringData('', '')  # 请修改包名和设备号
    data.show()
    sys.exit(app.exec_())

5.最终结果

使用说明

a. 连接android设备,通过adb devices获取设备号

b. 修改包名和设备号

c. UI自动化脚本运行起来后,直接运行monitoringdata.py脚本

小贴士

1.Android设备x,y坐标可打开系统设置-->开发者模式-->指针位置查看。

2.性能监控无法获取FPS时,则很可能是手机的“GPU呈现模式分析”未打开,在手机的开发者选项中,找到“GPU呈现模式分析”,选择“在adb shell dumpsys gfxinfo中”,如果是华为手机,则选择“在屏幕上显示为线型图”。

参考文献:

1.https://www.cnblogs.com/zeliangzhang/p/15268578.html

2.https://airtest.doc.io.netease.com/

3.https://developer.android.com/studio/test/monkeyrunner/MonkeyDevice

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值