其他测试框架 测试开发之路--Flask 之旅 (一)

孙高飞 · March 02, 2017 · Last by Tesla Lau replied at May 15, 2018 · 6283 hits

背景

接触python两周有余了。越来越喜欢这门语言,因为做什么都足够快,不像java那么笨重。虽然它还是那么容易出错。但是构建一个并不复杂的小工具的时候,它显的效率的多。就像海盗一样:快,狠,准。这里我就不比较java和python的优劣了,这个话题太大了,以我的水平也怕评论不好。我们就各自使用自己认为最适合项目的语言就好了。刚学python的第一周先是学习了一下基本语法,环境,IDE(好在有几年的java底子,学的比较快),然后用pytest和allure做了测试的基础架子,准备测试我们提供的python版本的SDK。之后用python重写了一遍环境管理的那一堆shell脚本,比shell少些了很多代码,之后扩展也好,维护也好都方便了很多(后悔这么晚才学python中~~~)。上周末在家开始学习django和flask, 考虑到底选什么作为建站的框架。后来还是选了flask,因为够小,够灵活。我暂时的需要可能不需要数据库之类的东西。所以也就没选什么都包办了的django。于是我拿我们环境管理的需求练手。之前是通过一堆脚本控制的,现在我做一个web,可以通过在页面上点点点就能管理好这些环境。我想一边练习一边记录其中的步骤,给像我一样刚接触的人一个快速上手的指引吧。由于都是业余时间做,所以更新的会比较慢。

Flask

这里介绍一下Flask给像我这种刚刚接触的人把。它给我的感觉就是个十分轻量级的,轻量级到令人发指的web框架。用过Flask之后才知道springMVC到底有多重。它使用起来也很简单,不需要任何web容器。 直接按下面的方式写一小段代码就可以了

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
return 'Hello World!'

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=9537)

当你运行这个脚本后,就可以在浏览器访问了。是不是够简单。同样它也支持像django那么大而全的功能,不过这些功能并没有被集成到Flask里面。 而是在你需要的时候通过安装相应的模块来实现。例如支持数据库的orm框架:Flask-SQLAlchemy 支持数据库迁移,升级等功能的模块:Flask_Migrate。 支持页面表单管理的模块:Flask-WTF。 这些都是需要的时候再安装就可以了。

学习资料

贴一下我找到的学习资料
Flask官方文档的中文翻译版本:http://docs.jinkan.org/docs/flask/index.html
Flask所有扩展模块的文档:http://flask.pocoo.org/extensions/
一个我觉得比较好的博客:http://www.pythondoc.com/flask-mega-tutorial/database.html#id2

开始

OK,我们首先安装Flask吧,直接用pip安装就好了。暂时我们的功能还很简单,不需要其他的扩展模块。先贴一个图片看看我们的项目结构。

我从上到下开始介绍吧

  • env_info是我们所有环境的配置文件,暂时我没有用数据库来保存这些信息。里面都是ini文件,详情不表了。
  • lib 是我放置控制docker的脚本的地方。如大家所见,都是些compile,deploy什么的
  • web 是我们Flask的根目录
  • static是我们放置静态文件的地方,例如css,js,图片什么的。注:这是Flask的默认设置
  • templates是我们所有模板的文件,大家可以先理解为所有的html文件都放在这里。注:这是Flask的默认设置

首先我们先在web目录的init.py文件里写一下我们Flask的配置:

# coding=utf-8

from flask import Flask

app = Flask(__name__)
# 如果想要使用Flask-WTF的表单,需要一个config文件
app.config.from_object('config')

接着创建一个urls.py文件,用来编写我们的路由和启动命令。就如我们上面的例子一样,我们这样写

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=9537)

之后运行urls.py就算是我们的启动脚本了。然后写一个首页的路由方法。如下:

@app.route('/')
@app.route('/index', methods=['GET', 'POST'])
def index():
configs = list_all_config()
return render_template('index.html', configs=configs)

