匿名吐槽 Monkey 图形化界面工具

匿名 · 2018年05月26日 · 最后由 匿名 回复于 2019年01月09日 · 2776 次阅读

一、背景

最开始工作是在一家公司做手机测试,什么的都不懂,adb也不知道,反正只要点点点就行了,然后跑monkey也是用的monkey图形化界面工具。当时觉得写这工具的人好厉害啊。
然后在另外一家公司做手机测试,发现也有这种monkey图形化界面工具。
后来进了新公司,闲来无事也写一个玩玩。
工具没有给公司的同事使用,个人认为不利于成长。

二、实现

环境:win7 + python:3.4
框架:tkinter
打包工具:pyinstaller(把py文件打包成exe可执行文件)

三、效果图


四、代码

没有使用MVC模式,逻辑代码和页面没有分离,没有进行参数化配置修改。再加上一些bug😂

# coding:utf-8
from tkinter import *
from datetime import datetime
import subprocess
import time
import threading
import os


class MonkeyTest:
def __init__(self):
self.root = Tk()
self.root.title('0easy')
self.line = 1 # 用于统计monkey日志的行数
self.flagWhile = 1 # 循环判断标志位

def createLogin(self):
self.frameLogin = Frame()
self.frameLogin.pack()
rowTitle, rowUsername, rowPassword, rowLogin, rowTip = 1, 2, 3, 4, 5
padxTitle, padyTitle = 150, 20
padxLabel, padyLabel = 10, 10
padxEntry = 20
# 标题
labelTitle = Label(self.frameLogin, text='Monkey测试工具', font=('微软雅黑', 20))
labelTitle.grid(row=rowTitle, column=0, columnspan=2, padx=padxTitle, pady=padyTitle)
# 账号
labelUsername = Label(self.frameLogin, text='账号:')
labelUsername.grid(row=rowUsername, column=0, sticky=E, padx=padxLabel, pady=padyLabel)
self.entryUsername = Entry(self.frameLogin)
self.entryUsername.grid(row=rowUsername, column=1, sticky=W, padx=padxEntry)
# 密码
labelPassword = Label(self.frameLogin, text='密码:')
labelPassword.grid(row=rowPassword, column=0, sticky=E, padx=padxLabel, pady=padyLabel)
self.entryPassword = Entry(self.frameLogin, show='*')
self.entryPassword.grid(row=rowPassword, column=1, sticky=W, padx=padxEntry)
# 登录
btnLogin = Button(self.frameLogin, text=' 登录 ', command=self.login)
btnLogin.grid(row=rowLogin, column=0, sticky=E, padx=10)
# 退出
btnQuit = Button(self.frameLogin, text=' 退出 ', command=self.loginOut)
btnQuit.grid(row=rowLogin, column=1, padx=10)
# 提示语
self.labelReminder = Label(self.frameLogin, text='')
self.labelReminder.grid(row=rowTip, column=0, columnspan=2, sticky=W + E)
self.root.mainloop()

def login(self):
username = self.entryUsername.get().strip()
password = self.entryPassword.get().strip()
if username == 'monkey' and password == '123456':
self.labelReminder['text'] = '登录成功'
self.frameLogin.destroy() # 销毁登录页面
self.createRun() # 创建monkey运行界面
else:
self.entryPassword.delete(0, END) # 清空密码框
self.labelReminder['text'] = '账号或者密码错误'
self.labelReminder['fg'] = 'red'

def loginOut(self):
self.root.destroy()

def createRun(self):
self.frame = Frame()
self.frame.pack()
rowTitle, rowDevice, rowEvent, rowSeed, rowIsCrash = 1, 2, 3, 4, 5
rowIsANR, rowPackage, rowRemind, rowExecute, rowInfo = 6, 7, 8, 9, 10
padxTitle, padyTitle = 10, 20
padxLabel, padyLabel = 10, 10
padxEntry, padyEntry = 20, 20
padxButton, padyButton = 10, 20
padxListbox, padyListbox = 10, 10
# keywords = ['// CRASH:', 'ANR in ']
# 标题
labelTitle = Label(self.frame, text='Monkey测试工具', font=('微软雅黑', 20))
labelTitle.grid(row=rowTitle, columnspan=2, padx=padxTitle, pady=padyTitle)
# 设备ID
btnDevice = Button(self.frame, text='获取设备ID', command=self.getDeivceID)
btnDevice.grid(row=rowDevice, column=0, sticky=E, padx=padxLabel, pady=padyLabel)
self.entryDevice = Entry(self.frame)
self.entryDevice.grid(row=rowDevice, column=1, sticky=W, padx=padxEntry)
# 事件次数
labelEvent = Label(self.frame, text='事件次数:')
labelEvent.grid(row=rowEvent, column=0, sticky=E, padx=padxLabel, pady=padyLabel)
self.entryEvent = Entry(self.frame)
self.entryEvent.grid(row=rowEvent, column=1, sticky=W, padx=padxEntry)
# seed
labelSeed = Label(self.frame, text='seed值:')
labelSeed.grid(row=rowSeed, column=0, sticky=E, padx=padxLabel, pady=padyLabel)
self.entrySeed = Entry(self.frame)
self.entrySeed.grid(row=rowSeed, column=1, sticky=W, padx=padxEntry)
# crash
labelCrash = Label(self.frame, text='出现crash是否继续:')
labelCrash.grid(row=rowIsCrash, column=0, sticky=E, padx=padxLabel, pady=padyLabel)
self.vCrash = IntVar()
btnCrash = Checkbutton(self.frame, variable=self.vCrash)
btnCrash.select() # 默认勾选
btnCrash.grid(row=rowIsCrash, column=1, sticky=W, padx=padxButton)
# ANR
labelANR = Label(self.frame, text='出现ANR是否继续:')
labelANR.grid(row=rowIsANR, column=0, sticky=E, padx=padxLabel, pady=padyLabel)
self.vANR = IntVar()
btnANR = Checkbutton(self.frame, variable=self.vANR)
btnANR.select() # 默认勾选
btnANR.grid(row=rowIsANR, column=1, sticky=W, padx=padxButton)
# 包名
labelPkg = Label(self.frame, text='请输入包名:')
labelPkg.grid(row=rowPackage, column=0, sticky=E, padx=padxLabel, pady=padyLabel)
self.entryPkg = Entry(self.frame)
self.entryPkg.grid(row=rowPackage, column=1, sticky=W, padx=padxEntry)
self.entryPkg.insert(0, 'com.android.settings') # 默认settings
# 提示信息
self.labelRemind = Label(self.frame, fg='red', font=('微软雅黑'))
self.labelRemind.grid(row=rowRemind, column=0, columnspan=2, sticky=E + W, padx=padxLabel, pady=padyLabel)
# 执行
self.btnExecute = Button(self.frame, text=' 执行 ', command=self.execute)
self.btnExecute.grid(row=rowExecute, column=0, sticky=E, padx=padxButton)
# 停止
btnStop = Button(self.frame, text=' 停止 ', command=self.stop)
btnStop.grid(row=rowExecute, column=1, sticky=W, padx=padxButton)
# 展示信息
self.listInfo = Listbox(self.frame, width=70, height=10)
self.listInfo.grid(row=rowInfo, column=0, columnspan=2, padx=padxListbox, pady=padyListbox)
self.root.mainloop()

def getDeivceID(self):
'''
获取设备id
:return:
'''
cmd = 'adb devices >c:/devices.log'
# cmd2 = 'adb devices'
# proc = subprocess.Popen(cmd2, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
# result = proc.communicate()[0].split(b'\n')[1].split(b'\t')[0]
os.system(cmd)
with open('c:/devices.log','r') as f:
result = f.readlines()[1].split('\t')[0]
self.entryDevice.delete(0, END) # 清空设备id编辑框
self.entryDevice.insert(0, result) # 填写设备id

def execute(self):
# 设备id不为空,运行脚本时其实没有限定设备id,此项主要用来检测是否能运行adb
if len(self.entryDevice.get().strip()) == 0:
self.labelRemind['text'] = '请输入设备ID'
return
# 事件次数为数字
self.event = self.entryEvent.get().strip()
if not self.event.isdigit():
self.labelRemind['text'] = '请输入正确的次数'
return
# seed值为数字
self.seed = self.entrySeed.get().strip()
if not self.seed.isdigit():
self.labelRemind['text'] = '请输入正确的seed'
return
# 是否忽略crash
self.crash = '--ignore-crashes'
if self.vCrash.get() == 0:
self.crash = ''
# 是否忽略ANR
self.anr = '--ignore-timeouts'
if self.vANR.get() == 0:
self.anr = ''
# 包名不为空
self.pkg = self.entryPkg.get().strip()
if len(self.pkg) == 0:
self.labelRemind['text'] = '需要填写测试的包名'
return
# 按钮置灰
self.btnExecute['state'] = 'disabled'
lTime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.listInfo.insert(END, lTime + ' Monkey测试开始。。。')
# 重置提示语
self.labelRemind['text'] = ''
# 重置行数
self.line = 1
# 重置循环标志位
self.flagWhile = 1
# 运行monkey语句
t1 = threading.Thread(target=self.executeCmd)
t1.start()
# 显示monkey日志
t2 = threading.Thread(target=self.startReadLog)
t2.start()

