python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态

1.用例文件下载

1.1 接口分析

在这里插入图片描述

1.2 实现

第一步:在forms.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不能为空')])

# 校验用例下载请求参数
class DownloadTestCaseForm(Form):
    id = IntegerField(validators=[InputRequired('用例id不能为空')])

第二步:在app.py文件中增加用例下载的视图函数并映射到url/download_testcase/


import os
from datetime import datetime
from flask import Flask, request, jsonify, send_from_directory
import conf
from flask_restful import Api, Resource
from exit import db
from forms import TestCaseForm, GetTestCaseForm, DownloadTestCaseForm
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)

# 用例文件下载视图函数
class DownloadTestCaseView(Resource):
    def get(self):
        form = DownloadTestCaseForm(request.args)

        if form.validate():
            # 校验通过
            id = form.id.data # 获取请求中的id
            testcase = db.session.query(TestCaseModel).filter(TestCaseModel.id == id).first() # 根据id在数据库中查询用例数据

            if testcase is not None:
                file_path = testcase.file_path
                file_name = file_path.split('/')[-1] # 获取用例文件名

                # 返回用例文件。使用send_from_directory来返回本地的文件,第一个参数为目录,第二个参数为文件名
                # 注意:真正的项目中我们不会将文件储存在本地目录下,而是储存在git或者其他储存服务器中
                file = send_from_directory(f'./case_files',filename=file_name,as_attachment=True)
                return file
            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')
api.add_resource(DownloadTestCaseView, '/download_testcase/', endpoint='download_testcase')


if __name__ == "__main__":
    app.run(debug=True,host='127.0.0.1',port=5055)

运行结果如下:

python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第1张图片

python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第2张图片

python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第3张图片

2.用例的执行

2.1 接口分析

在这里插入图片描述

2.2 接口实现原理

该接口会调用Jenkins的api,执行Jenkins任务。在Jenkins任务中下载用例文件,并执行用例文件。

2.3 实现

(1)创建Jenkins任务,用于执行用例

python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第4张图片
python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第5张图片
python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第6张图片

在这里插入图片描述
python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第7张图片
python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第8张图片

(2)Jenkinsapi介绍

Jenkinsapi封装了Jenkins的常用操作接口,十分的方便。
官网地址:https://jenkinsapi.readthedocs.io/en/latest/index.html
安装方式:pip install jenkinsapi

(2)完成用例执行接口

第一步:生成token

要使用api去连接Jenkins,需要使用token。所以我们需要先生成一个Jenkins的token。
python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第9张图片
第二步:完成用例执行的接口功能
先修改forms.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不能为空')])

# 校验用例下载请求参数
class DownloadTestCaseForm(Form):
    id = IntegerField(validators=[InputRequired('用例id不能为空')])

# 校验用例执行请求参数
class RunTestCaseForm(Form):
    id = IntegerField(validators=[InputRequired('用例id不能为空')])

修改app.py文件,增加用例执行的视图函数


import os
from datetime import datetime
from flask import Flask, request, jsonify, send_from_directory
import conf
from flask_restful import Api, Resource
from exit import db
from forms import TestCaseForm, GetTestCaseForm, DownloadTestCaseForm, RunTestCaseForm
from models import TestCaseModel
from werkzeug.datastructures import CombinedMultiDict
from jenkinsapi.jenkins import Jenkins
from jenkinsapi import api

# 实例化一个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)

# 用例文件下载视图函数
class DownloadTestCaseView(Resource):
    def get(self):
        form = DownloadTestCaseForm(request.args)

        if form.validate():
            # 校验通过
            id = form.id.data # 获取请求中的id
            testcase = db.session.query(TestCaseModel).filter(TestCaseModel.id == id).first() # 根据id在数据库中查询用例数据

            if testcase is not None:
                file_path = testcase.file_path
                file_name = file_path.split('/')[-1] # 获取用例文件名

                # 返回用例文件。使用send_from_directory来返回本地的文件,第一个参数为目录,第二个参数为文件名
                # 注意:真正的项目中我们不会将文件储存在本地目录下,而是储存在git或者其他储存服务器中
                file = send_from_directory(f'./case_files',filename=file_name,as_attachment=True)
                return file
            else:
                res = {
     
                    'code': 404,
                    'message': '未找到对应的用例'
                }
                return jsonify(res)

        else:
            res = {
     
                'code': 404,
                'message': form.errors
            }
            return jsonify(res)