我们来解释一下吧。 @app.route, 就是我们的路由方法,可以配置多个, 也就是多个url都可以绑定到这一个方法上。 methods参数设定了方法类型。 可以看到方法里面我们取了所有的配置信息(就是env_info中所有的配置信息)。我们希望将所有的配置信息都展示在首页上。 所以取出信息以后,我们使用render_template这个渲染模板的方法,将index.html进行渲染并把configs传递到模板中(在Flask中页面就是模板)。 好了,现在我们去看看这个index.html里是怎么写的吧。

<div class="row clearfix">
{% for config in configs %}
<div class="
col-md-4 column">
<h2>
{{ config.name_prefix }}
</h2>
<p>
</p>
<p>
<a class="
btn" href="#">查看详情 »</a>
<a class="
btn" href="/restart/{{ config.name_prefix }}">部署环境 »</a>
</p>
</div>
{% endfor %}
</div>

这里来看一下Flask内置的Jinjia2的语法吧。流程控制语句使用{% content %} 来表示, 直接把元素输出到页面上使用{{% element %}}。 上面的代码就是说,我循环一下传递进来的configs列表。把每个配置的名字都展示到页面上,然后每个配置都有两个链接,分别是查看配置的详情和部署环境。这个是后话了。 现在如果我们启动web服务,使用浏览器访问就会看到所有的配置都显示在了我们的主页上。 但是还有点不够,那就是我们的页面实在太难看了。 没有任何的布局。 但是我还是个前端的小白,让我重头学一遍css,js什么的太费时间了。 所以我们可以取巧一下,偷懒一下。来给我们的web换个装

bootstrap

换装工具就是bootstrap了。 下载地址如下: http://getbootstrap.com/
我们要把下载下来的文件解压,然后放到static目录下,如图:

然后我们在index.html加入引用

<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

<!-- HTML5 Shim Respond.js 用于让 IE8 支持 HTML5元素和媒体查询 -->
<!-- 注意: 如果通过 file:// 引入 Respond.js 文件,则该文件无法起效果 -->
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>

接着我们使用在线的页面生成工具:http://www.runoob.com/try/bootstrap/layoutit/#

我们用这个工具拖拽好自己的布局之后。就可以点击那个下载。把HTML代码弄到我们的页面里。 然后我们就有了如下的页面了

好吧,我不具备什么审美天赋。也没下什么大工夫。看着顺眼就行。

最关键的功能

好了,到了最关键的功能了,那就是部署一个环境。 所以在我们的首页中才会有这样一个链接:

<a class="btn" href="/restart/{{ config.name_prefix }}">重新启动 »</a>

现在我们需要为这个链接创造另一个路由方法。如下:

@app.route('/restart/<name>', methods=['GET', 'POST'])
def restart(name):
workspace = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
path = workspace + '/boot.sh'
config = workspace + '/env_info/' + name + '.ini'
command = 'bash %s %s' % (path, config)
result = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
jobs = Jobs.jobs
if name in jobs.keys():
jobs[name].kill()
jobs.pop(name)
jobs[name] = result
return render_template('restart.html', name=name)

这里我们看到了一个用法/restart/。 这个<>中的name就是页面向我们传递的参数。 所以在restart方法里我们才可以使用name这个参数。我们根据name找到相应的配置文件并使用subprocess模块进行调用。由于我们希望这个步骤是异步的,不能再部署过程中页面一直处于卡死的状态。所以使用Popen这个函数来调用。 我们把标准输出和标准错误都重定向到管道中。把这个调用后返回的对象保存在一个全局的dict中。方便之后取log的时候用。 那么现在我们可以在页面上点击链接进行部署了。但是有一个问题,就是我们不知道部署当前是失败了还是成功了。 我们希望能实时的在页面上看到部署的日志, 我们需求页面能实时的显示最新的log而不必刷新页面

Jquery Ajax

我这个前端水平就只能想到这样一个方案了,现在我们去现在一个Jquery.js吧。 同样放到static目录下。

然后我们再搞一个log页面, 在head标签中我们引入Jquery。

<script type=text/javascript src="{{ url_for('static', filename='jquery.js') }}"></script>

然后我们加入下面的一段JS:

<script type=text/javascript>
$SCRIPT_ROOT = {{ request.script_root|tojson|safe }};
</script>


