Python 测开27期 - julia - 学习笔记 - 测试平台后端开发

测试平台价值

  • 市场需求:测试平台的开发目前是测试行业中的一个热门的技术
  • 公司需求:
    • 能为团队带来市场价值,比如 wetest
    • 高效的平台做调度中心
  • 例子:
    • DevOps 平台
    • 精准化测试平台
    • 质量监控平台等等

测试平台 - 前提

  • 已有的开源测试平台不能满足需要,不要轻易造轮子
  • 测试的体系健全
  • 当体系、测试技术等游刃有余,构建平台展示带动整个团队甚至团队之外的其他团队
  • 需要公司级别的定制,比如整合公司内部的多套平台

常用的技术架构与组件

  • 前端技术架构:bootstrap、vue、react
  • 后端技术架构:django、flask、spring boot
  • 数据存储: mysql、 es
  • 任务调度架构:jenkins
  • 数据报表:echarts、vega、kibana、grafana、allure

常见的测试平台开发模式

  • 大而全
    • Python Django
    • Java Spring Boot
    • React(前端框架)

常见的测试平台开发模式

  • 小而简
    • Python Flask
    • Java sparkjava
    • Vue

Flask 简介

Flask 是一个轻量级的 web 开发框架。 它依赖 jinja2 和 Werkzeug WSGI 服务的一个微型框架。

一个最小的应用

# 1. 导入 Flask 模块
from flask import Flask
# 2. 创建Flask应用程序实例
app = Flask(__name__)

# 3. 定义路由及视图函数
@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"
# 4. 启动程序
if __name__ == '__main__':
    app.run()

两种运行方式

  • 代码调用
    • app.run()
  • 命令行运行
    • bash(mac/linux)
    • cmd(windows)
    • powershell(windows)

linux/mac: 命令行运行

$ export FLASK_APP=hello
$ flask run

windows: 命令运行

set FLASK_APP=hello
flask run

定义路由

  • 通过装饰器@app.route 添加路由
# 添加路由
@app.route('/hogwarts')
def api_demo2():
    return '这是霍格沃兹测试学院数据'

动态路由

  • 通过app.route('/user/<username>')添加动态路由
# 添加动态路由
@app.route('/listcases/<username>')
def select_case1(username):
    pass

限定类型

  • 路径中添加 <类型:变量名> 来限定变量的类型
  • @app.route('/post/<int:post_id>')
string accepts any text without a slash (the default)
int accepts integers
float like int but for floating point values
path like the default but also accepts slashes
any matches one of the items provided
uuid accepts UUID strings

地址尾部的“/”

  • 路由的尾部带有“/”(浏览器的地址栏中输入和不输入“/”的效果一样)
  • 路由的尾部没有“/”(输入的 URL 的结尾不能加“/”,会报错)

处理请求数据

request 对象

request 的常用属性/方法

属性/方法 说明
args 记录请求中的查询参数
json 记录请求中的 json 数据
files 记录请求上传的文件
form 记录请求中的表单数据
method 记录请求使用的 HTTP 方法
url 记录请求的 URL 地址
host 记录请求的域名
headers 记录请求的头信息

普通参数处理

  • 场景:
    • 普通的 url 链接,接收一个 get 请求
  • 解决办法
    • request.args

json 参数处理

  • 场景:
    • POST 相关的请求,带有 json 数据格式
  • 解决办法
    • request.json

表单请求

  • 场景
    • 比如:测试人网站的登录接口,需要用户名和密码,前端会提交一个 form 表单给后台
  • 解决办法
    • request.form

文件请求

  • 场景:
    • 页面上有个更新头像的功能, 或者上传一个 excel 文件的功能, 允许我们提交一个图片,或者文件 到后端服务器,那么
  • 解决方法
    • request.files.get(‘file’) 获取文件对象
    • filename 获取文件对象的文件名
    • save()方法 保存文件到指定路径下

处理响应信息

  1. 返回文本
@app.route('/text')
def get_text():
    return '返回文本'
  1. 返回元祖
    • (response, status)
    • (response, headers)
    • (response, status, headers)
    • 响应状态码默认为 200
@app.route('/tuple')
def tuple_res():
    return "你好呀", 200, {"hogwarts": "ad"}
  1. 返回json
    • 直接返回 dict 会转换为 json
    • 使用jsonify()方法,通过参数传入键值对
# 返回json
@app.route('/json')
def get_json():
    # jsonify({'status': 0})
    return jsonify(status=1, name="ad", age = 20)
# 返回字典
@app.route('/dict')
def get_dict():
    return {'status': 0}
  1. 返回 html
    • 使用模板渲染技术
    • html 文件必须在同级的 templates 目录下
@app.route('/html')
def get_html():
        # 调用render_template方法,传入html 文件的名称。
    # 注意html文件必须在 templates 目录下
    return render_template('demo.html')
<!-- 
html文件必须在templates目录下
/application.py
/templates
    /hello.html 
    -->
<html>
  <body>
    <h1>霍格沃兹测试开发</h1>
  </body>
</html>
  1. 设置额外数据-make_response()
    • 添加更多的响应信息
      • 设置 cookie
      • 设置响应头信息等
@app.route('/')
def index():
    resp = make_response(render_template('demo.html'))
    # 设置cookie
    resp.set_cookie('username', 'the username')
    # 设置响应头信息
    resp.headers["hogwarts"] = "ad2"
    return resp