# 用例执行视图函数
class RunTestCaseView(Resource):
    def get(self):
        form = RunTestCaseForm(request.args)
        # 判断请求参数是否合法
        if form.validate():
            id = form.id.data
            test_case = db.session.query(TestCaseModel).filter(TestCaseModel.id == id).first()
            if test_case is not None:
                id = test_case.id
                file_path = test_case.file_path
                file_name = file_path.split('/')[-1]  # 获取用例文件名

                # 调用Jenkins接口,执行用例
                # 执行Jenkins任务run_testcase,其中password为生成的token
                j = Jenkins("http://192.168.1.104:8080/",
                            username="xxx",
                            password="xxxxxxxxxxxx")
                j.build_job(jobname='run_testcase',
                            params={
     "file_name": file_name, 'id': id}) #params中的参数会传递给Jenkins中,用于任务构建

                res = {
     
                    'code': 200,
                    'message': 'run 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')
api_.add_resource(GetTestCaseView, '/get_testcase/', endpoint='get_testcase')
api_.add_resource(DownloadTestCaseView, '/download_testcase/', endpoint='download_testcase')
api_.add_resource(RunTestCaseView, '/run_testcase/', endpoint='run_testcase')


if __name__ == "__main__":
    app.run(debug=True,host='127.0.0.1',port=5055)

运行结果如下:
python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第10张图片
python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第11张图片

3.用例执行状态的获取

3.1 接口分析

我们调用完用例执行接口后,还需要使用另一个接口去不断的查询任务的状态,直到执行完成后返回任务的执行结果。
在这里插入图片描述
该接口中,我们传入用例id。接口视图函数中实现通过id查询到数据库并取出该用例在Jenkins中的最后一次的构建序号。然后根据序号使用Jenkins的api去查询任务的状态及执行结果。

3.2接口实现

(1)修改用例的数据库表

我们需要在用例表中增加一个字段jenkins_last_build_number,该字段用来储存用例在Jenkins中的最后一次的构建序号。
修改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())
    jenkins_last_build_number = db.Column(db.Integer, nullable=True, unique=True)

如何重新迁移数据库数据。
第一步:删除项目的数据表初始化的迁移文件migrates目录
python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第12张图片
第二步:删除记录迁移版本的数据表alembic_version
在这里插入图片描述
第三步:依次在当前目录的终端执行命令:

python manage.py db init
python manage.py db migrate 
python manage.py db upgrade

python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第13张图片

(2)修改用例执行的接口

我们需要修改一下用例执行的接口,增加以下功能:

  • 当用户调用用例执行接口后,需要将本次的构建序号更新到用例表中的jenkins_last_build_number字段中,后序的查询执行状态接口会根据这个序号去查看任务的构建状态。

修改app.py文件中的用例执行接口RunTestCaseView


import os
from datetime import datetime
from flask import Flask, request, jsonify, send_from_directory
import conf
from flask_restful import Api, Resource
from exit import db
from forms import TestCaseForm, GetTestCaseForm, DownloadTestCaseForm, RunTestCaseForm
from models import TestCaseModel
from werkzeug.datastructures import CombinedMultiDict
from jenkinsapi.jenkins import Jenkins
from jenkinsapi import api

# 实例化一个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)

# 用例文件下载视图函数
class DownloadTestCaseView(Resource):
    def get(self):
        form = DownloadTestCaseForm(request.args)

        if form.validate():
            # 校验通过
            id = form.id.data # 获取请求中的id
            testcase = db.session.query(TestCaseModel).filter(TestCaseModel.id == id).first() # 根据id在数据库中查询用例数据

            if testcase is not None:
                file_path = testcase.file_path
                file_name = file_path.split('/')[-1] # 获取用例文件名

                # 返回用例文件。使用send_from_directory来返回本地的文件,第一个参数为目录,第二个参数为文件名
                # 注意:真正的项目中我们不会将文件储存在本地目录下,而是储存在git或者其他储存服务器中
                file = send_from_directory(f'./case_files',filename=file_name,as_attachment=True)
                return file
            else:
                res = {
     
                    'code': 404,
                    'message': '未找到对应的用例'
                }
                return jsonify(res)

        else:
            res = {
     
                'code': 404,
                'message': form.errors
            }
            return jsonify(res)

# 用例执行视图函数
class RunTestCaseView(Resource):
    def get(self):
        form = RunTestCaseForm(request.args)
        # 判断请求参数是否合法
        if form.validate():
            id = form.id.data
            test_case = db.session.query(TestCaseModel).filter(TestCaseModel.id == id).first()
            if test_case is not None:
                id = test_case.id
                file_path = test_case.file_path
                file_name = file_path.split('/')[-1]  # 获取用例文件名

                # 调用Jenkins接口
                # 取run_testcase最后一次构建对象,根据该对象可以获取任务最后一次次构建的相关信息
                old_build = api.get_latest_build(jenkinsurl='http://192.168.1.104:8080/',
                                           jobname='run_testcase',
                                           username='xxx',
                                           password='xxxxxxxxxxxxxxxxxxxxxxxxxxxx')
                old_build_number = old_build.buildno # buildno返回构建序号

                # 执行Jenkins任务run_testcase,其中password为生成的token
                j = Jenkins("http://192.168.1.104:8080/",
                            username="ouyi",
                            password="11bafbfa9f21293159f4c5fa06f70e3ed1")
                j.build_job(jobname='run_testcase',
                            params={
     "file_name": file_name, 'id': id}) #params中的参数会传递给Jenkins中,用于任务构建

                # 将最新的构建序号更新到数据库中
                test_case.jenkins_last_build_number = old_build_number + 1 # 最新一次的构建序号
                db.session.add(test_case)
                db.session.commit()

                res = {
     
                    'code': 200,
                    'message': 'run 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')
