前言:
做测试开发有一段时间了,总会碰到各种各样的问题,特此记录下,已做备忘;
任务目标:
最近接手的工作是需要在平台上集成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;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("
","").replace(" "," ").split("
") #对获取到的数据进行过滤并按照
切割成列表
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'测试过程中出现程序无响应,具体内容为:
', i
Anr_info.append(i)
Acount += 1
elif re.match(str2, i):
print u'测试过程中出现程序崩溃,具体内容为:
', i
Crash_info.append(i)
Ccount += 1
elif re.match(str3, i):
print u'测试过程中出现程序异常,异体内容为:
', 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文件;