Flask RESTX 接口配置

RESTFUL 风格规范的接口

from flask import Flask
from flask_restx import Resource, Api

app = Flask(__name__)
api = Api(app)

# 接口路径定义到类上,对应的不同请求操作创建不同的方法
@api.route('/user')
class User(Resource):
    # restful 风格的 get 方法
    def get(self):
        return {"code": 0, "msg": "get success"}

    # restful 风格的 post 方法
    def post(self):
        return {"code": 0, "msg": "post success"}

    # restful 风格的 put 方法
    def put(self):
        return {"code": 0, "msg": "put success"}

Flask RESTX 集成 Swagger

Flask-RESTX namespace 的使用

  1. 定义 Namespace 实例
  2. 为类添加装饰器 @namespace.route("") 控制子路由
  3. 为命名空间指定访问资源路径 api.add_namespace(case_ns, '/case')
from flask import Flask
from flask_restx import Resource, Api

app = Flask(__name__)
api = Api(app)


# 接口路径定义到类上,对应的不同请求操作创建不同的方法

@api.route("/case")
class TestCase(Resource):
    # restful 风格的 get 方法
    def get(self):
        return {"code": 0, "msg": "get success"}

    # restful 风格的 post 方法
    def post(self):
        return {"code": 0, "msg": "post success"}

    # restful 风格的 put 方法
    def put(self):
        return {"code": 0, "msg": "put success"}

    # restful 风格的 delete 方法
    def delete(self):
        return {"code": 0, "msg": "delete success"}


@api.route("/demo")
class Demo(Resource):
    # restful 风格的 get 方法
    def get(self):
        return {"code": 0, "msg": "get success"}

    # restful 风格的 post 方法
    def post(self):
        return {"code": 0, "msg": "post success"}

    # restful 风格的 put 方法
    def put(self):
        return {"code": 0, "msg": "put success"}

    # restful 风格的 delete 方法
    def delete(self):
        return {"code": 0, "msg": "delete success"}


if __name__ == '__main__':
    app.run(debug=True)
  • 第一种:使用@api.doc()或者@namespace.doc()装饰请求方法,例如:@api.doc(params={'id': 'An ID'})
  • 第二种【推荐】:使用 parser = api.parser() 配合 @api.expect(parser) 装饰器实现入参的校验和传入
#第二种示例
from flask import Flask, request
from flask_restx import Resource, Api, Namespace,fields
from backend_demo.demo.log_util import logger

app = Flask(__name__)
api = Api(app)
hello_ns = Namespace("demo", description="demo学习")

@hello_ns.route("")
class Demo(Resource):
    get_parser = api.parser()
    get_parser.add_argument('id',type=int,location="args")

    @hello_ns.expect(get_parser)
    def get(self):
        logger.info(f"request.args ===>{request.args}")
        return {"code": 0, "msg": "get success"}

api.add_namespace(hello_ns,"/hello")

if __name__ == '__main__':
    app.run(debug=True)

api.parser()用法

格式:api.parser().add_argument(参数名, 关键字参数)

  • 第一个参数是参数名
  • 后面是关键字传参,常用的关键字有:
    • type :类型
    • required 约束控制
    • choices 枚举参数
    • location 对应 request 对象中的属性

常用的关键字参数

参数名 参数值
type int,bool,float,string,FileStorage
required True/False
choices 枚举
location args,form,json,files

get 请求示例

  • 处理 get 请求参数 args
from flask import Flask, request
from flask_restx import Resource, Api, Namespace,fields
from backend_demo.demo.log_util import logger

app = Flask(__name__)
api = Api(app)
hello_ns = Namespace("demo", description="demo学习")

@hello_ns.route("")
class Demo(Resource):
    # 通过 parser 解析器定义参数,添加约束限制
    get_parser = api.parser()
    get_parser.add_argument('id',type=int,location="args",required=True)
    get_parser.add_argument('case_title', type=str, location="args",required=True)

    # 通过 expect() 传递参数
    @hello_ns.expect(get_parser)
    def get(self):
        logger.info(f"request.args ===>{request.args}")
        return {"code": 0, "msg": "get success"}

api.add_namespace(hello_ns,"/hello")

if __name__ == '__main__':
    app.run(debug=True)

post 请求示例

  • 处理 json 格式
...
post_parser = api.parser()
post_parser.add_argument('id', type=int, location="json")
post_parser.add_argument('case_title', type=str, location="json")

# 通过 expect() 传递参数
@hello_ns.expect(post_parser)
def post(self):
    logger.info(f"request.args ===>{request.args}")
    return {"code": 0, "msg": "get success"}

...

post 请求示例

  • 处理 files/form/choice 格式
post_parser = api.parser()
post_parser.add_argument('file', type=FileStorage, location="files")
post_parser.add_argument('choice', choices=('one', 'two'), location="args")
post_parser.add_argument('param1', type=int, help='username', location='form', required=True)
post_parser.add_argument('param2', type=str, help='password', location='form', required=True)
@hello_ns.expect(post_parser)
def post(self):
    logger.info(f"request.args ===>{request.args}")
    logger.info(f"request.args ===>{request.files}")
    logger.info(f"request.args ===>{request.form}")
    return {"code": 0, "msg": "get success"}