【python开发】迈出第一步,这可能是我唯一一次的Python开发了
好久没写博了,今天就瞎唠唠吧
背景:
组内有个测试平台,是基于Python2+tornado 框架写的,之前自己维护了一套系统的UIweb自动化代码,现在需要集成进去。这很可能是自己唯一一次基于python开发了,后面组内要换java。
难点:
其实开发内容很简单,但是由于之前只写接口、UI这样的自动化,开发没实操过,难点来源于没实操过,
比如:ajax交互、js、轮询等等。反正不懂的就搜,不能搜怎么写代码 /认真脸
https://cn.bing.com/
http://google.com/
翻不了墙的就用bing吧,也不错。
设计尝试
由于一直没有解决掉如果在vue实现的前端页面下,通过属性赋值这样的操作去上传图片(随着学习vue,希望可以找到解决方法)。
故我一直都是采用的autoit这个window程序去实现的上传图片操作,其实也蛮稳定的。
只不过受制于这个程序,我不能将我的UI自动化脚本在Linux上执行,也不能实现无ui的执行。所以呢,综上所述,我只能把我的脚本放在一台执行脚本专用的window机器上执行。
这个前提条件确定下来了,我就得想怎么实现了。
为了更好让集成到测试平台里,让我的脚本更具灵活性,我想到了使用selenium-Grid的方式,由linux去执行脚本,然后调用远程的window机器去执行脚本,看起来不错哦。好的,学习grid,
调试grid,一切顺利。直到最后一步需要见证奇迹的时刻,果然。。。
失败了,为什么呢?因为当执行到调用autoit的.exe程序时候,是从脚本执行的机器上去找的。wtf, 算了 ,工期将至,先实现一版用起来吧(流下了没有技术的泪水o(╥﹏╥)o)。
设计实现方案确定
这时候我发现了python-jenkins这个第三方库,提供了丰富的API操作Jenkins的方法。
所以我的方案hi:在测试机器上本地启动一个jenkins服务,然后通过python-jenkins去调用jenkins提供的API去运行测试脚本,再jenkin集成allure生成一个漂亮的报告,行得通哦。
需求功能确定
需求确定:
可以在 case模块跟mark标记里去选择要执行的测试用例
case模块就不用说了,就是这个
mark标记,就是pytest框架里的mark标记功能,可以给case上打上你定义的mark,然后执行时候可以指定mark去执行被标记的case,场景比如:你标记出冒烟需要的case,打上smoke
然后执行测试:pytest -m smoke 就可以啦
可以查看测试机器的状态
因为就一台机器,所以一个人在执行测试的时候,其他人再点击执行就不行啦,只要机器不是空闲的,就把执行测试的按钮置灰。然后列表页展示相关信息,可以查看测试报告
这个就是通过API去获取任务执行情况了,然后把allure报告地址也给返回到前端
功能实现
终于到撸代码的时候了,因为这个平台是用Python的开发框架 tornado+bootstrap实现的,现学现卖吧,咱也不敢问。
- 先写个html页面出来
<div class="page-content">
<h3>测试平台</h3>
<br>
<form method="post" action="/exec_test">
<div>
<p>选择模块进行测试:
<select id="sel_case_module" name="case_module" style="border-left-width: 1px;margin-left: 5px;width: 360px;">
<option value="" selected="selected">请选择要执行测试的case模块</option>
<option value="all_case">全部case</option>
</select>
</p>
</div>
<div>
<p>选择mark进行测试:
<select id="sel_case_mark" name="case_mark" style="width: 360px;">
<option value="" selected="selected">请选择要执行测试的mark</option>
</select>
</p>
</div>
</form>
<button type="button" id="exec_test" class="btn btn-sm btn-light" onclick="execute_test()" disabled="true">
<a href="javascript:void(0)">执行测试</a>
</button>
<div>
<p style="padding-top: 10px;">测试机器:</p>
<div id="running_build_status" name="running_build_status" style=""></div>
</div>
<div class="popover-title" >
<strong>
<span>测试结果:<span id="test_result"></span></span>
</strong>
</div>
<table class="table table-bordered table-hover table-condensed table-striped" style="font-size: 12px;min-width: 1200px;margin-bottom: 0">
<tr>
<th style="word-break: keep-all;min-width: 60px">当前构建任务名称</th>
<th style="word-break: keep-all;min-width: 60px">当前构建任务号</th>
<th style="word-break: keep-all;min-width: 130px;">当前构建任务状态</th>
<th style="word-break: keep-all">当前构建任务时长(s)</th>
<th style="word-break: keep-all">当前构建开始时间</th>
<th style="word-break: keep-all;min-width: 130px;">测试报告地址</th>
</tr>
<td id="job_name"></td>
<td id="job_number"></td>
<td id="job_status"></td>
<td id="job_duration"></td>
<td id="start_time"></td>
<td id="test_report">
<a href="" target="_blank"></a>
</td>
<tbody id="test_info">
</tbody>
</table>
</div>
</div>
页面长啥样呢,咳咳,美观咱就算了,就是个能用就行
- 那怎么样让后端返回我写的这个html页面呢?
很简单,在服务端路由分发里加上对应的url
# 路由分发
urls = [
(r'/ui_ad_mis', AdMisHandler)
]
然后去对应的AdMisHandler去写对应的服务就好了
class AdMisHandler(BaseHandler):
def get(self):
'''
返回UI测试页面
'''
self.render("html/ui_ad_mis.html")
- 那前端页面的下拉菜单列表怎么获取我在jenkin配置的参数化构建里配置的参数呢?
很简单,后端通过API获取到配置的参数,然后返回给前端就好
先看后端
class QueryCase(BaseHandler):
def get(self):
'''
查询case模块
:return: case_module 用例模块
case_mark mark标记
'''
server = self.jenkins_server()
case_module = server.get_job_info("exec_group_by_module")["actions"][0]["parameterDefinitions"][0]["choices"]
case_mark = server.get_job_info("exec_group_by_mark")["actions"][0]["parameterDefinitions"][0]["choices"]
self.finish({"case_module": case_module, "case_mark": case_mark})
别忘记去配置url,然后通过这个服务,我们返回case_module,和case_mark,下面就是前端去接收了。我是希望在点击进入页面的时候,这个列表就可以获取到,所以
在点击左侧菜单栏的时候去发起这个请求,query_case()
<ul class="submenu" style="overflow: hidden; display: none;">
<li id="ad_mis">
<a href="#" onclick="showAtRight('ui_ad_mis');query_case()">
<i class="icon-double-angle-right"></i>广告后台</a>
</li>
</ul>
- 页面上写好了,那么怎么去发起请求呢? 这里通过js,ajax
//查询选择框下拉
function query_case(){
$.ajax({
type: "Get",
async: true,
dataType: "json",
url: "/query_case",
success:function(data){
for (var i=0;i<data["case_module"].length;i++){
document.getElementById("sel_case_module").options
.add(new Option(data["case_module"][i],data["case_module"][i]));
}
for (var i=0;i<data["case_mark"].length;i++){
document.getElementById("sel_case_mark").options
.add(new Option(data["case_mark"][i],data["case_mark"][i]));
}
},
error:function(data){
console.log("请求失败");
}
})
}
上面在js文件里定义qurey_case()这个函数。
type: "Get",是get方法;
dataType: "json",数据类型是json;
url: "/query_case", 这里就会指向你后端url配置里的路径了;
success:function(data),当请求成功,data就是后端返回的数据了,然后通过for循环去遍历展示到下拉框里
<div>
<p>选择模块进行测试:
<select id="sel_case_module" name="case_module" style="border-left-width: 1px;margin-left: 5px;width: 360px;">
<option value="" selected="selected">请选择要执行测试的case模块</option>
<option value="all_case">全部case</option>
</select>
</p>
</div>
<div>
<p>选择mark进行测试:
<select id="sel_case_mark" name="case_mark" style="width: 360px;">
<option value="" selected="selected">请选择要执行测试的mark</option>
</select>
</p>
</div>
看下实现的效果
OK,那么我选中了一个下拉后,点击执行,就要去执行测试了。
同样的套路,前端定义点击事件,去调用js里的 execute_test()函数。
html文件
<button type="button" id="exec_test" class="btn btn-sm btn-light" onclick="execute_test()" disabled="true">
<a href="javascript:void(0)">执行测试</a>
</button>
后端
后端通过server.build_job()去执行配置好的任务即可,这里也考虑到了,如果同时选择了module,mark执行后的操作,
test_result_info 这个代码有冗余,可以优化掉,暂时先这样了
class ExecTestHandler(BaseHandler):
'''
执行测试
:param sel_case_value 选择的case模块
sel_case_value 选择的case标记
'''
def post(self):
sel_case_value = self.get_argument("case_module")
sel_case_mark = self.get_argument("case_mark")
# 当选择了mark,没选择case模块,执行markcase
if sel_case_mark != '' and sel_case_value == '':
server = self.jenkins_server()
current_job_name = "exec_group_by_mark"
next_build_number = server.get_job_info(current_job_name)['nextBuildNumber']
server.build_job(current_job_name, {"mark_name": sel_case_mark})
test_result_info = {
"code": 0,
"desc": "",
"current_job_name": current_job_name,
"current_build_number": next_build_number,
"current_last_build_result": "测试中",
}
self.finish(test_result_info)
elif sel_case_mark == '' and sel_case_value != '':
server = self.jenkins_server()
if sel_case_value == "all_case":
current_job_name = "exec_all_case"
next_build_number = server.get_job_info(current_job_name)['nextBuildNumber']
server.build_job(current_job_name)
else:
current_job_name = "exec_group_by_module"
next_build_number = server.get_job_info('exec_group_by_module')['nextBuildNumber']
server.build_job("exec_group_by_module", {"case_name": sel_case_value})
test_result_info = {
"code": 0,
"desc": "",
"current_job_name": current_job_name,
"current_build_number": next_build_number,
"current_last_build_result": "测试中",
}
self.finish(test_result_info)
elif sel_case_value == '' and sel_case_mark == '':
self.finish({"code":-1,"desc":"缺少case模块参数"})
上面就是执行测试的过程了,因为执行测试是需要时间的,不会立即结束,所以返回点数据到前端页面展示,让人知道开始测试啦
效果就是这样
5.然后我这边怎么知道测试是否结束了呢?
这里通过前端的轮询请求查询,当执行结束后,把剩下的数据返回
前端
// 轮询Jenkins执行状态
var timer = null;
timer = window.setInterval(query_result, 10000); //10s一次
function query_result() {
req_data = {
"job_name": data['current_job_name'],
"job_number": data['current_build_number']
}
$.ajax({
type: "Post",
async: true,
dataType: "json",
data: req_data,
url: "/query_result",
success: function (data) {
//查询结果是"SUCCESS",请求查询构建信息
if (data["result"] == "SUCCESS") {
window.clearInterval(timer);
$.ajax({
type: "Post",
async: true,
dataType: "json",
url: "/get_job_info",
data: req_data,
success: function (data) {
window.clearInterval(window.setInterval(query_result, 5000));
// console.log("构建成功,查询构建信息");
// console.log(data);
document.querySelector("#job_status").textContent = data['current_last_build_result'];
document.querySelector("#job_duration").textContent = data['current_job_build_duration'];
document.querySelector("#start_time").textContent = data['current_job_build_start_time'];
document.querySelector("#test_report>a").textContent = "查看报告";
document.querySelector("#test_report>a").setAttribute("href", data["report_link"]);
},
error: function (data) {
window.clearInterval(window.setInterval(query_result, 5000));
console.log("查询构建信息失败");
},
})
后端这里有个查询jenkins构建是否完成的服务
class QueryResultHandler(BaseHandler):
'''
查询构建结果
:return build_result
'''
def post(self):
next_build_number = int(self.get_argument('job_number'))
current_job_name = self.get_argument('job_name')
server = self.jenkins_server()
job_last_build_result = server.get_build_info(current_job_name, next_build_number)['result'] # 任务最新一次构建结果
build_result = {"result": job_last_build_result}
self.finish(build_result)
当构建完成后,去调用另一个返回剩下信息和测试报告地址的服务(有个解析控制台log的方法,暂时还没写,不急,哈哈)
class ReturnJobInfoHandler(BaseHandler):
'''
构建完成后,返回信息
:return test_result_info
'''
def parse_jenkins_console_log(self):
'''
解析Jenkins控制台的log输出
:return: test_time 测试时长
'''
pass
def post(self):
next_build_number = int(self.get_argument('job_number'))
current_job_name = self.get_argument('job_name')
server = self.jenkins_server()
for i in range(0, 100, 1):
job_build_duration = (server.get_build_info(current_job_name, next_build_number)['duration']) / 1000
if job_build_duration == 0:
sleep(2)
job_build_duration
i += 1
new_job_build_duration = job_build_duration
time_stamp = server.get_build_info(current_job_name, next_build_number)['timestamp'] / 1000
current_job_build_start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time_stamp)) # 构建开始日期
report_link = "http://xxxx:9090/job/%s/%s/allure" % (current_job_name, next_build_number)
test_result_info = {
"current_last_build_result": "测试完成",
"current_job_build_duration": new_job_build_duration,
"current_job_build_start_time": current_job_build_start_time,
"report_link": report_link
}
self.finish(test_result_info)
OK 测试结束后,页面就可以看到完成的信息了
点击查看报告
还是比较好看的报告。
- 上面还有个查询机器状态的功能
这里也是通过js轮询去查询的,然后根据不同的状态给机器状态改色
//轮询测试机器执行任务状态
var timer = null;
timer = window.setInterval(query_running_build,3000);
function query_running_build(){
$.ajax({
type: "Get",
async: true,
dataType: "json",
url: "/query_running_build",
success:function(data){
document.querySelector("#running_build_status").textContent = data["running_status"];
if (data["running_status"] === "空闲中"){
document.querySelector("#running_build_status").setAttribute("style", "color: #00be67");
document.querySelector("#exec_test").disabled=false;
}
else{
document.querySelector("#running_build_status").setAttribute("style", "color: red");
document.querySelector("#exec_test").disabled=true;
}
},
error:function(data){
console.log("获取机器执行状态失败,停止轮询")
window.clearInterval(window.setInterval(query_running_build,3000));
}
})
}
后端对应的服务
class QueryRunningBuild(BaseHandler):
def get(self):
'''
返回是否有构建任务
:return: running_status: 构建中,空闲中
'''
server = self.jenkins_server()
running_build = server.get_running_builds()
if len(running_build) == 0:
run_status = {"running_status": "空闲中"}
else:
run_status = {"running_status": "构建中"}
self.finish(run_status)
差不多就是这些吧,附上
python-jenkins的地址:
https://pypi.org/project/python-jenkins/
https://python-jenkins.readthedocs.io/en/latest/api.html
后面再开发可能就是用java springboot +vue 去维护我们新的平台了。那时候应该有新的收获吧。
以上内容,如果有可以改进或者改错的地方,欢迎指出
【python开发】迈出第一步,这可能是我唯一一次的Python开发了的更多相关文章
- java微信开发API第一步 服务器接入
I如何接入服务器,下面就为大家进行介绍 一.说明 * 本示例根据微信开发文档:http://mp.weixin.qq.com/wiki/home/index.html最新版(4/3/2016 5:34 ...
- python 装饰器 第一步:基本函数
# 第一步:基本函数 def eat(): print('吃饭') # 调用 eat()
- 迈出第一步,Hexo博客搭建
很早之前看到别人的博客就总想着自己之后也要搭一个,最近突然来了干劲,就开始搭起了博客.不过搭博客还真是一个累活,失败了不下十次,用了好几天的时间,感觉自己在浪费时间,但是看到现在博客终于能用了,非常开 ...
- 开发Portlet第一步:如何基于Crystal开发静态Portlet?
当团队需要基于Crystal开发Porltet时,分为以下三部: 基于Crystal开发静态Portlet 基于将静态Portlet修改为基于测试数据的动态Portlet 将动态Portlet与动态数 ...
- 【转】APUE学习1:迈出第一步,编译myls.c
原文网址:http://blog.csdn.net/sddzycnqjn/article/details/7252444 注:以下写作风格均学习自潘云登前辈 /******************** ...
- Go语言开发的第一步,安装开发环境编写第一个Go程序
关于go就不介绍了,google自己的语言.其它的百科,最近比较郁闷,处于纠结状态,不想说话,真心不想说话. 开发工具安装: 安装包下载: http://code.google.com/p/go/do ...
- android unity3d开发学习第一步
1:下载unitysetup 开发环境 http://unity3d.com/unity/download/download-windows 2:下载三维制作软件 制作我们需要的场景 http://u ...
- Python股票量化第一步环境搭建
很久之前就希望可以量化分析股票,那么国内的股票数据API也有个,最有名的就是tushare,然后还有baostock. 今天我们就来研究一下这个baostock吧. 首先,我们需要下载一个叫做anac ...
- 迈出第一步:自适应高度的ImageView(AutoHeightImageView)
这个博客注册很久了,可是一直都没有勇气来写一点东西.今天解决了一个让我纠结很久的问题,于是,我决定开始我的博客生涯,希望我能坚持下去. 不知道是不是只有我遇到了这个问题,在ListView中显示图片, ...
随机推荐
- 29-main()的使用说明
* 1. main()方法作为程序的入口 * 2. main()方法也是一个普通的静态方法 * 3. main()方法可以作为我们与控制台交互的方式.(使用Scanner) 如何将控制台获取的数据传给 ...
- C调用C++代码
有时C程序里需要用到C++的类,但是C语言又不能直接调用类,这时需要把C++的类使用C接口封装后,再调用, 可以将封装后的C++代码编译成库文件,供C语言调用: 需要注意的是,封装的C++代码库文件是 ...
- Android Studio--家庭记账本(五)
想到每次删除之后将表单自动初始化,却发现会导致除去已经删除的不在出现在表单上,数据库中剩余的数据还会再次出现在表单中.导致表单上的数据越来越多.后来又想到,当我实现删除之后,把表单采用全部清除,再重新 ...
- Python数据类型-dic,set常见操作
字典常见方法 语法:字典名[新key]=value 功能:给字典增加键值 语法:字典名[字典里存在的key]=新的value 功能:修改字典里的值 功能:删除字典的元素,通过key来进行删除, ...
- Django 环境下常用的模型设计
Django 环境下常用的模型设计 用户表 继承 django.contrib.auth.model import AbstractUser AbstractUser 默认已经包含了很多字段了 id ...
- LeetCode 90 | 经典递归问题,求出所有不重复的子集II
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是LeetCode专题第56篇文章,我们一起来看看LeetCode第90题,子集II(Subsets II). 这题的官方难度是Medi ...
- 运用cookie实现记住密码自动登陆:
苦命的程序员:只有博客才能了解我的路!!! 废话不多说:直接上代码: 1.首先在现在登录的页面上来获取cookie的所有: 2.在验证页面来设置clookie的用户名和密码还有是否是自动登录: 到此就 ...
- CSS动画实例:旋转的圆角正方形
在页面中放置一个类名为container的层作为效果呈现容器,在该层中再定义十个名为shape的层层嵌套的子层,HTML代码描述如下: <div class="container&qu ...
- VulnHub靶场学习_HA: Chakravyuh
HA: Chakravyuh Vulnhub靶场 下载地址:https://www.vulnhub.com/entry/ha-chakravyuh,388/ 背景: Close your eyes a ...
- 机器学习:支持向量机(SVM)
SVM,称为支持向量机,曾经一度是应用最广泛的模型,它有很好的数学基础和理论基础,但是它的数学基础却比以前讲过的那些学习模型复杂很多,我一直认为它是最难推导,比神经网络的BP算法还要难懂,要想完全懂这 ...