一、实现原理
使用python模拟终端输入monkey命令,同时抓取ERROR级别及以上的logcat日志,最后通过数据处理判定是否测试通过(结果含有"Monkey finished"结果为pass、含有
“System appears to have crashed"结果为false)并筛选monkey日志中的crash和anr数量
二、环境
python + monkey
三、项目目录结构
四、配置文件
yaml文件存储测试数据:
data:
phone : Y2J5T17524006969
monkeyclickcount : 10 # Monkey点击次数
packages : 要测试的包名 # 要测试的包名
path_text : 'logs/text.log' # 运行日志保存地址
error: 'logs/error.log' # 错误日志保存地址
throttle : 500 # 事件的时延,单位是毫秒
send: 1000 100 # 用于指定伪随机数生成器的seed值
syskeys: 70 1000 # 调整“系统”按键事件的百分比(这些按键通常被保留,由系统使用,如Home、Back、Start Call、End Call及音量控制键)
appswitch: 10 1000 # 调整其它类型事件的百分比。它包罗了所有其它类型的事件,如:按键、其它不常用的设备按钮、等等
count : 3 # 暂时用不上,如果后期需要执行次数的话可以使用
touch : 80 # 设置操作事件的百分比 显示详细信息,随机执行80个事件,
五、data文件读取yaml文件内容:
import yaml,os
# path1 = os.path.abspath('../config/config.yaml')
# print (path1)
class OpenYaml:
def __init__(self,file_name=None):
if file_name:
self.file_name = file_name
else:
self.file_name = './config/config.yaml'
self.data = self.getdata()
def getdata(self):
# 读取yaml的值
with open(self.file_name,'r',encoding='utf-8') as f:
self.data = yaml.load(f,Loader=yaml.FullLoader)
return self.data
if __name__ == '__main__':
tt = OpenYaml()
print(tt.getdata()['data']['phone'])
六、logs:分别存储执行流日志和错误日志:
七、monkeyss 执行Monkey命令:
from data.raad_yaml import OpenYaml
import os
import gevent
def get_devices():
command_result = ("adb devices") # 执行adb命令用于判断设备是否连接正常
mydevice = os.popen(command_result) # 执行adb 命令
mystr = mydevice.read() # 获取命令后的内容
splits = mystr[25:41] # 获取设备号
mal = OpenYaml() # 读取yaml 文件
if splits in mal.getdata()['data']['phone']:
"""
phone:设备号 如果更换可在 yaml 文件中更改
判断设备是否连接成功,如果未连接或者连接成功设备号不正确不执行~
"""
print('设备连接正常,开始执行Monkey命令~')
MonkeyCmd = "adb shell monkey -p %s --pct-touch %s -v -v -v --ignore-crashes --ignore-timeouts %s - s %s --throttle %s 1>%s 2>%s" \
% (mal.getdata()['data']['packages'], # 测试的包名
mal.getdata()['data']['touch'], # 显示详细信息,随机执行80个事件
mal.getdata()['data']['monkeyclickcount'], # 点击次数
mal.getdata()['data']['send'], # 用于指定伪随机数生成器的seed值
mal.getdata()['data']['throttle'], # 事件的时延,单位是毫秒
mal.getdata()['data']['path_text'], # 运行日志保存路径
mal.getdata()['data']['error']) # 错误日志保存路径
os.popen(MonkeyCmd)
""" 如果不执行某些事件,再命令行中注释掉,注意删除对应的 %s 值 """
print('执行命令:', MonkeyCmd)
else:
print('设备链接失败,请检查设备连接后再试~/或设备号是否正确:', splits)
get_devices()
方法二:可以获取多个设备,指定多个设备运行
python三种方式自动获取多个安卓adb设备名
方法1:
使用os.popen方式
import os
def check_adb_devices():
'''
检查adb 设备,并返回设备sn list
:return: 设备sn list
'''
adb_list=[]
ret =os.popen('adb devices').readlines()
print('ret={}'.format(ret))
if len(ret) ==1:
print('未识别到adb 设备...')
return adb_list
else:
for n in ret:
if '\tdevice\n' in n:
adb=str(n).strip().split('\tdevice')[0].strip()
adb_list.append(str(adb))
print('adb设备数量={},adb_list={}'.format(len(adb_list), adb_list))
return adb_list
if __name__ == '__main__':
check_adb_devices()
执行结果:
ret=['List of devices attached\n', '1234a4f3\tdevice\n', 'mn4xwsbfrd\tdevice\n', '\n']
adb设备数量=2,adb_list=['1234a4f3', 'mn4xwsbfrd']
方法2:
使用正则表达式方式匹配
def check_adb_devices_re():
'''
用re正则方式获取adb 列表,并返回设备sn list
:return: 设备sn list
'''
# 获取所有的安卓设备SN号码
devices_list = []
str_list = os.popen('adb devices').readlines()
print(str_list)
count = 0
for i in str_list:
if '\tdevice' in i:
device_name = ''
device_name = re.sub('\tdevice', '', i).replace('\n', '').strip()
print("Device_{}_name={}".format(count, device_name))
devices_list.append(device_name)
count = count + 1
print("devices={},devices_list={}".format(count,devices_list))
return devices_list
执行结果:
['List of devices attached\n', '1234a4f3\tdevice\n', '\n']
Device_0_name=1234a4f3
devices=1,devices_list=['1234a4f3']
方法3:
实际使用中发现,方法1和方法2在直接运行python文件的时候没有问题,但是把py文件打包成exe程序后,就会出现无法获取到cmd命令的返回值,经过分析为:打包后程序的cmd调试窗口被禁用了,就会导致程序无法获取返回值。
解决方法:
将adb devices的返回结果写入到txt文件中,再逐行读取,并进行字符串匹配,检测实际的adb设备名;
代码实现
运行cmd指令函数
def run_cmd( cmd_str='', echo_print=1):
"""
执行cmd命令,不显示执行过程中弹出的黑框
备注:subprocess.run()函数会将本来打印到cmd上的内容打印到python执行界面上,所以避免了出现cmd弹出框的问题
:param cmd_str: 执行的cmd命令
:return:
"""
from subprocess import run
if echo_print == 1:
print('\n执行cmd指令="{}"'.format(cmd_str))
run(cmd_str, shell=True)
获取多个adb设备名的函数
def check_adb_devices_ini():
global devices_list
import os
import re
# 获取所有的安卓设备SN号码
devices_list = []
adb_devices_file = '.\\Config\\adb.ini'
if os.path.exists(adb_devices_file):
run_cmd('del /f /q ' + adb_devices_file)
print(run_cmd('adb devices >' + adb_devices_file))
f = open(adb_devices_file, "r")
str_list = f.readlines()
print(str_list)
if 'device' not in str(str_list[1]):
print('adb devices:{}。未识别到任何adb设备,请确认安卓设备已正确连接且USB调试已打开...'.format(str_list))
print("未识别到任何设备,请确认设备已正确连接上...")
return -1
count = 1
for i in str_list:
if '\tdevice' in i:
device_name = ''
device_name = re.sub('\tdevice', '', i).replace('\n', '').strip()
print("Device_{}_name={}".format(count, device_name))
devices_list.append(device_name)
count = count + 1
print("devices_list={}".format(devices_list))
return devices_list
调用方法
if __name__ == '__main__':
check_adb_devices_ini()
执行结果
执行cmd指令="del /f /q .\Config\adb.ini"
执行cmd指令="adb devices >.\Config\adb.ini"
None
['List of devices attached\n', '3123caad\tdevice\n', 'mn4xwsb3ed\tdevice\n', '\n']
Device_1_name=3123caad
Device_2_name=mn4xwsb3ed
devices_list=['3123caad', 'mn4xwsb3ed']
代码(根据自身需求更改第60行的monkey命令)
# coding:utf-8
import os, time
import sys, re
import random
import subprocess
device = ""
package = ""
number = 0
result_true = []
result_false = []
crash_flag = 0
anr_flag = 0
# 获取设备名称
def devices():
global device, flag
cmd1 = "adb devices > content/devices.csv"
d = os.system(cmd1)
with open("content/devices.csv", encoding="utf-8", mode="r") as f: # 筛选出进程包名和activity
lines = f.readlines()
for line in lines:
if "device" in line:
device = line.split(' ')[0]
# 获取包名
def get_package():
global package
cmd = 'adb shell dumpsys window | grep mCurrentFocus > content/info.csv' # info.csv文件中是当前activity概括信息
d = os.system(cmd)
with open("content/info.csv", encoding="utf-8", mode="r") as f: # 筛选出进程包名和activity
lines = f.readlines()
for line in lines:
if "mCurrentFocus" in line:
if "null" in line:
continue
value1 = line.split('{')[1]
if 'mode' in value1:
value2 = value1.split(' ')[4]
else:
value2 = value1.split(' ')[2]
if "\n" in value2:
value = value2.strip("}\n")
package = value.split('/')[0]
else:
value = value2.strip("}")
package = value.split('/')[0]
# print(value)
else: # 未获取到包名信息则停止脚本
print("未获取到package信息!脚本终止执行!")
# 开始执行monkey并获取log
def start_monkey():
global device, package, number
number = random.randint(1, 500)
cmd1 = f"adb -s {device} shell monkey -p {package} -v -v -v --throttle 10 -s {number} --ignore-crashes --ignore-timeouts --ignore-native-crashes --pct-syskeys 0 --pct-anyevent 0 800000 > log/monkeylog.txt"
cmd2 = f"adb -s {device} logcat -v time *:E > log/logcat.txt"
# print(f"包名:{package}")
subprocess.Popen(cmd2, shell=True)
time.sleep(1)
subprocess.Popen(cmd1, shell=True)
time.sleep(1)
# 判断monkey是否已经执行结束
def is_finish():
global result_true, result_false
with open('log/monkeylog.txt', encoding='utf-8', mode='r') as f:
lines = f.read()
pattern1 = re.compile(f'Monkey finished', re.IGNORECASE)
pattern2 = re.compile(f'System appears to have crashed', re.IGNORECASE)
result_true = pattern1.findall(lines)
result_false = pattern2.findall(lines)
# print(f"result_ture:{result_true}\nresult_false:{result_false}")
# 筛选出monkey日志中crash和anr的数量
def select():
global crash_flag,anr_flag
with open('log/monkeylog.txt', encoding='utf-8', mode='r') as f:
lines = f.readlines()
for line in lines:
if "crash" in line.lower():
crash_flag += 1
if "anr" in line:
anr_flag += 1
if __name__ == '__main__':
devices()
get_package()
start_monkey()
while True:
is_finish()
if "Monkey finished" in result_true: #结果含有"Monkey finished"结果为pass
print(f"本次稳定性测试种子值为:{number}")
select()
print(f"monkey日志中存在{crash_flag}处crash")
print(f"monkey日志中存在{anr_flag}处anr")
# cmd = "adb shell && exit"
# os.system(cmd)
exit("pass")
elif "System appears to have crashed" in result_false: #System appears to have crashed"结果为false
print(f"本次稳定性测试种子值为:{number}")
exit("false")
else:
time.sleep(1)
八、创建bugreport.txt报告,并生成html文件
执行完成monkey,生成txt日志后,
创建bugreport.txt报告,并生成html文件
bugreport参考文档:bugreport获取及chkbugreport工具分析_幸福的达哥的博客-CSDN博客
完成
九、start.bat 文件再Dos中运行,或者结合jenkins
python monkeyss.py
十、杀死后台测试程序APP
def killAPP(config):
"""
杀死后台测试程序APP。
:return:Null
"""
killApp = "adb -s %s shell am force-stop %s" % (config.get("phone"), config.get("packageName"))
os.popen(killApp)
print("******已清理后台测试程序APP。")