api_.add_resource(GetTestCaseView, '/get_testcase/', endpoint='get_testcase')
api_.add_resource(DownloadTestCaseView, '/download_testcase/', endpoint='download_testcase')
api_.add_resource(RunTestCaseView, '/run_testcase/', endpoint='run_testcase')


if __name__ == "__main__":
    app.run(debug=True,host='127.0.0.1',port=5055)

我们调用一下用例运行接口:
python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第14张图片
看看Jenkins中的构建序号:
python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第15张图片
看看数据库中的数据是否更新:
python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第16张图片

(3)完成用例状态查询接口

第一步:修改forms.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不能为空')])

# 校验用例下载请求参数
class DownloadTestCaseForm(Form):
   id = IntegerField(validators=[InputRequired('用例id不能为空')])

# 校验用例执行请求参数
class RunTestCaseForm(Form):
   id = IntegerField(validators=[InputRequired('用例id不能为空')])

# 校验用例状态查询请求参数
class GetTestCaseStatusForm(Form):
   id = IntegerField(validators=[InputRequired('用例id不能为空')])

第二步:修改app.py文件,增加查询用例执行状态接口GetTestCaseStatusView



import os
import time
from datetime import datetime
from flask import Flask, request, jsonify, send_from_directory
from jenkinsapi.custom_exceptions import NotFound

import conf
from flask_restful import Api, Resource
from exit import db
from forms import TestCaseForm, GetTestCaseForm, DownloadTestCaseForm, RunTestCaseForm, GetTestCaseStatusForm
from models import TestCaseModel
from werkzeug.datastructures import CombinedMultiDict
from jenkinsapi.jenkins import Jenkins
from jenkinsapi import api

# 实例化一个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)

# 用例文件下载视图函数
class DownloadTestCaseView(Resource):
    def get(self):
        form = DownloadTestCaseForm(request.args)

        if form.validate():
            # 校验通过
            id = form.id.data # 获取请求中的id
            testcase = db.session.query(TestCaseModel).filter(TestCaseModel.id == id).first() # 根据id在数据库中查询用例数据

            if testcase is not None:
                file_path = testcase.file_path
                file_name = file_path.split('/')[-1] # 获取用例文件名

                # 返回用例文件。使用send_from_directory来返回本地的文件,第一个参数为目录,第二个参数为文件名
                # 注意:真正的项目中我们不会将文件储存在本地目录下,而是储存在git或者其他储存服务器中
                file = send_from_directory(f'./case_files',filename=file_name,as_attachment=True)
                return file
            else:
                res = {
     
                    'code': 404,
                    'message': '未找到对应的用例'
                }
                return jsonify(res)

        else:
            res = {
     
                'code': 404,
                'message': form.errors
            }
            return jsonify(res)

# 用例执行视图函数
class RunTestCaseView(Resource):
    def get(self):
        form = RunTestCaseForm(request.args)
        # 判断请求参数是否合法
        if form.validate():
            id = form.id.data
            test_case = db.session.query(TestCaseModel).filter(TestCaseModel.id == id).first()
            if test_case is not None:
                id = test_case.id
                file_path = test_case.file_path
                file_name = file_path.split('/')[-1]  # 获取用例文件名

                # 调用Jenkins接口
                # 取run_testcase最后一次构建对象,根据该对象可以获取任务最后一次次构建的相关信息
                old_build = api.get_latest_build(jenkinsurl='http://192.168.1.104:8080/',
                                           jobname='run_testcase',
                                           username='xxx',
                                           password='xxxxxxxxxxxxxxxxxx')
                old_build_number = old_build.buildno # buildno返回构建序号

                # 执行Jenkins任务run_testcase,其中password为生成的token
                j = Jenkins("http://192.168.1.104:8080/",
                            username="ouyi",
                            password="11bafbfa9f21293159f4c5fa06f70e3ed1")
                j.build_job(jobname='run_testcase',
                            params={
     "file_name": file_name, 'id': id}) #params中的参数会传递给Jenkins中,用于任务构建

                # 将最新的构建序号更新到数据库中
                test_case.jenkins_last_build_number = old_build_number + 1 # 最新一次的构建序号
                db.session.add(test_case)
                db.session.commit()

                res = {
     
                    'code': 200,
                    'message': 'run success'
                }
                return jsonify(res)

            else:
                res = {
     
                    'code': 404,
                    'message': '未找到相应用例'
                }
                return jsonify(res)



        else:
            res = {
     
                'code': 404,
                'message': form.errors
            }
            return jsonify(res)


