您的位置:首页 > 编程语言 > Python开发

Python开发测试工具(一)—Monkey

2016-04-01 17:24 666 查看

文章首发:我的博客

背景

最近在测试安卓的时候,经常会用到Monkey,Monkey作为安卓的基础工具,必须要到命令行去敲敲敲,做起来非常非常麻烦,于是我就想能不能利用学会的Python知识直接开发一个带有界面的安卓测试工具。

思路

整个实现流程并不困难,一个界面填写数据,提交到后台,然后Python调用os模块,执行shell命令,然后就可以实现GUI端的Monkey了。

Web端

前段时间一直都在看web端的知识,我就想用web写一个界面,然后后台用Django执行,就可以很简单的完成这个工具了。

使用POST表单实现

前端就是用Bootstrap来实现了,对我来说,写了这么久得博客,这块东西比较容易了,代码就不放了,bootstrap套套就出来了,界面图如下:



本来上面还有一个结束Monkey按钮的,被我拿掉了,拿掉的原因后面会说。

现在该有的东西都有了,后台实现起来也不难,获取前端给的参数,然后执行命令就行了,第一版用的是POST提交表单的方式执行,我发现了这么些问题:

点击执行Monkey命令的时候整个页面是卡住的,没办法点击获取Monkey进程和结束Monkey

执行完Monkey后整个页面会刷新,也就是说没办法从前端获取我命令的参数,虽然日志中可以获取到命令相关的参数,但是总的来说并不是那么直观

使用Ajax提交表单

用POST提交是最简单的了,我也最熟悉,不过存在上面的问题,总是很操蛋,于是我就改为使用Ajax异步提交数据,这样就不会导致执行Monkey的时候整个页面卡死的情况了。

页面没有修改,改了一个方法,jQuery的方法如下:

$(document).ready(function () {
$("#sub").click(function () {
$.post("/monkey/",
{
"package_name": $("#package_name").val(),
"event_count": $("#event_count").val(),
"log_path": $("#log_path").val(),
"log_level": $("#log_level").val(),
"delay": $("#delay").val(),
"sys_event": $("#sys_event").val(),
"touch_event": $("#touch_event").val(),
"zoom_event": $("#zoom_event").val(),
"finger_event": $("#finger_event").val(),
"ball_event": $("#ball_event").val(),
"nav_event": $("#nav_event").val(),
"switch_event": $("#switch_event").val()
}
)
})
});


使用这个方法实现,是解决了一些问题,执行Monkey的时候页面也不会卡了,也可以正常的获取Monkey进程,不过依然存在这么些问题:

端口容易出现异常,我执行的过程中发现Monkey经常会占用两个pid

执行停止Monkey命令的时候无法生效

第一个问题倒是还好,占用两个结束两次就行了,倒也还能接受,第二个问题就无法接受了,NND不能直观的开始和结束,我写这个工具就没有什么意义了,我专门试了直接调用python执行停止命令是正常的。代码如下:

$(document).ready(function () {
$("#stop_monkey").click(function () {
$.post("/monkey/stop_monkey/",
{
"monkey_id": $("#monkey_id").val()
})
})
});


@csrf_exempt
def stop_monkey(request):
data = {
"msg": "Monkey已经停止"
}
if request.method == 'POST':
command = 'adb shell kill {}'.format(request.POST['monkey_id'])
cm.run_monkey(command)
return JsonResponse(data, safe=False)
return JsonResponse(data, safe=False)


若读者知道,请发邮件告诉我到底是为什么这里不会执行,我丢了print在这上面是可以正常打印的。

GUI端

web端的实验失败了,效果并没有达到我的预期,只能换一种方案了,用桌面的GUI端来实现这个方案试试,然而GUI端又有很多选择,PYQT据说是最好的,但是在我电脑数据抹掉之前我有装过这个,虽然最后装成功了,但是整个装的过程太操蛋了,有点心理阴影,wxpython也是一个不错的选择,不过还要装第三方包,有点折腾,最终我选择了最基本的Tkinter来处理GUI。那么问题来了,Tkinter没用过,要重新去熟悉这个包。

《Tkinter介绍文档》。。。麻蛋又是全英文的,果然英语还是必须要学好,我大概看了两小时,把一些主要的点扫了一遍,就开始写了,代码丑也是没办法的,时间少,没办法去慢慢想设计模式。

写完的界面是这样的:



按钮方法

整个GUI的代码有100多行,有点长,布局是用Grid写的,所有的代码我就不贴了,我把按钮的方法贴一下:

connect_text = Button(master, text='获取设备号',
command=lambda: cm.set_text(device_name, ad.get_devices()))
up_pknm_conf = Button(master, text='修改包名',
command=lambda: cm.update_conf(status, 'package_name', cm.get_text(package_name)))
up_log_path_conf = Button(master, text='修改日志地址',
command=lambda: cm.update_conf(status, 'log_path', cm.get_text(log_path)))
up_log_level_conf = Button(master, text='修改日志等级',
command=lambda: cm.update_conf(status, 'log_level', cm.get_text(log_level)))
up_count_conf = Button(master, text='修改测试数量',
command=lambda: cm.update_conf(status, 'count', cm.get_text(count)))
up_delay_conf = Button(master, text='修改延时',
command=lambda: cm.update_conf(status, 'delay', cm.get_text(delay)))
up_touch_conf = Button(master, text='修改触摸事件',
command=lambda: cm.update_conf(status, 'touch', cm.get_text(touch)))
up_motion_conf = Button(master, text='修改手势事件',
command=lambda: cm.update_conf(status, 'motion', cm.get_text(motion)))
up_pinch_conf = Button(master, text='修改缩放事件',
command=lambda: cm.update_conf(status, 'pinch', cm.get_text(pinch)))
up_trackball_conf = Button(master, text='修改轨迹球事件',
command=lambda: cm.update_conf(status, 'trackball', cm.get_text(trackball)))
up_screen_conf = Button(master, text='修改屏幕事件',
command=lambda: cm.update_conf(status, 'screen', cm.get_text(screen)))
up_nav_conf = Button(master, text='修改导航事件',
command=lambda: cm.update_conf(status, 'nav', cm.get_text(nav)))
up_major_conf = Button(master, text='修改主要事件',
command=lambda: cm.update_conf(status, 'major', cm.get_text(major)))
up_system_conf = Button(master, text='修改系统事件',
command=lambda: cm.update_conf(status, 'system', cm.get_text(system)))
up_app_conf = Button(master, text='修改切屏事件',
command=lambda: cm.update_conf(status, 'app', cm.get_text(app)))
up_keyboard_conf = Button(master, text='修改键盘事件',
command=lambda: cm.update_conf(status, 'keyboard', cm.get_text(keyboard)))
up_anyevents_conf = Button(master, text='修改其他事件',
command=lambda: cm.update_conf(status, 'anyevents', cm.get_text(anyevents)))
cat_monkey_pid = Button(master, text='显示Monkey进程',
command=lambda: cm.set_text(monkey_pid, ad.get_monkey_id()))
start_monkey = Button(master, text='开始Monkey',
command=lambda: mk.merge_command(cm.get_text(log_path), *cm.collect(*ENTRYLIST)))
stop_monkey = Button(master, text='结束Monkey',
command=lambda: ad.stop_monkey(status))
get_monkey = Button(master, text='获取Monkey',
command=lambda: cm.set_text(status, mk.get_monkey(cm.get_text(log_path), *cm.collect(*ENTRYLIST))))


值得一提的是,在Button组件中的command执行函数是不能带参数的,否则就会报错,不知道Tkinter的作者是怎么想的,不带参数的函数能有几个啊。。。。。

我用了一个折中的方法,用lambda函数来处理这一块,这样就可以加上参数了。

封装其他方法

其他方法我使用了三个函数,common来处理普通的方法,adb专门处理操作shell的方法,而monkey专门处理和Monkey有关的方法,这样设计以后工具就很容易拓展了,可以加上性能监控的方法等高级功能。

简单的贴一下方法。

class Adb:
def __init__(self):
self.mk = monkey.Monkey()
self.cm = common.Common()

def get_devices(self):
"""
获取设备名称
:return:设备名称
"""
a = os.popen('adb devices')
devices = a.readlines()
spl = devices[1].find(' ')
devices_name = devices[1][:spl]
if devices_name == '':
return "请确认设备是否连接"
else:
return devices_name

def get_monkey_id(self):
"""
获取monkey进程ID
:return:monkey进程id
"""
if self.get_devices():
a = os.popen('adb shell ps | grep monkey')
try:
monkey_id = a.read().split(' ')[5]
print "进程为{} 的Monkey已停止".format(monkey_id)
except Exception:
monkey_id = ''
return monkey_id
else:
print "设备未连接"

def stop_monkey(self, entry):
"""
停止monkey
:param monkey_id:monkey的进程号
:return:None
"""
monkey_id = self.get_monkey_id()
if monkey_id != '请确认你的设备是否连接':
os.system('adb shell kill {}'.format(monkey_id))
self.cm.set_text(entry, "进程为{} 的Monkey已停止".format(monkey_id))
else:
print "设备未连接"


def merge_command(self, path, *args):
"""
组合命令,Monkey使用
:param path:日志地址
:param args:Monkey命令中的其他参数
:return:None
"""
member = ' '.join(args)
command = 'adb shell monkey {} > {}'.format(member, path)
self.run(command)

def get_monkey(self, path, *args):
"""
获取Monkey命令
:param path: 日志地址
:param args: Monkey命令中的其他参数
:return:
"""
if self.check_total(*args):
member = ' '.join(args)
command = 'adb shell monkey {} > {}'.format(member, path)
return command
else:
return '事件百分数大于100%,请修正后再获取'

def check_total(self, *args):
"""
检查事件百分比是否合规,大于100则返回False
:param args:传入的事件列表
:return:True
"""
rst = []
all_list = self.deal_list(*args)
for x in all_list:
x = x.split(' ')[1]
rst.append(int(x))
num = sum(rst)
if num > 100:
return False
else:
return True

def deal_list(self, *args):
"""
处理列表数据,返回只有事件的列表
:param args:传入的列表数据
:return:list
"""
rst = []
for x in range(1, 12):
rst.append(args[x])
print rst
return rst


存在的问题

用GUI端来处理也会存在这么个问题,点击开始的时候回出现整个GUI卡住的情况,必须要等Monkey执行完毕才能正常操作其他东西,因此我想了一个折中的方案,加一个状态显示的文本框,加一个生成Monkey命令的按钮,这样生成命令然后在GUI上就可以使用其他命令了,加上多进程或者多线程应该能解决这个问题,但是现在我对多线程多进程几乎是0了解,等了解了之后再来完善这部分的功能。

最后

就实现了这么一点点功能,代码量差不多也要300行,不得不说真是一个操蛋的工程。

最后,我仍然没有解决这个操蛋的问题,等研究完多线程之后再来重新改善这段代码。

最后的最后,虽然功能还不齐全,还是放上Github,怎么说也是一个可以试想功能的工具吧。

最近博客上线了,读者可以关注我的博客。

博客地址:www.wengyb.com
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: