背景
随着测试工具的丰富,可执行程序文件越来越多,对于测试的工具管理来说也不是很友好;而且有我们后期会利用PerfDogService搭建自动化平台,所以今天先来一个简易版的demo;
做完之后是这个样子:
执行完毕后会显示
选择存储会同步上传到云端
当前web前端表现比较丑 比较素颜,没有花花绿绿的CSS样式,样式打算后期优化,先把主要功能搞出来;
一.结构组成
python web框架: Tornado
前端组件库: element-ui,VUE
核心: PerfDogService
整体思路其实很简单,首先获取本地的机型信息显示到前端,前端发送指令到Server,Server开始选中相应的机型开始测试。
web框架选型理由: Tornado使非阻塞式的服务器,速度相当快。这得益于其非阻塞的方式和对epoll的运用。Tornado每秒可以处理数以千计的连接,对于实时Web服务来说Tornado确实是一个理想的Web框架;简单理解就是相比于其他python web框架, Tornado的性能非常好,可以承载很高的并发访问量,不过缺点就是Tornado走的是少而精的方向,适合定制化,缺少一些库,比如数据库支持什么的, 需要自己写或者找三方的;
前端: 其实你也可以用其他的,适合自己的就好
二.核心结构讲解
目录结构是这个样子。我是用的Pycharm建立的虚拟环境开发,所以会有一个虚拟环境venv文件夹;
Main.py WebServer的主服务.
demo.py 调用PerfDogService的Handler存放文件夹
Template index.html存放的文件夹
Static html调用的一些js或者css存放的文件夹
perfdog_pb2.py,perfdog_pb2_grpc.py PerfDogService运行所依赖的重要文件.
三.核心方法讲解
在index.html中主要有两个核心方法:
1.getdevicesinfo(),主要是用于当用户在打开网页的时候从服务器获取当前本地的连接测试手机;对应的是(r"/devices", get_devicesHandler)路由,主要会调用demowe.py的Get_deviceList()方法
2.save_test()方法主要用于将当前网页上的数据发送给服务器;对应 (r"/save_test/([^/]+)", get_test_data)路由,加了通配符是因为这里以后可以传一个当前使用用户的id用于存储是谁提出了测试请求;主要会调用demo.py的run_test_data();其实主要还是运行perfdog的测试程序,对应demo.py的run(DEVICES_UID=None,APP_NAME=None,TEST_TIMER=None, CASENAME=None,SAVEDATA_TYPE=None,IS_SAVE=None,OTHER_GETDATA=None)
四.完整代码
代码较烂,轻喷,主要是还没优化,初版功能,后面会有较大改动
Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="shortcut icon" href="/static/images/icon/logo.png" />
<title>{% block title %}{% end %}</title>
<!-- Tell the browser to be responsive to screen width -->
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<!-- Bootstrap 3.3.6 -->
<link rel="stylesheet" href="/static/element-ui/lib/theme-chalk/index.css">
<script src="/static/vue.min.js"></script>
<script src="/static/element-ui/lib/index.js"></script>
<script src="/static/jquery-3.3.1.min.js"></script>
<!-- Theme style -->
</head>
<body>
<div id="app" class="text">
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm" >
<el-form-item label="测试名称" prop="name">
<el-input v-model="ruleForm.name" ></el-input>
</el-form-item>
<el-form-item label="测试时间(s)" prop="test_time">
<el-input v-model="ruleForm.test_time"></el-input>
</el-form-item>
<el-form-item label="测试APP名字" prop="app_name">
<el-input v-model="form.app_name"></el-input>
</el-form-item>
<el-form-item label="是否保存到本地">
<el-switch v-model="form.save_flag"></el-switch>
</el-form-item>
<el-form-item label="选择设备">
<el-select v-model="form.devices" placeholder="请选择设备" style="width: 140px" size="small" alue-key="id" @change="currentSel">
<el-option
v-for="item in devices"
:key="item"
:label="item"
:value="item">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="额外捕获数据">
<el-checkbox-group v-model="form.type">
<el-checkbox label="CPU Core Usage" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="导出数据类型">
<el-radio-group v-model="form.resource">
<el-radio label="json"></el-radio>
<el-radio label="Xls"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item>
<el-button :disabled = "form.btnDisabled" type="primary" @click="save_test('ruleForm')">立即创建</el-button>
<el-button @click="close()">取消</el-button>
</el-form-item>
</el-form>
</div>
</body>
<script type="text/javascript">
var id = "{{id}}"
var app = new Vue({
el: '#app',
data() {
return {
devices: ["",""],
//多条件查询是否性别下拉框
form: { //变量存储集合
name: '',
devices:'',
app_name:'',
date1: '',
date2: '',
save_flag: false,
selected_device:'',
type: [],
resource: '',
btnDisabled:false,
},
ruleForm: {//如果有必要创建字典类型变量可以这样使用
id:id,
name: '',
test_time: '',
},
rules: {
name: [
{ required: true, message: '请输入本次测试名称', trigger: 'blur' },
{ min: 1, max:500 , message: '长度在 1 到 500 个字符', trigger: 'blur' }
],
}
}
},
methods: {
save_test() {
test_time=this.ruleForm.test_time
if(test_time.length<1){
alert("名称不能为空")
return
}
params={"test_name": this.ruleForm.name,"test_time": this.ruleForm.test_time, "is_save":this.form.save_flag,"other_data": this.form.type,"selected_device":this.selected_device,"save_type":this.form.resource,"app_name":this.form.app_name}
this.form.btnDisabled=true;
$.ajax({
method:"post",
url:"/save_test/"+id,
data: JSON.stringify(params),
dataType:"json",
}).done((res) => {
if(res["success"]==false){
alert(res["error"])
}
else{
alert("测试已完成")
}
this.form.btnDisabled=false;
})
},
getdevicesinfo(){
$.ajax({
method:"get",
url:"/devices",
dataType:"json",
}).done((res) => {
this.devices=res["devices"]
})
},
currentSel(selVal) {
this.selected_device = selVal
console.log("选择的name为:" + this.selected_device);
console.log(selVal);
},
close(){
//do someting
}
},
created() {
this.getdevicesinfo()
},
mounted() {
}
})
</script>
</html>
Main.py
import tornado.ioloop
import tornado.web
import os
from demo import *
import json
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
# self.write("Hello, world")
class get_devicesHandler(tornado.web.RequestHandler):
def get(self):
devices=Get_deviceList()
self.finish({
"devices":devices
})
class get_test_data(tornado.web.RequestHandler):
def post(self,id):
print("id: ",id)
data=self.request.body.decode()
data=json.loads(data)
test_name,test_timer,is_save,other_get_data,selected_device,app_name,save_type=data["test_name"],data["test_time"],data["is_save"],data["other_data"],data["selected_device"],data["app_name"],data["save_type"]
res=run_test_data(test_name=test_name,test_timer=test_timer,is_save=is_save,other_get_data=other_get_data,selected_device=selected_device,app_name=app_name,save_type=save_type)
self.finish({
"success":res
})
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
(r"/devices", get_devicesHandler),
(r"/save_test/([^/]+)", get_test_data),
],
static_path=os.path.join(os.path.dirname(__file__), "static"),
template_path=os.path.join(os.path.dirname(__file__), "templates"),
)
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
Demo.py
原汁原味代码,没有经过任何优化结构,适合初学者
# -*- coding: utf-8 -*-
import re
import subprocess
import time
import traceback
import os
import grpc
import perfdog_pb2_grpc
import perfdog_pb2
import threading
class Config():
PERFDOGSERVER_PATH=r'C:\Users\Administrator\Desktop\perfdog\PerfDogService.exe' #PerfDogService的路径
TOKEN='d9ba122735654db4a2417fe9e0cd74db981938ef5f8c415b1457ca60bb853813'#token
DEVICES_UID="813QEDTE228ZK" #需要测试的手机序列号
APP_NAME="天气" #需要测试的app名字
TEST_TIMER=66 #需要测试时长
LABEL="this is a label" #label内容
NOTE="this is a note" #note内容
CASENAME="天气测试数据" #用例描述 ,会覆盖相同用例名字的数据
SAVEDATA_BEGINTIME=1 #导出到本地数据的开始时间(秒)
SAVEDATA_ENDTIME=20 #导出到本地数据的结束时间(秒)
OUTPUT="F:\\perfdog_service_output\\" #导出文件所保存的目录,
SAVEDATA_TYPE=perfdog_pb2.EXPORT_TO_JSON #导出文件所保存的类型,
# 在代码里启动PerfDogService或手动启动PerfDogService
print("0.启动PerfDogService")
# 1.**************************填入PerfDogService的路径
perfDogService = subprocess.Popen(Config.PERFDOGSERVER_PATH)
# 等待PerfDogService启动完毕
time.sleep(5)
print("1.通过ip和端口连接到PerfDog Service")
options = [('grpc.max_receive_message_length', 100 * 1024 * 1024)]
channel = grpc.insecure_channel('127.0.0.1:23456', options=options)
print("2.新建一个stub,通过这个stub对象可以调用所有服务器提供的接口")
stub = perfdog_pb2_grpc.PerfDogServiceStub(channel)
def Get_deviceList():
str_init = ' '
all_info = os.popen('adb devices').readlines()
for i in range(len(all_info)):
str_init += all_info[i]
devices_name = re.findall('\n(.+?)\t', str_init, re.S)
print('所有设备名称:\n', devices_name)
return devices_name#[{},{}]
def get_AppList():
try:
global stub
devices = []
deviceEventIterator = stub.startDeviceMonitor(perfdog_pb2.Empty())
for deviceEvent in deviceEventIterator:
# 从DeviceEvent中获取到device对象,device对象会在后面的接口中用到
device = deviceEvent.device
print("当前devices: ", device, " **** ", deviceEvent)
devices.append(device.uid)
print("手机列表", list(deviceEventIterator))
print(devices)
# return list(deviceEventIterator)
except Exception as err:
print("cuowu ",err)
def run_test_data(test_name=None,test_timer=None,is_save=None,other_get_data=None,selected_device=None,app_name=None,save_type=None):
print("本次测试名称: ",test_name)
try:
run(DEVICES_UID=selected_device, APP_NAME=app_name, TEST_TIMER=test_timer, CASENAME=test_name,
SAVEDATA_TYPE=save_type, IS_SAVE=is_save, OTHER_GETDATA=other_get_data)
return True
except Exception as err:
print("DataError: ",err)
return False
# 第一次运行demo前需要通过pip安装grpcio(1.23.0)和protobuf(3.10.0)
def run(DEVICES_UID=None,APP_NAME=None,TEST_TIMER=None, CASENAME=None,SAVEDATA_TYPE=None,IS_SAVE=None,OTHER_GETDATA=None):
try:
# 在代码里启动PerfDogService或手动启动PerfDogService
print("0.启动PerfDogService")
# 1.**************************填入PerfDogService的路径
perfDogService = subprocess.Popen(Config.PERFDOGSERVER_PATH)
# 等待PerfDogService启动完毕
time.sleep(5)
print("1.通过ip和端口连接到PerfDog Service")
options = [('grpc.max_receive_message_length', 100 * 1024 * 1024)]
channel = grpc.insecure_channel('127.0.0.1:23456', options=options)
print("2.新建一个stub,通过这个stub对象可以调用所有服务器提供的接口")
stub = perfdog_pb2_grpc.PerfDogServiceStub(channel)
print("3.通过令牌登录,令牌可以在官网申请")
userInfo = stub.loginWithToken(perfdog_pb2.Token(token=Config.TOKEN))
print("UserInfo:\n", userInfo)
print("4.启动设备监听器监听设备,每当设备插入和移除时会收到一个DeviceEvent")
deviceEventIterator = stub.startDeviceMonitor(perfdog_pb2.Empty())
for deviceEvent in deviceEventIterator:
# 从DeviceEvent中获取到device对象,device对象会在后面的接口中用到
device = deviceEvent.device
print("当前devices: ",device," **** ",deviceEvent)
# time.sleep(20000)
if deviceEvent.eventType == perfdog_pb2.ADD and device.uid==DEVICES_UID:
print("设备[%s:%s]插入\n" % (device.uid, perfdog_pb2.DEVICE_CONTYPE.Name(device.conType)))
# 每台手机会返回两个conType不同的设备对象(USB的和WIFI的),如果是测有线,取其中的USB对象
if device.conType == perfdog_pb2.USB:
print("5.初始化设备[%s:%s]\n" % (device.uid, perfdog_pb2.DEVICE_CONTYPE.Name(device.conType)))
stub.initDevice(device)
print("6.获取app列表")
appList = stub.getAppList(device)
#
apps = appList.app
app_index = 0
for app in apps:
print('%s: %s->%s' % (app_index, app.label, app.packageName))
if app.label==APP_NAME:
app_select=app_index
break
else:
app_index += 1
app_select=None
if app_select is None:app_select = int(input("未安装输入APP,请选择要测试App: "))
app = apps[app_select]
print("7.获取设备的详细信息")
deviceInfo = stub.getDeviceInfo(device)
print("8.开启性能数据项")
stub.enablePerfDataType(
perfdog_pb2.EnablePerfDataTypeReq(device=device, type=perfdog_pb2.NETWORK_USAGE))
if "CPU Core Usage" in OTHER_GETDATA:
stub.enablePerfDataType(
perfdog_pb2.EnablePerfDataTypeReq(device=device, type=perfdog_pb2.NORMALIZED_CPU_CORE_USAGE))
print("9.开始收集[%s:%s]的性能数据\n" % (app.label, app.packageName))
print(stub.startTestApp(perfdog_pb2.StartTestAppReq(device=device, app=app)))
req = perfdog_pb2.OpenPerfDataStreamReq(device=device)
perfDataIterator = stub.openPerfDataStream(req)
def perf_data_process():
for perfData in perfDataIterator:
print("数据: ",perfData)
threading.Thread(target=perf_data_process).start()
# 开始采集一些数据
time.sleep(int(TEST_TIMER))
print("10.设置label")
stub.setLabel(perfdog_pb2.SetLabelReq(device=device, label="I am a label"))
time.sleep(3)
print("11.添加批注")
stub.addNote(perfdog_pb2.AddNoteReq(device=device, time=5000, note="I am a note"))
if IS_SAVE:
print("12.上传和导出所有数据")
SAVEDATA_TYPE = perfdog_pb2.EXPORT_TO_JSON if SAVEDATA_TYPE == "json" else perfdog_pb2.EXPORT_TO_EXCEL
saveResult = stub.saveData(perfdog_pb2.SaveDataReq(
device=device,
caseName=CASENAME, # web上case和excel的名字
uploadToServer=True, # 上传到perfdog服务器
exportToFile=True, # 保存到本地
outputDirectory=Config.OUTPUT,
dataExportFormat=SAVEDATA_TYPE
))
print("保存结果:\n", saveResult)
print("12.上传和导出第5秒到20秒的数据")
print("13.停止测试")
stub.stopTest(perfdog_pb2.StopTestReq(device=device))
print("over")
break
elif deviceEvent.eventType == perfdog_pb2.REMOVE:
print("设备[%s:%s]移除\n" % (device.uid, perfdog_pb2.DEVICE_CONTYPE.Name(device.conType)))
except Exception as e:
print("出现错误", e)
traceback.print_exc()
if __name__ == '__main__':
Get_deviceList()
#