<script type=text/javascript>
$(function () {
for (var i = 1; i < 10000; i++) {
$.get($SCRIPT_ROOT + '/log/{{ name }}', function (data) {
$("#log").append(data);
})
}
});
</script>


<dev id="log"></dev>

我来解释一下上面这段JS。 $.get方法就是动态执行一个请求的方法,后面跟上url。 $("#log")是JS的选择器,用来定位页面元素,意思是找到id为log的元素并执行append的操作。所有的代码的意思就是循环不停的调用这个get方法,并将返回值作为data传递给之后的function,这个function用来定位元素并把日志追加到页面上。 那么现在,我们唯一缺的就是再写这么一个路由方法实时读取日志了。如下:

@app.route('/log/<name>')
def update_log(name):
# time.sleep(1)
job = Jobs.jobs[name]
# error = job.stderr.readline()
out = job.stdout.readline()
return '<p>'+out+'</p>'

看过之前restart方法的代码,我们知道所有的Popen返回的结果对象都放到了一个全局的dict里。 现在我们根据页面AJax传递name把它找出来。然后读取一行日志信息返回。OK,万事大吉。 我不会截动态图~~ 大家见谅。。。

唠叨两句

我知道前端的发展现在已经很厉害了,Jquery这种玩意都不知道过时了多久,我们公司的前端都已经在用React.js。可我是没那么多时间去学了。反正我们做的都是给项目内部用的,所以我想不用纠结什么前沿技术了,够用就好。目前我就做了这么多。 大概实现了最主要的几个功能:显示配置,部署环境,实时获取log。之后加上创建配置和修改配置的功能后就基本上达到可用状态了。额,想想在老东家的时候用springMVC为了达到可用状态可是比这多花了不少的时间。所以我现在很喜欢python,在构建这些工具的时候效率更高,更快。 有个小段子是我最开始请教我司写SDK的小伙子的时候,问他你为什么选择用python,他回答说:为了多活两年。 恩,现在我有点理解他为什么这么说了。 之后我会慢慢的完善这个小网站,就当是练手了,然后我会慢慢的把过程都记录下来发到社区里,一个是对我的学习和工作有个记录,再一个也希望对一些同学有所帮助。毕竟在工作中做测试平台,测试工具什么的可能都要封装成一个web。由于这纯是我的个人意愿,组织上并没有给我在工作中安排时间去做。 所以我都是抽空搞一搞,进度可能有点慢。希望之后能成熟到给一些非技术人员,例如产品,售前的同学去搭建一个他们需要的环境,而不再需要懂技术的人跑去给他们运行脚本部署。恩~ 保持学习

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 16 条回复 时间 点赞

谢谢分享,赞效率~

高产啊~~~

最近刚好在做这个,很有用,多谢分享👍

学得好快,感谢分享

创建进程把输出重定向到管道的话,需要及时从管道中读取字符串,不然管道堵塞会出问题的

多谢提醒~~ 刚刚学习python,不知道这些。 我这边估计得有几十个环境。 用户可能不一定把日志都看完。 我该怎么做呢? 要不要把日志都重定向到文件里?

最近也在学flask,希望这个系列继续分享啊

不错不错,我自己也折腾了下Flask,蓝图+mysql+jquery的一个增删改成练习了下,后面暂时就没有弄了

最近写了几个基于Flask的内部服务,写的比较简单,还没有用到mysql,用的最简单的sqlite,感觉够用。

Web页面生成工具很实用,感谢分享~

谢谢分享,我觉得解决了一个痛点就是 自己写的一堆脚本不方便执行 现在可以搭建网站 然后在里面点击就行了。。

—— 来自TesterHome官方 安卓客户端

孙高飞 #12 · March 07, 2017 作者
rockyrock 回复

其实我也是这个出发点。。。

rockyrock 回复

一样的感受,打算做和楼主一样的事情。参考了。以前我是用pyqt4做一个很简单的ui,还是感觉不方便 ,还有就是以后落实的话,html有很好的维护性,易用性。反正浏览器每个电脑都装的

life is short i use python !i

flask系统的资料比较少,推荐一个 flask 的入门教程给大家:

http://xc.hubwiz.com/course/562427361bc20c980538e26f?affid=20180313testerhome

感谢分享

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up