我们这个测试平台的主要功能有:上传储存用例,查看用例,运行用例,下载用例,上传测试报告等。
第一步:创建app.py,用来启动一个flask服务
from flask import Flask
import conf
from flask_restful import Api,Resource
# 实例化一个Flask对象,用于启动flask服务
app = Flask(__name__)
# 添加配置文件
app.config.from_object(conf)
# 实例化flask_restful对象
api = Api(app)
# 设置用例储存视图函数
class TestCaseStoreView(Resource):
def get(self):
return '666'
api.add_resource(TestCaseStoreView, '/', endpoint='testcase_store')
if __name__ == "__main__":
app.run(debug=True,host='127.0.0.1',port=5055)
第二步:创建conf.py文件,用于项目的配置文件
"""
flask项目的配置文件
"""
# 数据库连接配置
HOSTNAME = '192.168.1.104'
PORT = 3306
USERNAME = 'root'
PASSWORD = 'ouyi1994'
DATABASE = 'auto_test_frame'
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
SQLALCHEMY_TRACK_MODIFICATIONS = True
第三步:创建models.py文件,用于创建用例表
"""
使用flask_sqlalchemy模块声明数据库表
"""
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from app import app
# 实例化一个SQLAlchemy对象,用于操作数据库
db = SQLAlchemy(app)
class TestCaseModel(db.Model):
"""
用例表模型类
"""
__tablename__ = 'testcase'
id = db.Column(db.Integer, primary_key=True, autoincrement=True) # id,主键
name = db.Column(db.String(30), unique=True, nullable=False) # 用例名
description = db.Column(db.String(200), unique=False, nullable=True) # 描述
file_path = db.Column(db.String(200), unique=True, nullable=False) # 用例文件路径
update_time = db.Column(db.DateTime, default=datetime.now()) # 更新时间
if __name__ == "__main__":
db.create_all()
db是SQLAlchemy的一个实例化对象,用于操作数据库。所以我们在项目中会经常将db对象导入到其他文件中操作。为了防止导入db时,因为循环导入报错。我们可以将db对象放置在一个单独的第三方文件中存放。
第一步:新建一个exit.py文件,用于生成db对象。
"""
中间文件
"""
from flask_sqlalchemy import SQLAlchemy
# 实例化一个SQLAlchemy对象,用于操作数据库
db = SQLAlchemy()
使用该文件可以有效防止引用冲突
第二步:创建app.py,用来启动一个flask服务
from flask import Flask
import conf
from flask_restful import Api,Resource
from exit import db
# 实例化一个Flask对象,用于启动flask服务
app = Flask(__name__)
# 添加配置文件
app.config.from_object(conf)
# 实例化flask_restful对象
api = Api(app)
# 将SQLAlchemy对象db关联到app
db.init_app(app)
# 设置用例储存视图函数
class TestCaseStoreView(Resource):
def get(self):
return '666'
api.add_resource(TestCaseStoreView, '/', endpoint='testcase_store')
if __name__ == "__main__":
app.run(debug=True,host='127.0.0.1',port=5055)
第三步:创建models.py文件,用于创建用例表
"""
使用flask_sqlalchemy模块声明数据库表
"""
from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from exit import db
class TestCaseModel(db.Model):
"""
用例表模型类
"""
__tablename__ = 'testcase'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(30), unique=True, nullable=False)
description = db.Column(db.String(200), unique=False, nullable=True)
file_path = db.Column(db.String(200), unique=True, nullable=False)
update_time = db.Column(db.DateTime, default=datetime.now())
使用Flask_Migrate和flask_script来完成数据库的初始化和迁移
因为我们在项目开发中会频繁的改动数据库类结构,使用Flask_Migrate和flask_script可以非常方便的来完成数据库的初始化和迁移管理。所有的迁移操作其实都是Alembic做的,能跟踪模型的变化,并将变化映射到数据库中。
创建manage.py文件,用来完成数据库的管理。
"""
用于数据库初始化
"""
from flask_script import Manager
from flask_migrate import Migrate,MigrateCommand
from exit import db
from app import app
from models import TestCaseModel # 将需要初始化或者迁移的表模型类导入到该文件中
manage = Manager(app)
Migrate(app,db)
manage.add_command('db',MigrateCommand)
if __name__ == '__main__':
manage.run()
python manage.py db init
生成数据表初始化的迁移文件migrates
python manage.py db migrate
,自动检测模型,生成迁移脚本。python manage.py db upgrade
,将迁移脚本的操作映射到数据库中。编辑app.py文件
import os
from datetime import datetime
from flask import Flask, request
import conf
from flask_restful import Api, Resource
from exit import db
from models import TestCaseModel
# 实例化一个Flask对象,用于启动flask服务
app = Flask(__name__)
# 添加配置文件
app.config.from_object(conf)
# 实例化flask_restful对象
api = Api(app)
# 将SQLAlchemy对象db关联到app
db.init_app(app)
# 设置用例储存视图函数
class TestCaseStoreView(Resource):
def post(self):
name = request.form.get('name') # 获取请求参数:name
description = request.form.get('description') # 获取请求参数:description
test_file = request.files.get('test_file') # 获取请求参数:test_file
test_file_name = test_file.filename # 用例文件名。
# 判断name参数是否符合规定
if name is not None and isinstance(name,str):
# 判断test_file参数是否符合规定
if test_file is not None and test_file_name.endswith('.py'):
# 判断description参数是否为空
if description is not None:
# 判断判断description参数是否为str类型
if isinstance(description,str):
now = datetime.now().strftime('%Y-%m-%d-%H%M%S') # 生成时间戳
now_path = os.getcwd() # 获取当前目录路径
test_file.save(f'./{
now}_{
test_file_name}') # 保存用例文件到当前目录路径
file_path = os.path.join(now_path, f'{
now}_{
test_file_name}') # 拼接出用例文件路径
testcase = TestCaseModel(name=name, file_path=file_path,description=description)
# 保存到数据库
db.session.add(testcase)
db.session.commit()
return jsonify({
'code': 200,
'message': 'success'
})
else:
return jsonify({
'code': 404,
'message': 'description必须为str类型'
})
else:
now = datetime.now().strftime('%Y-%m-%d-%H%M%S') # 生成时间戳
now_path = os.getcwd() # 获取当前目录路径
test_file.save(f'./{
now}_{
test_file_name}') # 保存用例文件到当前目录路径
file_path = os.path.join(now_path,f'{
now}_{
test_file_name}') # 拼接出用例文件路径
testcase = TestCaseModel(name=name,file_path=file_path)
# 保存到数据库
db.session.add(testcase)
db.session.commit()
return jsonify({
'code':200,
'message':'success'
})
else:
return jsonify({
'code': 404,
'message': 'test_file参数不能为空,且必须为python文件'
})
else:
return jsonify({
'code':404,
'message':'name参数不能为空,且必须为str类型'
})
api.add_resource(TestCaseStoreView, '/testcase_store/', endpoint='testcase_store')
if __name__ == "__main__":
app.run(debug=True,host='127.0.0.1',port=5055)
我们使用postman来构造数据访问接口,看接口功能是否实现。
在上面的接口实现中,我们需要使用了if语句来校验前端传过来的参数。这种方式是不可取的,因为会使得我们的代码是否冗余。我们可以看到这个接口中只有3个参数,确使用了大量的if语句来进行校验。
我们可以使用flask-wtf来校验前端参数。
新建一个form.py文件,来进行参数的校验工作。
from wtforms import Form, StringField, FileField
from wtforms.validators import Length, InputRequired, ValidationError
from flask_wtf.file import FileRequired
# 校验用例储存请求参数
class TestCaseForm(Form):
name = StringField(validators=[Length(max=30, message='参数name不合法,参数长度不能大于30'),
InputRequired(message='参数name不能为空')
])
description = StringField(validators=[Length(max=200, message='参数description不合法,参数长度不能大于200')])
test_file = FileField(validators=[FileRequired(message='必须上传用例文件')])
def validate_test_file(self, field):
"""自定义test_file验证器"""
if not field.data.filename.endswith('.py'): # field.data获取需要校验的参数值,这里为上传的文件对象
raise ValidationError('用例文件必须为python文件') # 使用ValidationError抛出提示信息
修改app.py文件:
import os
from datetime import datetime
from flask import Flask, request, jsonify
import conf
from flask_restful import Api, Resource
from exit import db
from forms import TestCaseForm, GetTestCaseForm
from models import TestCaseModel
from werkzeug.datastructures import CombinedMultiDict
# 实例化一个Flask对象,用于启动flask服务
app = Flask(__name__)
# 添加配置文件
app.config.from_object(conf)
# 实例化flask_restful对象
api = Api(app)
# 将SQLAlchemy对象db关联到app
db.init_app(app)
# 设置用例储存视图函数
class TestCaseStoreView(Resource):
def post(self):
now = datetime.now().strftime('%Y-%m-%d-%H%M%S') # 生成时间戳
now_path = os.getcwd() # 获取当前目录路径
# 实例化TestCaseForm对象,用于参数校验。其参数为需要校验的参数字典列表
# CombinedMultiDict将两个属性组合起来传给校验器
form = TestCaseForm(CombinedMultiDict([request.form,request.files]))
if form.validate():
# 参数校验通过
name = form.name.data # 获取参数name的值
description = form.description.data # 获取参数description的值
test_file = form.test_file.data # 获取用例文件test_file对象
# 判断name是否已在数据库中存在
name_fag = db.session.query(TestCaseModel).filter(TestCaseModel.name==name).first()
if name_fag is None:
if description is not None:
# 保存用例文件
file_path = os.path.join(now_path, f'./case_files/{
now}_{
test_file.filename}')
test_file.save(file_path)
# 保存数据库
testcase = TestCaseModel(name=name,description=description,file_path=file_path)
db.session.add(testcase)
db.session.commit()
res = {
'code': 200,
'message': 'success'
}
return jsonify(res)
else:
# 保存用例文件
file_path = os.path.join(now_path, f'./case_files/{
now}_{
test_file.filename}')
test_file.save(file_path)
# 保存数据库
testcase = TestCaseModel(name=name, file_path=file_path)
db.session.add(testcase)
db.session.commit()
res = {
'code': 200,
'message': 'success'
}
return jsonify(res)
else:
res = {
'code': 404,
'message': '用例名字不能重复'
}
return jsonify(res)
else:
# 参数校验不通过
res = {
'code': 404,
'message': form.errors
}
return jsonify(res)
api.add_resource(TestCaseStoreView, '/testcase_store/', endpoint='testcase_store')
if __name__ == "__main__":
app.run(debug=True,host='127.0.0.1',port=5055)
运行结果如下:
修改form.py文件
from wtforms import Form, StringField, FileField, IntegerField
from wtforms.validators import Length, InputRequired, ValidationError
from flask_wtf.file import FileRequired
# 校验用例储存请求参数
class TestCaseForm(Form):
name = StringField(validators=[Length(max=30, message='用例名字不合法,参数长度不能大于30'),
InputRequired(message='用例名字不能为空')
])
description = StringField(validators=[Length(max=200, message='用例描述不合法,参数长度不能大于200')])
test_file = FileField(validators=[FileRequired(message='必须上传用例文件')])
def validate_test_file(self, field):
"""自定义test_file验证器"""
if not field.data.filename.endswith('.py'): # field.data获取需要校验的参数值,这里为上传的文件对象
raise ValidationError('用例文件必须为python文件') # 使用ValidationError抛出提示信息
class GetTestCaseForm(Form):
id = IntegerField(validators=[InputRequired('用例id不能为空')])
修改app.py
import os
from datetime import datetime
from flask import Flask, request, jsonify
import conf
from flask_restful import Api, Resource
from exit import db
from forms import TestCaseForm, GetTestCaseForm
from models import TestCaseModel
from werkzeug.datastructures import CombinedMultiDict
# 实例化一个Flask对象,用于启动flask服务
app = Flask(__name__)
# 添加配置文件
app.config.from_object(conf)
# 实例化flask_restful对象
api = Api(app)
# 将SQLAlchemy对象db关联到app
db.init_app(app)
# 设置用例储存视图函数
class TestCaseStoreView(Resource):
def post(self):
now = datetime.now().strftime('%Y-%m-%d-%H%M%S') # 生成时间戳
now_path = os.getcwd() # 获取当前目录路径
# 实例化TestCaseForm对象,用于参数校验。其参数为需要校验的参数字典列表
# CombinedMultiDict将两个属性组合起来传给校验器
form = TestCaseForm(CombinedMultiDict([request.form,request.files]))
if form.validate():
# 参数校验通过
name = form.name.data # 获取参数name的值
description = form.description.data # 获取参数description的值
test_file = form.test_file.data # 获取用例文件test_file对象
# 判断name是否已在数据库中存在
name_fag = db.session.query(TestCaseModel).filter(TestCaseModel.name==name).first()
if name_fag is None:
if description is not None:
# 保存用例文件
file_path = os.path.join(now_path, f'./case_files/{
now}_{
test_file.filename}')
test_file.save(file_path)
# 保存数据库
testcase = TestCaseModel(name=name,description=description,file_path=file_path)
db.session.add(testcase)
db.session.commit()
res = {
'code': 200,
'message': 'success'
}
return jsonify(res)
else:
# 保存用例文件
file_path = os.path.join(now_path, f'./case_files/{
now}_{
test_file.filename}')
test_file.save(file_path)
# 保存数据库
testcase = TestCaseModel(name=name, file_path=file_path)
db.session.add(testcase)
db.session.commit()
res = {
'code': 200,
'message': 'success'
}
return jsonify(res)
else:
res = {
'code': 404,
'message': '用例名字不能重复'
}
return jsonify(res)
else:
# 参数校验不通过
res = {
'code': 404,
'message': form.errors
}
return jsonify(res)
# 查看用例视图函数
class GetTestCaseView(Resource):
def get(self):
form = GetTestCaseForm(request.args)
if form.validate():
id = form.id.data
id_fag = db.session.query(TestCaseModel).filter(TestCaseModel.id==id).first()
# 查看id是否在数据库中存在
if id_fag is not None:
res = {
'code': 200,
'id': id_fag.id,
'name': id_fag.name,
'description': id_fag.description,
'file_path': id_fag.file_path,
'update_time': str(id_fag.update_time)
}
return jsonify(res)
else:
res = {
'code': 404,
'message': '未找到对应的用例信息'
}
return jsonify(res)
else:
res = {
'code':404,
'message':form.errors
}
return jsonify(res)
api.add_resource(TestCaseStoreView, '/testcase_store/', endpoint='testcase_store')
api.add_resource(GetTestCaseView, '/get_testcase/', endpoint='get_testcase')
if __name__ == "__main__":
app.run(debug=True,host='127.0.0.1',port=5055)
为了防止返回的json数据在浏览器中中文乱码,我们需要给增加app增加一项配置
修改conf.py文件:
"""
flask项目的配置文件
"""
# 数据库连接配置
HOSTNAME = '192.168.1.104'
PORT = 3306
USERNAME = 'root'
PASSWORD = 'ouyi1994'
DATABASE = 'auto_test_frame'
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 防止浏览器乱码
JSON_AS_ASCII = False
/
我们在视图函数中定义url地址时,最好加上/
。因为这样我们在浏览器上访问url时,不论是否加上/
都可以访问到。实际上是浏览器会自动补上/
我们之所以使用pycharm这种开发工具来编写代码,有一个重要的原因是方便调试。
例如:我们创建了一个视图函数并映射了一个url,我们想要获取到url请求中的参数。但是我们并不知道这个参数是中request的哪个属性下,这时我们就可以使用debug模式
第一步:在pycharm中启动debug模式
第二步:在浏览器访问url
第三步:调试,找到我们想要的参数
当然调试并不是只有这一个作用,大家在使用过程中可以自己多摸索摸索
总结:
到这里我们就完成了,用例储存及查询用例两个后台接口的开发。当然这种实现逻辑并不是很好,因为我们只是教学讲解,所以用较简单的方式来完成它即可。