- 最近在钻研手机端的专项测试,总结了一些内容;
工具收集:
- appcrawler:类似monkey的自动化遍历app爬虫工具
github地址https://github.com/seveniruby/AppCrawler ;- 自动化探索性测试并获取加载时间和性能数据,需要借助一些工具
- 支持ios和Android
- 命令行执行
- 支持自定义断言判断app是否崩溃
- 支持阿里开源ui自动化框架macaca
- 新老版本对比
- leakcanary:Android的高效率发现大部分内存泄漏导致的OOM
- 开源的,基于java语言,github地址https://github.com/square/leakcanary
中文说明:https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/- 可在appcrawler集成
- https://testerhome.com/topics/6022(在Jenkins上的实战帖)
- 开源的,基于java语言,github地址https://github.com/square/leakcanary
- MLeaksFinder:ios版自动内存泄漏检测工具
- 开源的,基于object-c语言
github地址 https://github.com/Zepo/MLeaksFinder
中文说明:https://wereadteam.github.io/2016/07/20/MLeaksFinder2 - 可在appcrawler集成
- 开源的,基于object-c语言
- BlockCanaryEx:记录Android卡顿日志
- 开源的,基于BlockCanary项目
github地址 https://github.com/seiginonakama/BlockCanaryEx
中文说明:
https://github.com/seiginonakama/BlockCanaryEx/blob/master/README_ZH.md - 卡顿的时候侧重于关注app的代码,哪些方法耗时最多,重点记录和在ui显示出来
- gc采样,卡顿的时候关注gc是否出现问题
- 需要在app代码内注入
- 开源的,基于BlockCanary项目
- 腾讯gt:直接运行在手机,抓取性能参数
- 支持ios(需要嵌入工程)和Android(由app和sdk组成,GT控制台可以独立安装使用,SDK需嵌入被调测的应用、并利用GT控制台进行信息展示和参数修改)
- github地址:https://github.com/Tencent/GT
- adb shell命令
adb shell命令实战
- 获取包名和activity的方式:https://testerhome.com/topics/9209
在线正则表达式调试:https://regex101.com/
这里自定义包名和activity,在下方使用:
- packagename:com.chuanblog91.ui
- activity:com.chuanblog91.ui/mainActivity
- 启动app123def LaunchApp(self):cmd = 'adb shell am start -W -n com.chuanblog91.ui'content = os.popen(cmd)
- 停止app进程123def StopApp(self):cmd = 'adb shell am force-stop com.chuanblog91.ui'os.popen(cmd)
- 获取机器识别码123456789101112def GetDevices(self):cmd = 'adb devices'content = os.popen(cmd)devicesinfo = content.read()devicesregx = r"(\S{1,30})\sdevice"devicesmatches = re.finditer(devicesregx, devicesinfo)for devicesmatchNum, devicesmatch in enumerate(devicesmatches):devicesmatchNum = devicesmatchNum + 1self.devicesname = devicesmatch.group(len(devicesmatch.groups()))return self.devicesname
- 获取app的pid123456789101112def CheckAppPid(self):cmd = 'adb -s {} shell ps com.chuanblog91.ui'.format(self.devicesname)content = os.popen(cmd)pidinfo = content.read()pidregex = r"\S\d_\S\d{2}\s{4}(\d{1,6})"pidmatches = re.finditer(pidregex, pidinfo)for pidmatchNum, pidmatch in enumerate(pidmatches):pidmatchNum = pidmatchNum + 1self.pidnum = pidmatch.group(len(pidmatch.groups()))return self.pidnum
获取app的uid
12345678910111213def CheckAppUid(self):cmd = "adb -s {0} shell cat /proc/{1}/status".format(self.devicesname, self.pidnum)content = os.popen(cmd)uidinfo = content.read()uidregex = r"Uid:\s(\d{1,6})"uidmatches = re.finditer(uidregex, uidinfo)for uidmatchNum, uidmatch in enumerate(uidmatches):uidmatchNum = uidmatchNum + 1self.uidnum = uidmatch.group(len(uidmatch.groups()))return self.uidnum获取app所占用的内存量
123456789101112131415161718192021222324252627282930313233343536373839404142def CheckAppMeminfo(self):"""PSS – Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)USS – Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)如果没有root权限的Android手机可能获取不到uss;"""cmd = 'adb shell "procrank |grep {}"'.format(self.pidnum)content = os.popen(cmd)meminfo = content.read()# 获取pss内存值pssregex = r"(\d{1,9})\S\s{1,3}\S{1,9}\s{2}com.chuanblog91.ui"pssmatches = re.finditer(pssregex, meminfo)for pssmatchNum, pssmatch in enumerate(pssmatches):pssmatchNum = pssmatchNum + 1self.pssnum = pssmatch.group(len(pssmatch.groups()))self.logDict({"PssMem":self.pssnum})# 获取uss内存值ussregex = r"(\d{1,9})\S\s{2}com.chuanblog91.ui"ussmatches = re.finditer(ussregex, meminfo)for ussmatchNum, ussmatch in enumerate(ussmatches):ussmatchNum = ussmatchNum + 1self.ussnum = ussmatch.group(len(ussmatch.groups()))# 获得机器最大内存设置数cmd = 'adb -s {} shell cat /system/build.prop'.format(self.devicesname)content = os.popen(cmd)maxmeminfo = content.read()maxmemregex = r"dalvik\.vm\.heapsize=(?P<maxmem>\d+\S)"# get maxmam valuemaxmemmatches = re.finditer(maxmemregex, maxmeminfo)for maxmemmatchNum, maxmemmatch in enumerate(maxmemmatches):maxmemmatchNum = maxmemmatchNum + 1maxmemstr = maxmemmatch.group(len(maxmemmatch.groups()))maxmem = re.sub("\D", "", maxmemstr)return maxmem, self.pssnum, self.ussnum获取app占用的cpu量
1234567891011121314151617181920212223242526272829303132def CheckAppCpuinfo(self):cmd = 'adb -s {} shell dumpsys cpuinfo com.chuanblog91.ui'.format(self.devicesname)content = os.popen(cmd)cpuinfo = content.read()# 获得总cpu占用率totalcpuregex = r"(\d{1,2}|100|\d{1,2}\.\d)%\sTOTAL"totalcpumatches = re.finditer(totalcpuregex, cpuinfo)for totalcpumatchNum, totalcpumatch in enumerate(totalcpumatches):totalcpumatchNum = totalcpumatchNum + 1totalcpu = totalcpumatch.group(len(totalcpumatch.groups()))# 获得用户操作cpu占用率usercpuregex = r"TOTAL:\s(\d{1,2}|100|\d{1,2}\.\d)%"usercpumatches = re.finditer(usercpuregex, cpuinfo)for usercpumatchNum, usercpumatch in enumerate(usercpumatches):usercpumatchNum = usercpumatchNum + 1usercpu = usercpumatch.group(len(usercpumatch.groups()))# 获得系统内核cpu占用率kernelcpuregex = r"TOTAL:\s(\d{1,2}|100|\d{1,2}\.\d)%\s\S{4}\s\S\s(\d{1,2}|100|\d{1,2}\.\d)%"kernelcpumatches = re.finditer(kernelcpuregex, cpuinfo)for kernelcpumatchNum, kernelcpumatch in enumerate(kernelcpumatches):kernelcpumatchNum = kernelcpumatchNum + 1kernelcpu = kernelcpumatch.group(len(kernelcpumatch.groups()))return totalcpu, usercpu, kernelcpu获取上下行流量
12345678910111213def CheckNetFlow(self):# 获取上下行流量,需要获取两次取差值cmd = "adb -s adb -s {0} shell \"cat /proc/net/xt_qtaguid/stats|grep {1} |awk '{{rx_bytes+=$6}}END{{print rx_bytes}}'\"".format(self.devicesname, self.uidnum)content = os.popen(cmd)rxnetinfo = content.read().strip()cmd = "adb -s adb -s {0} shell \"cat /proc/net/xt_qtaguid/stats|grep {1} |awk '{{tx_bytes+=$8}}END{{print tx_bytes}}'\"".format(self.devicesname, self.uidnum)content = os.popen(cmd)txnetinfo = content.read().strip()self.logDict({"NetInfo":{"RxNetInfo":rxnetinfo,"TxNetInfo":txnetinfo}})return rxnetinfo, txnetinfoapp流畅度(gfxinfo)
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455def GfxInfo(self):# 流畅度'''打开手机gfx监控: 设置-开发者选项-GPU呈现模式分析-在adb shell dumpsys gfxinfo中当渲染时间大于16.67,按照垂直同步机制,该帧就已经渲染超时那么,如果它正好是16.67的整数倍,比如66.68,则它花费了4个垂直同步脉冲,减去本身需要一个,则超时3个如果它不是16.67的整数倍,比如67,那么它花费的垂直同步脉冲应向上取整,即5个,减去本身需要一个,即超时4个,可直接算向下取整最后的计算方法思路:执行一次命令,总共收集到了m帧(理想情况下m=128),但是这m帧里面有些帧渲染超过了16.67毫秒,算一次jank,一旦jank,需要用掉额外的垂直同步脉冲。其他的就算没有超过16.67,也按一个脉冲时间来算(理想情况下,一个脉冲就可以渲染完一帧)所以FPS的算法可以变为:m / (m + 额外的垂直同步脉冲) * 60'''cmd = "adb -s 127.0.0.1:21503 shell dumpsys \"gfxinfo com.chuanblog91.ui|awk '/Execute/,/hierarchy/{if(i>1)print x;x=$0;i++}'\""content = os.popen(cmd)def str2float(strf):# 保留原本位数的小数点def char2num(s):return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]def char2int(x, y):return 10 * x + ytstr = strf.split('.')hightre = reduce(char2int, map(char2num, tstr[0]))if len(tstr)>1:lowre = reduce(char2int, map(char2num, tstr[1]))*(0.1**len(tstr[1]))else:lowre = 0return hightre + lowregfList = []gfxinfo = content.readlines()jank_count = 0vsync_overtime = 0for gf in gfxinfo:if gf.strip() != '':gf = gf.strip().split('\t')render_time = str2float(gf[0]) + str2float(gf[1]) + str2float(gf[2])gfList.append(render_time)if render_time > 16.67:jank_count += 1if render_time % 16.67 == 0 :# 计算额外垂直脉冲次数vsync_overtime += int(render_time / 16.67) - 1else:vsync_overtime += int(render_time / 16.67)# print jank_countif len(gfList) != 0:gfx_count = len(gfList)else:gfx_count = 1fps = int(gfx_count * 60 / (gfx_count + vsync_overtime))self.logDict({"GfxInfo":{"gfx_count":str(gfx_count),"jank_count":str(jank_count),"fps":str(fps)}})return (gfx_count, jank_count, fps)