def executeCmd(self):
cmd = 'adb shell monkey -p ' + self.pkg + ' ' + self.crash + ' ' + self.anr + ' --monitor-native-crashes --throttle 1000 -s ' + self.seed + ' -v -v -v ' + self.event + ' >c:/monkey.log'
os.system(cmd)

def startReadLog(self):
'''
5秒读一次monkeylog
第一次等5秒是让adb命令先运行,防止先读log而又没有monkey.log文件,导致异常
:return:
'''
while self.flagWhile > 0: # 标志位初始值大于0,进入循环
time.sleep(5)
self.readLog()

def stop(self):
self.btnExecute['state'] = 'active' # 激活执行按钮
self.flagWhile = -1 # 标志位小于0,跳出循环

def readLog(self):
count = 1
f = open('c:/monkey.log', 'r', encoding='utf8')
for i in f.readlines():
count = count + 1 # 记录每次读取log的行数,每读一行,计数+1
# count > line 防止重复insret
if count > self.line and ('// CRASH:' in i or 'ANR in ' in i):
lTime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.listInfo.insert(END, lTime + ' ' + i)
# 存在'// Monkey finished',就判断monkey结束
elif '// Monkey finished' in i:
lTime = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.listInfo.insert(END, lTime + ' Monkey测试结束!')
self.stop()
# print(self.line, count)
self.line = count


m = MonkeyTest()
m.createLogin()

五、打包

1.先安装一个pywin32.exe文件

下载地址:https://sourceforge.net/projects/pywin32/files/pywin32/Build%20220/
自己下载对应的exe文件版本

2.在cmd命令窗口输入:pip install pyinstaller

安装成功后,会在对应的python/script目录下生成一个pyinstaller.exe文件

3.重新启动cmd命令窗口输入:pyinstaller -F c:/monkey.py


--noconsole 去掉 dos窗口
-i c:/logo.ico 加上logo

六、使用

1.首先配置好adb环境,打开工具,登录账号:monkey,密码:123456,登录,代码的login()方法可以修改账号密码
2.获取设备ID,输入对应的信息,然后点击【运行】
3.日志保存在 c:/monkey.log

七、问题

1.为什么我打包的文件运行一直有dos黑框?
打包命令后面加上:--noconsole或者百度其他方法。
尽管用了--noconsole,在点击【获取设备ID】和【执行】的时候还是弹dos窗口。
目前还没找到解决办法,有解决的办法的同学请在下面留言告诉我,感谢。

2.为什么执行adb命令用os.popen或者os.system,不用subprocess.popen?
打包时去掉了dos窗口,用subprocess.poppen执行无效。

3.为什么匿名?
使用社区的时间比较少,有时候遇到一些问题,不会及时回复,会显得比较高冷。匿名了,你就不会知道是谁这么高冷了。

共收到 12 条回复 时间 点赞

工具没有给公司的同事使用,个人认为不利于成长。

就几个命令,和成长没啥关系

-w指令:直接发布的exe应用带命令行调试窗口,在指令内加入-w命令可以屏蔽
-F指令:注意指令区分大小写。这里是大写。使用-F指令可以把应用打包成一个独立的exe文件,否则是一个带各种dll和依赖文件的文件夹
--icon=

我一般是用-w和--icon

仅楼主可见

14年初也玩过,用WX设计了monkey的UI界面和配合监控执行的图形界面,可之后被自己否了,不实用,用途太窄没解决问题。后来主攻专项方案设计,执行者少量diy配置部分,后来维护到jenkins job,手动配置触发。
其实很多工具在如何设计执行方案解决实际测试目标需求,但并不是多数人懂得怎么设计配参及如何配合监控。

当年做的UI,当初只想着把可DIY部分摆全

默认匿名,其实没必要,跟上贴,不匿名。

-w我用过,我感觉和--noconsole是一样的效果,使用-w的时候,subprocess.poppen拿不到返回值。

匿名 回复

大神,14年就会写了。


这个是我17年写的,也感觉没啥用😂

看到过很多这种,公司也有monkey测试,公司做的monkey测试是在云平台,创建任务自动执行,然后解析log,发送报告~ 界面 的话,说没用也没用,哪些参数什么的基本也不会去怎么改;说有用也稍微有点用,可以给测试人员用 ~

需要什么操作自己百度一下参数就完事。这点都做不到的也谈不上成长了。想玩玩练手的话可以试试,构建成功后自动集群跑monkey,统计性能数据,结束后分析log信息,发报告这种。


这是我最近做的,要是早点看到你们的内容就好了

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册