# 查询用例执行状态视图函数
class GetTestCaseStatusView(Resource):
    def get(self):
        form = GetTestCaseStatusForm(request.args)
        # 校验参数是否通过
        if form.validate():
            id = form.id.data
            test_case = db.session.query(TestCaseModel).filter(TestCaseModel.id == id).first()
            if test_case is not None:
                build_number = test_case.jenkins_last_build_number # 从数据库中取出构建序号
                time1 = time.time()  # 生成一个时间戳

                while True:
                    try:
                        # 调用Jenkins接口,查询构建状态
                        # 根据序号获取构建对象
                        new_build = api.get_build(jenkinsurl='http://192.168.1.104:8080/', 
                        						  jobname='run_testcase',
                                                  username='xxx', 
                                                  password='xxxxxxxxxxxxxxxxx',
                                                  build_no=build_number)
                        # 获取该次构建的结果,通过则为`SUCCESS`,失败为`FAILURE`,还未执行结束则为`None`
                        build_result = new_build._data.get('result')
                        if build_result is None:
                            # 如果任务还在执行
                            res = {
     
                                'code':200,
                                'status':'building',
                                'result':build_result
                            }
                            return jsonify(res)
                        else:
                            # 如果任务已执行结束
                            res = {
     
                                'code':200,
                                'status':'complete',
                                'result':build_result
                            }
                            return jsonify(res)
                    except NotFound as e:
                        # 调用执行用例接口到Jenkins开始执行任务会有时间差。这个时间段内会出现根据序号找不到构建的异常NotFound。
                        # 需要把NotFound异常接受,不能让其中断程序的运行
                        # 还需要设置一个超时时间,如果超过这个时间仍未找到对应构建,则抛出异常
                        time2 = time.time()
                        if (time2 - time1) < 20:
                            pass
                        else:
                            res = {
     
                                'code': 404,
                                'message': '未查询到相关任务的状态'
                            }
                            return 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')
api_.add_resource(DownloadTestCaseView, '/download_testcase/', endpoint='download_testcase')
api_.add_resource(RunTestCaseView, '/run_testcase/', endpoint='run_testcase')
api_.add_resource(GetTestCaseStatusView, '/get_testcase_status/', endpoint='get_testcase_status')

if __name__ == "__main__":
    app.run(debug=True,host='127.0.0.1',port=5055)

这个查询用例执行状态的接口一般不是用户触发的,而是用户执行用例后,前端自动触发且不断的调用该查询用例执行状态的接口,直到用例执行完成。因为我们现在还未完成前端的工作,所以我们使用python中的requests模块来模拟这种场景。

编写testapi.py文件如下:


import subprocess
import time

import requests

# 执行id为16的用例 预期为执行通过
requests.get('http://127.0.0.1:5055/run_testcase/?id=16') # 调用执行用例接口
time.sleep(2)
while True:
    res = requests.get('http://127.0.0.1:5055/get_testcase_status/?id=16') # 调用查询用例状态接口
    status = res.json().get('status')
    if status == 'complete': # 如果用例已执行结束则退出循环
        print(res.json())
        break
    else:
        print(res.json())
        time.sleep(5)  # 5秒调用一次查询状态接口


# 执行id为17的用例 预期为执行不通过
requests.get('http://127.0.0.1:5055/run_testcase/?id=17') # 调用执行用例接口
time.sleep(2)
while True:
    res = requests.get('http://127.0.0.1:5055/get_testcase_status/?id=17') # 调用查询用例状态接口
    status = res.json().get('status')
    if status == 'complete': # 如果用例已执行结束则退出循环
        print(res.json())
        break
    else:
        print(res.json())
        time.sleep(5)  # 5秒调用一次查询状态接口

运行结果如下:

{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': 'SUCCESS', 'status': 'complete'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': None, 'status': 'building'}
{'code': 200, 'result': 'FAILURE', 'status': 'complete'}

从查询状态结果的返回可以看出,第一条用例执行通过,第二天用例执行失败。

再看看Jenkins中的构建结果是否与我们查询到的一致。
python自动化(七)自动化测试平台开发:5.后端开发之用例文件下载,执行用例,查询用例执行状态_第17张图片

你可能感兴趣的:(python自动化,python,flask,软件测试)