这篇文章适用于使用过Vue和Django的同学,文章介绍如何搭建vue-element-admin框架,通过实例介绍,如何从vue-element-admin挑选适用于自己项目的页面,这样测试开发人员可以更加集中于测试需求的开发,文章最后介绍了处理队列的python库celery,用于异步执行任务。它将任务添加到Celery队列中,使应用程序可以继续执行其他操作,而不必等待任务完成。这个方法通常用于处理比较耗时的任务,以避免对应用程序的性能造成影响。
Vue-Element-Admin是一个基于Vue和Element-UI实现的后台管理系统快速开发框架。该框架借助了Element-UI组件库以及Vue的数据驱动视图特性,极大地简化了后台管理系统的开发过程。
Vue-Element-Admin框架提供了大量的实现后台管理系统所需的通用组件和功能,例如路由管理、用户管理、权限管理等。同时,该框架还提供了多个主题风格、国际化以及Chrome调试工具等实用特性,为后台管理系统的开发提供了更多的扩展和自定义选项。
Vue-Element-Admin框架使用Webpack打包工具进行构建,并对Webpack的构建方式进行了优化,同时该框架还支持ES6、ES7的新特性,并提供了一系列的代码检查和格式化工具,以保证代码的质量和规范性。
GitHub地址:https://github.com/PanJiaChen/vue-element-admin
项目在线预览:https://panjiachen.gitee.io/vue-element-admin
与真实环境使用Nginx相比,更建议将打包好的文件,放到DJango的Template目录下,方便调试
使用npm run build:prod
开始build包
打包后的文件放到Django项目目录下重命名为frontend
修改settings中TEMPLATES和STATICFILES_DIRS
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
## 指向frontend
'DIRS': [os.path.join(BASE_DIR, 'frontend')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
STATICFILES_DIRS = [
## 指向frontend/static
os.path.join(BASE_DIR, "frontend/static")
]
jenkins流水线构建是自动化测试的一个重要环节,如果自动化没有实现流水线,这个自动化不能算得上成功,但是很多企业jenkins没有对外开放,只有少数人有访问的权限,而且jenkins可能部署在多个服务器,没有办法将需要使用的job汇总在一起展示,需要登陆多个平台。同时jenkins显示自动化每个节点信息都很友好。鉴于需要使用python语言,所以最后确定利用vue实现前端界面,利用django实现后台服务,Python调用jenkins job 实现自动测试任务调度的方案。
首先确保本地搭建了jenkins服务,保证后端服务可以调通
nohup java -jar jenkins.war --httpPort=8080 &
开发过程中可以前后端同时进行最后集成在一起,本例先开发后端服务,保证需求可以实现
pip install python-jenkins
import time
import requests
import jenkins
class JenkinsClient(object):
def __init__(self):
self.jenkins_config = {
"url": "http://120.25.253.110:8080/",
"username": "*****1688",
"password": "******5588"
}
self.jenkins_server = jenkins.Jenkins(**self.jenkins_config)
def get_all_jobs(self):
'''
获取所有JOBS
'''
return self.jenkins_server.get_all_jobs()
def get_job_info(self, job_name):
return self.jenkins_server.get_job_info(job_name)
def get_all_jobs_info(self):
'''
获取所有JOB信息
'''
jobs_list = []
jobs = self.get_all_jobs()
for job in jobs:
jobs_dict = {}
job_name = job.get('name')
number = self.get_lastbuild_number(job_name)
url, timestamp, result = self.get_build_job_info(job_name, number)
test_total, tests_skip, tests_faild = self.get_job_testcase(job.get('url'), number)
jobs_dict['build_job'] = job_name
jobs_dict['build_url'] = url
jobs_dict['build_time'] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp / 1000))
jobs_dict['build_number'] = number
jobs_dict['build_result'] = result
jobs_dict['testcase_total'] = test_total
jobs_dict['testcase_skips'] = tests_skip
jobs_dict['testcase_failure'] = tests_faild
print(jobs_dict)
jobs_list.append(jobs_dict)
return jobs_list
3.1 开发数据模型
class JenkinsJobModel(models.Model):
objects = models.Manager()
build_job = models.CharField('任务名称', max_length=255)
build_number = models.IntegerField('构建编号', default=0)
build_time = models.DateTimeField('构建时间')
build_url = models.URLField('构建URL', max_length=500)
build_result = models.CharField('构建结果', max_length=50)
testcase_total = models.IntegerField('用例总数', default=0)
testcase_skips = models.IntegerField('用例跳过数', default=0)
testcase_failure = models.IntegerField('用例失败数', default=0)
created_at = models.DateTimeField('创建时间', default=datetime.datetime.now)
updated = models.DateTimeField('更新时间', auto_now=True)
def __str__(self):
return f'{self.build_job}-{self.build_number}'
3.2 同步数据库,python manage.py makemigrations
和python manage.py migrate
3.3 编写serializers代码
class JenkinsJobModelSerializer(serializers.ModelSerializer):
class Meta:
model = JenkinsJobModel
exclude = [
'created_at'
]
3.4 编写视图代码
client = JenkinsClient()
@api_view(['GET'])
def get_all_jobs(request):
'''
获取所有任务,首次手工将JENKINS同步到测试管理平台
'''
search = request.GET.get('title')
querysets = JenkinsJobModel.objects.all()
if not querysets:
print("表中无记录,则重新调用Jenkins API同步到Django")
jobs = client.get_all_jobs_info()
for job in jobs:
JenkinsJobModel.objects.get_or_create(**job)
else:
print(f"总共同步创建{JenkinsJobModel.objects.count()}条JOB记录!")
querysets = JenkinsJobModel.objects.all()
elif search:
querysets = JenkinsJobModel.objects.filter(Q(build_job__icontains=search))
print(f"按条件搜索匹配{querysets.count()}条记录!")
else:
querysets = JenkinsJobModel.objects.all()
serializer = JenkinsJobModelSerializer(querysets, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
npm run dev
启动vue-element-admin服务,并浏览页面找到合适自己使用的页面,如下是我想要的页面,对模块不熟悉可以借助官方文档https://panjiachen.github.io/vue-element-admin-site/zh/guidetableRouter
,然后定位到@/views/table/complex-table <el-table
:key="tableKey"
v-loading="listLoading"
:data="joblist"
border
fit
highlight-current-row
style="width: 100%;"
@sort-change="sortChange"
>
<el-table-column label="序号" prop="id" sortable="custom" align="center" width="50" :class-name="getSortClass('id')">
<template slot-scope="scope">
<span>{{ scope.row.id }}</span>
</template>
</el-table-column>
<el-table-column label="任务名称" align="center">
<template slot-scope="scope">
<span class="link-type" @click="handleUpdate(scope.row)">{{ scope.row.build_job }}</span>
</template>
</el-table-column>
<el-table-column label="构建编号" align="center" width="100">
<template slot-scope="scope">
<span>{{ scope.row.build_number }}</span>
</template>
</el-table-column>
<el-table-column label="构建时间" width="170px" align="center">
<template slot-scope="scope">
<span>{{ scope.row.build_time}}</span>
</template>
</el-table-column>
<el-table-column label="构建结果" width="110px" align="center">
<template slot-scope="scope">
<span>{{ scope.row.build_result }}</span>
</template>
</el-table-column>
<el-table-column label="用例总数" width="110px" align="center">
<template slot-scope="scope">
<span v-if="scope.row.testcase_total>0" style="color:red;">{{ scope.row.testcase_total }}</span>
<span v-else>{{ scope.row.testcase_total }}</span>
</template>
</el-table-column>
<el-table-column label="用例跳过数" width="110px" align="center">
<template slot-scope="scope">
<span v-if="scope.row.testcase_skips>0" style="color:red;">{{ scope.row.testcase_skips }}</span>
<span v-else>{{ scope.row.testcase_skips }}</span>
</template>
</el-table-column>
<el-table-column label="用例失败数" width="110px" align="center">
<template slot-scope="scope">
<span v-if="scope.row.testcase_failure>0" style="color:red;">{{ scope.row.testcase_failure }}</span>
<span v-else>{{ scope.row.testcase_failure }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" min-width="100" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="success" size="mini">
<a :href="scope.row.build_url" target="_blank" style="text-decoration: none">
跳转
</a>
</el-button>
<el-button type="primary" size="mini" @click="handleBuild(scope.row.build_job)">
构建
</el-button>
<el-button type="info" size="mini" @click="handleUpdate(scope.row)">
编辑
</el-button>
<el-button type="danger" size="mini" @click="handleUpdate(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
npm run build
到最后部署的时候,可以使用nginx整合前后端,监听前端接口转发给后端服务,也可以将dist的目录直接配置到django的静态文件夹下,注意在setting文件中指定静态文件的路径,在路由中可以 path('', TemplateView.as_view(template_name="index.html"))
直接将vue的index也页面作为主页面和配置re_path(r'^static/(?P.*)$', static.serve, {'document_root': settings.STATIC_ROOT})
全局提供静态资源服务urlpatterns = [
path('admin/', admin.site.urls),
path('autotest/', include("autotest.urls")),
path('', TemplateView.as_view(template_name="index.html")), ## 这里将url的根路径指向vue中的index页面
path('api-auth/', include('rest_framework.urls')),
re_path(r'^static/(?P.*)$' , static.serve,
{'document_root': settings.STATIC_ROOT}, name='static'),
]
以购买机票为例,通常来说,当我们购票时,机票系统需要与航空公司的服务器进行交互,查询座位信息、价格等,因为交互过程可能比较耗时,因此一般是通过异步方式来实现。
具体来说,在购买机票时,我们通常会提交一张订单,然后等待系统返回确认信息。在这个过程中,我们可能会看到一个“正在处理中”的提示,这实际上是机票系统在与航空公司的服务器交互、获得确认信息的时候所调用的异步接口。
异步接口工作的原理是在发起请求后并不立即返回结果,而是在后台继续执行其他任务(如查询其他座位信息),然后再把结果返回,这样就能避免阻塞主线程。
因此,异步的实现能够提高机票系统的处理能力和效率,并且增加用户体验,使用户可以更快速地购买机票。
Celery是一个Python开发的分布式任务队列,它可以处理大量任务并且具有较高的可靠性。它本质上是一个任务调度器,将任务发送到工作队列中,由一组工人异步地处理这些任务。最常见的使用方法是将它与Django、Flask、Bottle等Web框架结合使用,Celery可以实现异步处理任务、延迟任务执行等功能。
Celery的工作原理基于三级模型,即任务发布者、消息中间件和任务执行者。在这个模型中,任务发布者将任务传递给消息中间件(如RabbitMQ,Redis等),然后任务执行者从中间件中获取到消息,并执行其中的任务。
Celery具有许多应用场景,如:
Celery是一个开源的分布式任务队列,其由以下组件构成:
Celery的原理如下:
这个过程中,消息代理充当了任务分配器和数据传输媒介的角色。 任务可以是任何Python可调用函数或类方法,并且可以在任何数量的节点上并行运行以实现扩展性。
pip install celery
pip install eventlet (windows必装)
pip install redis
pip install flower
Celery windows环境为什么用到eventlet?
在 Windows 环境下,Celery 默认使用了 eventlet 作为任务执行的协程库,是因为 Windows 平台下没有像 Linux 平台上的 gevent 这样的高效协程库。
Eventlet 是一种 Python 协程库,它提供了简单易用的 API,使得编写异步代码变得简单。Celery 使用 eventlet 作为后台执行器来利用 Windows 上的并发优势,可以提高同步函数和方法的执行速度,从而避免阻塞现象产生,提高 Celery 的任务执行效率。
以下是使用Celery+Redis的Python代码中实例化Celery对象的示例:
from celery import Celery
import time
app = Celery('tasks', broker='redis://127.0.0.1:6379/0',
backend='redis://127.0.0.1:6379/1')
@app.task
def send_email(name):
print("向%s发送邮件..." % name)
time.sleep(5)
print("向%s发送邮件完成" % name)
return "ok"
@app.task
def send_msg(name):
print("向%s发送短信..." % name)
time.sleep(5)
print("向%s发送短信完成" % name)
return "ok"
from celery_task import send_email, send_msg
# celery_task 是创建的异步执行文件
if __name__ == '__main__':
for i in range(1, 10):
result1 = send_email.delay("张三")
print(result1.id)
result2 = send_email.delay("李四")
print(result2.id)
result3 = send_msg.delay("王五")
print(result3.id)
result4 = send_msg.delay("赵六")
print(result4.id)
在这里,我们先使用 Celery()
函数创建一个 app
实例,其中将 Celery 实例命名为 tasks
。链接到指定的Redis消息代理和结果存储后端。
在这里使用了 Redis
作为我们的消息代理和结果后端,使用地址 redis://localhost:6379/0
,其中 0
表示 redis 中第一个数据库,如果你需要更多数据这点可以根据自己的需求进行设置。之后我们用 @app.task
来定义任务。
然后测试代码中启动队列运行
启动celery调度,确保启动的时候可以找到celery_task,可以切换到对应的目录下启动
celery -A celery_task worker -l info -P eventlet
启动flow服务进行监控,监控的数据库需要和celery_task代码中的保持一致,可以监听到对应的数据
celery --broker=redis://127.0.0.1:6379/0 flower --address=0.0.0.0 --prot=5555
在Django项目的settings.py
文件中添加以下配置,其中myproject为项目名:
CELERY_BROKER_URL = 'redis://127.0.0.1:6379/0' # Redis作为消息中间件
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/1' # Redis作为任务结果存储
CELERY_TIMEZONE = "Asia/Shanghai" # 指定时区,不指定默认为 'UTC'
在Django项目的根目录下,创建一个名为celery.py
的文件,添加以下代码:
import os
import django
from celery import Celery
from myproject import settings
# 设置环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
django.setup()
# 创建Celery实例对象
app = Celery('myproject')
# 指定中间人
app.config_from_object('django.conf:settings', namespace='CELERY')
# 自动注册任务
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
__all__ = ['app']
在Django项目的某个app中创建tasks.py
文件,一定是该文件,符合celery的编码规范,并编写任务代码:
from celery import shared_task
@shared_task
def add(x, y):
return x + y
from django.http import HttpResponse
from .tasks import *
def task_add_view(request):
add.delay(100, 200)
return HttpResponse(f'调用函数结果')
使用以下命令来启动Celery服务:
celery -A django项目名 worker -l info -P eventlet
其中celery_client
为Celery实例所在的模块,worker
表示启动一个工作进程,`-l表示日志等级。
如果调度策略要改成定时任务,则可以在settings指定,添加如下代码
CELERY_BEAT_SCHEDULE = {
'add-every-30-seconds': { # 任务名字,随意
'task': 'api.tasks.add', # celery任务,这里一定是完整的路径,否则报错
'schedule': 30.0, # 定时时间
'args': (16, 16) # 需要传递到 task 的参数
},
}
并启动beat服务
celery -A 项目名 beat -l info