测试平台价值
- 市场需求:测试平台的开发目前是测试行业中的一个热门的技术
- 公司需求:
- 能为团队带来市场价值,比如 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()方法 保存文件到指定路径下
处理响应信息
- 返回文本
@app.route('/text')
def get_text():
return '返回文本'
- 返回元祖
- (response, status)
- (response, headers)
- (response, status, headers)
- 响应状态码默认为 200
@app.route('/tuple')
def tuple_res():
return "你好呀", 200, {"hogwarts": "ad"}
- 返回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}
- 返回 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>
- 设置额外数据-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 的使用
- 定义 Namespace 实例
- 为类添加装饰器
@namespace.route("")
控制子路由 - 为命名空间指定访问资源路径
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"}