前言:
做测试开发有一段时间了,总会碰到各种各样的问题,特此记录下,已做备忘;
任务目标:
最近接手的工作是需要在平台上集成monkey命令,支持命令执行并对日志进行筛选,将包含exception、crash和anr的记录筛选出来并统计出现次数;
任务整理与分析:
1.实际概况:
平台部署在linux服务器上;用户使用自己的电脑(win)连接真机(安卓)执行monkey命令;
2.思路分析:
1)服务器需要收集执行monkey命令的主机信息(主机名,IP地址等);
2)通过收集到的主机信息,控制主机执行monkey命令;
3.技术选型:
1)通过redis上传主机信息;
2)jsonp跨域传递命令,flask本地运行,接收命令并使用subprocess的Popen模块执行命令,执行后将结果返回给服务器并展示;
项目实施:
1.收集主机信息:
前提:用户本机需准备好python环境并安装flask和redis模块;本机配置好adb环境;
执行:运行提供的salve.py文件,上传主机信息;
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import flask,redis,os,socket from flask import request from subprocess import * import shlex,time,datetime,re,json def check_salve(): try: import redis import flask except Exception as e: if 'redis' in e: quit('运行salve节点需要装redis模块') elif 'flask' in e: quit('运行salve节点需要装flask模块') def reg_salve(): #把salve节点注册到服务端的redis里面 myname = socket.getfqdn(socket.gethostname()) myaddr = socket.gethostbyname(myname) #获取本机ip r=redis.Redis(host='xxxx',port=6379)#这里写服务端redis的ip salve_key = 'salves' r.hset(salve_key,myname,myaddr) #set key,哈希类型,大key是 salves,小key是 salve节点的主机名,value是子节点ip check_salve() reg_salve() |
2.平台展示主机信息提供执行入口:
从redis读取主机信息并展示,提供执行入口,选择本机信息后点击后跳转到执行页面;
3.执行过程:
1)执行页面显示IP地址和执行按钮;
html模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | <form id="mobile_form" enctype="multipart/form-data"> <div style="margin-bottom: 20px;"> <span style="color:greenyellow;font-weight: bolder">您的IP地址是:</span> <span id="id_select_ip"> {{ ip }} </span> </div> <span class="btn btn-primary" id="sub_command">开始执行</span> </form> <div id="result_show" class="hide"> <div style="margin:20px 0;"> <h5>执行结果如下所示:</h5> </div> <table class="table table-bordered"> <thead> <tr> <th scope="col" style="text-align: center;width:680px;">执行命令</th> <th scope="col" style="text-align: center">Exception次数</th> <th scope="col" style="text-align: center">CRASH次数</th> <th scope="col" style="text-align: center">ANR次数</th> <th scope="col" style="text-align: center">执行结果</th> </tr> </thead> <tbody> <tr> <td id="command_td"></td> <td id="exception_td" style="text-align: center"></td> <td id="crash_td" style="text-align: center"></td> <td id="anr_td" style="text-align: center"></td> <td id="result_td" style="text-align: center"></td> </tr> <tr id="Except_tr" class="hide"> <td colspan="5"> <div id="except_span"> </div> </td> </tr> <tr id="crash_tr" class="hide"> <td colspan="5"> <div id="crash_span"> </div> </td> </tr> <tr id="anr_tr" class="hide"> <td colspan="5"> <div id="anr_span"> </div> </td> </tr> </tbody> </table> </div> <div class="mobile_cover hide" id="mobile_cover"> <div class="mobile_coverShow hide" id="mobile_coverShow"> <span class="notice_text">执行monkey测试中,请耐心等候......</span> </div> </div> |
2)点击执行按钮,通过jsonp将命令发送到本机;
JS代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | $("#sub_command").click(function () { $("#mobile_cover").removeClass("hide"); $("#mobile_coverShow").removeClass("hide"); var ipadrress = $("#id_select_ip").text(); ipadrress=ipadrress.replace(/ /g,''); //过滤空格 var hide_val = $("#result_show").attr("class"); if(hide_val != "hide"){ $("#result_show").addClass("hide"); }; $.ajax({ url:"http://"+ipadrress+":8989/", type:"get", async:false, dataType:"jsonp", jsonp:"callback", success:function (sout_dir) { $("#command_td").text(sout_dir['command']); $("#result_show").removeClass("hide"); $("#mobile_cover").addClass("hide"); $("#mobile_coverShow").addClass("hide"); var ecount = sout_dir['ERROR']['Exception_count']; if(ecount == 0){ $("#exception_td").text("0"); }else{ $("#exception_td").text(ecount); $("#exception_td").addClass("except_detail"); var except_infos = sout_dir['ERROR']['Exception_info']; if($("#except_span:has(div)").length){ $("#except_span>div").remove(); }; for(except_info in except_infos ){ $("#except_span").append("<div style='height:25px;color:red;'>" + except_infos[except_info] +"</div>"); }; }; var Ccount = sout_dir['CRASH']['Ccount']; if(Ccount==0){ $("#crash_td").text("0"); }else{ $("#crash_td").text(Ccount); $("#crash_td").addClass("crash_detail"); var crash_infos = sout_dir['CRASH']['Crash_info']; if($("#crash_span:has(div)").length){ $("#crash_span>div").remove(); }; for(crash_info in crash_infos ){ $("#crash_span").append("<div style='height:25px;color:red;'>"+crash_infos[crash_info]+"</div>"); }; }; var Acount = sout_dir['ANR']['Acount']; if(Acount==0){ $("#anr_td").text("0"); }else{ $("#anr_td").text(Acount); $("#anr_td").addClass("anr_detail"); var anr_infos = sout_dir['ANR']['Anr_info']; if($("#anr_span:has(div)").length){ $("#anr_span>div").remove(); }; for(anr_info in anr_infos ){ $("#anr_span").append("<div style='height:25px;color:red;'>"+anr_infos[anr_info]+"</div>"); }; }; if (sout_dir['result_info']){ $("#result_td").text(sout_dir['result_info']) }else{ $("#result_td").text("执行失败!"); } } }) }); $("#exception_td").click(function () { var hide_ex = $("#Except_tr").attr("class"); if($("#except_span:has(div)").length){ if(hide_ex == "hide"){ $("#Except_tr").removeClass("hide"); }else{ $("#Except_tr").addClass("hide"); } } }); $("#crash_td").click(function () { var hide_cr = $("#crash_td").attr("class"); if($("#crash_span:has(div)").length){ if(hide_cr == "hide"){ $("#crash_tr").removeClass("hide"); }else{ $("#crash_tr").addClass("hide"); } } }); $("#anr_td").click(function () { var hide_anr = $("#anr_tr").attr("class"); if($("#anr_span:has(div)").length){ if(hide_anr == "hide"){ $("#anr_tr").removeClass("hide"); }else{ $("#anr_tr").addClass("hide"); } } }); |
3)本机接收到命令后执行并过滤日志,返回数据;
salve.py完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | import flask,redis,os,socket from flask import request from subprocess import * import shlex,time,datetime,re,json def check_salve(): try: import redis import flask except Exception as e: if 'redis' in e: quit('运行salve节点需要装redis模块') elif 'flask' in e: quit('运行salve节点需要装flask模块') def reg_salve(): #把salve节点注册到服务端的redis里面 myname = socket.getfqdn(socket.gethostname()) myaddr = socket.gethostbyname(myname) #获取本机ip r=redis.Redis(host='10.103.27.223',port=6379)#这里写服务端redis的ip salve_key = 'salves' r.hset(salve_key,myname,myaddr) #set key,哈希类型,大key是 salves,小key是 salve节点的主机名,value是子节点ip check_salve() reg_salve() server = flask.Flask(__name__) @server.route('/') def run(): callback = request.values.get('callback') #判断真机连接状态 check_command = "adb devices" check_command = shlex.split(check_command) check_status = Popen(check_command,bufsize=0,shell=True,stdout=PIPE,stderr=PIPE) check_infos = check_status.stdout.read() check_infos_new = check_infos.strip().replace("\r","").replace("\t"," ").split("\n") #对获取到的数据进行过滤并按照\n切割成列表 command_old = "adb shell monkey -p com.laijin.simplefinance -s 500 --pct-touch 20 --pct-motion 20 --pct-trackball 20 --throttle 100 --ignore-crashes --ignore-timeouts --monitor-native-crashes -v -v 3000" command = shlex.split(command_old) # 将获取到的命令切割成列表 # 如果列表最后包含device字样,表示设备已连接成功 if "device" in check_infos_new[-1]: print(u"---------设备连接成功,开始执行命令~!--------------") res= Popen(command,bufsize=0,shell=True,stdout=PIPE,stderr=PIPE) filelines = res.stdout.readlines() # 准备日志分析 str1 = '.*ANR.*' str2 = '.*CRASH.*' str3 = '.*Exception.*' str4 = '.*finished.*' Acount, Ccount, Ecount = 0, 0, 0 Anr_info = [] Crash_info = [] Exception_info = [] result_info = "" # 如获取到的信息为空,说明端口被占用,需杀掉占用端口的进程 if len(filelines) == 0: query_command = "netstat -ano |findstr '5037'" query_command = shlex.split(query_command) query_info = Popen(query_command, bufsize=0, shell=True, stdout=PIPE, stderr=PIPE) query_infos = query_info.stdout.readlines() for q in query_infos: q_list = q.strip().split(" ") while '' in q_list: q_list.remove('') if q_list[-2] == "LISTENING": result_info = u"端口号被占用,请杀死进程号为%s的进程后重试!"%q_list[-1] Exception_info = "" Ecount = 0 Anr_info = "" Acount = 0 Crash_info = "" Ccount = 0 else: #执行monkey命令并返回 for i in filelines: i = i.strip() if re.match(str1, i): print u'测试过程中出现程序无响应,具体内容为:\n', i Anr_info.append(i) Acount += 1 elif re.match(str2, i): print u'测试过程中出现程序崩溃,具体内容为:\n', i Crash_info.append(i) Ccount += 1 elif re.match(str3, i): print u'测试过程中出现程序异常,异体内容为:\n', i Exception_info.append(i) Ecount += 1 # 如果存在任何异常则不用判断日志是否正常完成 if Acount or Ccount or Ecount == 0: for i in filelines: if re.match(str4, i): print u'测试过程中正常' result_info = u"测试过程中正常" else: print u"------------设备连接异常,请校验后重试!---------------" result_info = u"设备连接异常,请校验后重试!" Exception_info = "" Ecount=0 Anr_info = "" Acount=0 Crash_info="" Ccount=0 sout_dir = { 'ERROR':{ "Exception_info":Exception_info, "Exception_count":Ecount }, 'ANR':{ "Anr_info":Anr_info, "Acount":Acount }, 'CRASH':{ "Crash_info":Crash_info, "Ccount":Ccount }, 'result_info':result_info, 'command':command_old } return ''.join([ callback, '(', json.dumps(sout_dir), ');' ]) server.run(host='0.0.0.0',port=8989) |
4)平台接收到数据,通过html模板展示;
遗留问题:
1. 端口被占用时,应自动杀死占用端口的进程后重新执行进程;(目前发现占用端口的为360手机助手,无法单独杀死进程,暂放弃);
2.本地修改salve.py文件后,需手动杀死adb.exe进程,再重新运行salve.py文件;