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

孙高飞 · 2017年03月02日 · 最后由 Tesla Lau 回复于 2018年05月15日 · 1304 次阅读

背景

接触 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 官方 安卓客户端

rockyrock 回复

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

rockyrock 回复

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

life is short i use python ! i

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

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

感谢分享

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