Django接口自动化平台实现(四)
5. 用例集合
预期效果如下:
5.1 定义模型类
1)models.py 中新增 case_suite 模型类:
1 from django.db import models2 from smart_selects.db_fields import GroupedForeignKey # pip install django-smart-selects:后台级联选择3 from django.contrib.auth.models import User4 5 6 class Project(models.Model):7 id = models.AutoField(primary_key=True)8 name = models.CharField('项目名称', max_length=50, unique=True, null=False)9 proj_owner = models.CharField('项目负责人', max_length=20, null=False) 10 test_owner = models.CharField('测试负责人', max_length=20, null=False) 11 dev_owner = models.CharField('开发负责人', max_length=20, null=False) 12 desc = models.CharField('项目描述', max_length=100, null=True) 13 create_time = models.DateTimeField('项目创建时间', auto_now_add=True) 14 update_time = models.DateTimeField('项目更新时间', auto_now=True, null=True) 15 16 def __str__(self): 17 return self.name 18 19 class Meta: 20 verbose_name = '项目信息表' 21 verbose_name_plural = '项目信息表' 22 23 24 class Module(models.Model): 25 id = models.AutoField(primary_key=True) 26 name = models.CharField('模块名称', max_length=50, null=False) 27 belong_project = models.ForeignKey(Project, on_delete=models.CASCADE) 28 test_owner = models.CharField('测试负责人', max_length=50, null=False) 29 desc = models.CharField('简要描述', max_length=100, null=True) 30 create_time = models.DateTimeField('创建时间', auto_now_add=True) 31 update_time = models.DateTimeField('更新时间', auto_now=True, null=True) 32 33 def __str__(self): 34 return self.name 35 36 class Meta: 37 verbose_name = '模块信息表' 38 verbose_name_plural = '模块信息表' 39 40 41 class TestCase(models.Model): 42 id = models.AutoField(primary_key=True) 43 case_name = models.CharField('用例名称', max_length=50, null=False) # 如 register 44 belong_project = models.ForeignKey(Project, on_delete=models.CASCADE, verbose_name='所属项目') 45 belong_module = GroupedForeignKey(Module, "belong_project", on_delete=models.CASCADE, verbose_name='所属模块') 46 request_data = models.CharField('请求数据', max_length=1024, null=False, default='') 47 uri = models.CharField('接口地址', max_length=1024, null=False, default='') 48 assert_key = models.CharField('断言内容', max_length=1024, null=True) 49 maintainer = models.CharField('编写人员', max_length=1024, null=False, default='') 50 extract_var = models.CharField('提取变量表达式', max_length=1024, null=True) # 示例:userid||userid": (\d+) 51 request_method = models.CharField('请求方式', max_length=1024, null=True) 52 status = models.IntegerField(null=True, help_text="0:表示有效,1:表示无效,用于软删除") 53 created_time = models.DateTimeField('创建时间', auto_now_add=True) 54 updated_time = models.DateTimeField('更新时间', auto_now=True, null=True) 55 user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='责任人', null=True) 56 57 def __str__(self): 58 return self.case_name 59 60 class Meta: 61 verbose_name = '测试用例表' 62 verbose_name_plural = '测试用例表' 63 64 65 class CaseSuite(models.Model): 66 id = models.AutoField(primary_key=True) 67 suite_desc = models.CharField('用例集合描述', max_length=100, blank=True, null=True) 68 if_execute = models.IntegerField(verbose_name='是否执行', null=False, default=0, help_text='0:执行;1:不执行') 69 test_case_model = models.CharField('测试执行模式', max_length=100, blank=True, null=True, help_text='data/keyword') 70 creator = models.CharField(max_length=50, blank=True, null=True) 71 create_time = models.DateTimeField('创建时间', auto_now=True) # 创建时间-自动获取当前时间 72 73 class Meta: 74 verbose_name = '用例集合表' 75 verbose_name_plural = '用例集合表'
2)数据迁移
在项目目录下,执行以下两个命令进行数据迁移(将模型类转换成数据库表):
python manage.py makemigrations # 生成迁移文件(模型类的信息) python manage.py migrate # 执行开始迁移(将模型类信息转换成数据库表)
5.2 后台 admin 添加数据
1)注册模型类到 admin.py:
1 from django.contrib import admin2 from .import models3 4 5 class ProjectAdmin(admin.ModelAdmin):6 list_display = ("id", "name", "proj_owner", "test_owner", "dev_owner", "desc", "create_time", "update_time")7 8 admin.site.register(models.Project, ProjectAdmin)9 10 11 class ModuleAdmin(admin.ModelAdmin): 12 list_display = ("id", "name", "belong_project", "test_owner", "desc", "create_time", "update_time") 13 14 admin.site.register(models.Module, ModuleAdmin) 15 16 17 class TestCaseAdmin(admin.ModelAdmin): 18 list_display = ( 19 "id", "case_name", "belong_project", "belong_module", "request_data", "uri", "assert_key", "maintainer", 20 "extract_var", "request_method", "status", "created_time", "updated_time", "user") 21 22 admin.site.register(models.TestCase, TestCaseAdmin) 23 24 25 class CaseSuiteAdmin(admin.ModelAdmin): 26 list_display = ("id", "suite_desc", "creator", "create_time") 27 28 admin.site.register(models.CaseSuite, CaseSuiteAdmin)
2)登录 admin 系统,进入用例集合表,添加数据:
5.3 定义路由
from django.urls import path, re_path from . import viewsurlpatterns = [path('', views.index),path('login/', views.login),path('logout/', views.logout),path('project/', views.project, name='project'),path('module/', views.module, name='module'),path('test_case/', views.test_case, name="test_case"),re_path('test_case_detail/(?P<test_case_id>[0-9]+)', views.test_case_detail, name="test_case_detail"),re_path('module_test_cases/(?P<module_id>[0-9]+)/$', views.module_test_cases, name="module_test_cases"),path('test_suite/', views.test_suite, name="test_suite"), ]
5.4 定义视图
1 from django.shortcuts import render, redirect, HttpResponse2 from django.contrib import auth # Django用户认证(Auth)组件一般用在用户的登录注册上,用于判断当前的用户是否合法3 from django.contrib.auth.decorators import login_required4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage5 from .form import UserForm6 import traceback7 from .models import Project, Module, TestCase, CaseSuite8 9 10 # 封装分页处理11 def get_paginator(request, data):12 paginator = Paginator(data, 10) # 默认每页展示10条数据13 # 获取 url 后面的 page 参数的值, 首页不显示 page 参数, 默认值是 114 page = request.GET.get('page')15 try:16 paginator_pages = paginator.page(page)17 except PageNotAnInteger:18 # 如果请求的页数不是整数, 返回第一页。19 paginator_pages = paginator.page(1)20 except InvalidPage:21 # 如果请求的页数不存在, 重定向页面22 return HttpResponse('找不到页面的内容')23 return paginator_pages24 25 26 # 项目菜单27 @login_required28 def project(request):29 print("request.user.is_authenticated: ", request.user.is_authenticated)30 projects = Project.objects.filter().order_by('-id')31 print("projects:", projects)32 return render(request, 'project.html', {'projects': get_paginator(request, projects)})33 34 35 # 模块菜单36 @login_required37 def module(request):38 if request.method == "GET": # 请求get时候,id倒序查询所有的模块数据39 modules = Module.objects.filter().order_by('-id')40 return render(request, 'module.html', {'modules': get_paginator(request, modules)})41 else: # 否则就是Post请求,会根据输入内容,使用模糊的方式查找所有的项目42 proj_name = request.POST['proj_name']43 projects = Project.objects.filter(name__contains=proj_name.strip())44 projs = [proj.id for proj in projects]45 modules = Module.objects.filter(belong_project__in=projs) # 把项目中所有的模块都找出来46 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})47 48 49 # 测试用例菜单50 @login_required51 def test_case(request):52 print("request.session['is_login']: {}".format(request.session['is_login']))53 test_cases = ""54 if request.method == "GET":55 test_cases = TestCase.objects.filter().order_by('id')56 print("testcases in testcase: {}".format(test_cases))57 elif request.method == "POST":58 print("request.POST: {}".format(request.POST))59 test_case_id_list = request.POST.getlist('testcases_list')60 if test_case_id_list:61 test_case_id_list.sort()62 print("test_case_id_list: {}".format(test_case_id_list))63 test_cases = TestCase.objects.filter().order_by('id')64 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})65 66 67 # 用例详情页68 @login_required69 def test_case_detail(request, test_case_id):70 test_case_id = int(test_case_id)71 test_case = TestCase.objects.get(id=test_case_id)72 print("test_case: {}".format(test_case))73 print("test_case.id: {}".format(test_case.id))74 print("test_case.belong_project: {}".format(test_case.belong_project))75 76 return render(request, 'test_case_detail.html', {'test_case': test_case})77 78 79 # 模块页展示测试用例80 @login_required81 def module_test_cases(request, module_id):82 module = ""83 if module_id: # 访问的时候,会从url中提取模块的id,根据模块id查询到模块数据,在模板中展现84 module = Module.objects.get(id=int(module_id))85 test_cases = TestCase.objects.filter(belong_module=module).order_by('-id')86 print("test_case in module_test_cases: {}".format(test_cases))87 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})88 89 90 @login_required91 def case_suite(request):92 case_suites = CaseSuite.objects.filter()93 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})94 95 96 # 默认页的视图函数97 @login_required98 def index(request):99 return render(request, 'index.html') 100 101 102 # 登录页的视图函数 103 def login(request): 104 print("request.session.items(): {}".format(request.session.items())) # 打印session信息 105 if request.session.get('is_login', None): 106 return redirect('/') 107 # 如果是表单提交行为,则进行登录校验 108 if request.method == "POST": 109 login_form = UserForm(request.POST) 110 message = "请检查填写的内容!" 111 if login_form.is_valid(): 112 username = login_form.cleaned_data['username'] 113 password = login_form.cleaned_data['password'] 114 try: 115 # 使用django提供的身份验证功能 116 user = auth.authenticate(username=username, password=password) # 从auth_user表中匹配信息,匹配到则返回用户对象 117 if user is not None: 118 print("用户【%s】登录成功" % username) 119 auth.login(request, user) 120 request.session['is_login'] = True 121 # 登录成功,跳转主页 122 return redirect('/') 123 else: 124 message = "用户名不存在或者密码不正确!" 125 except: 126 traceback.print_exc() 127 message = "登录程序出现异常" 128 # 用户名或密码为空,返回登录页和错误提示信息 129 else: 130 return render(request, 'login.html', locals()) 131 # 不是表单提交,代表只是访问登录页 132 else: 133 login_form = UserForm() 134 return render(request, 'login.html', locals()) 135 136 137 # 注册页的视图函数 138 def register(request): 139 return render(request, 'register.html') 140 141 142 # 登出的视图函数:重定向至login视图函数 143 @login_required 144 def logout(request): 145 auth.logout(request) 146 request.session.flush() 147 return redirect("/login/")
5.5 定义模板
新增 templates/case_suite.html 模板:
1 {% extends 'base.html' %}2 {% load static %}3 {% block title %}测试集合{% endblock %}4 {% block content %}5 <form action="" method="POST">6 {% csrf_token %}7 8 <div class="table-responsive">9 <table class="table table-striped"> 10 <thead> 11 <tr> 12 <th>id</th> 13 <th>测试集合名称</th> 14 <th>创建者</th> 15 <th>创建时间</th> 16 <th>查看/删除测试用例</th> 17 <th>添加测试用例</th> 18 <th>用例集合执行结果</th> 19 </tr> 20 </thead> 21 <tbody> 22 23 {% for case_suite in case_suites %} 24 <tr> 25 <td>{{ case_suite.id }}</td> 26 <td>{{ case_suite.suite_desc }}</td> 27 <td>{{ case_suite.creator }}</td> 28 <td>{{ case_suite.create_time|date:"Y-n-d H:i" }}</td> 29 <td><a href="">查看/删除测试用例</a></td> 30 <td><a href="">添加测试用例</a></td> 31 <td><a href="">查看用例集合执行结果</a></td> 32 </tr> 33 {% endfor %} 34 </tbody> 35 </table> 36 </div> 37 </form> 38 39 {# 实现分页标签的代码 #} 40 {# 这里使用 bootstrap 渲染页面 #} 41 <div id="pages" class="text-center"> 42 <nav> 43 <ul class="pagination"> 44 <li class="step-links"> 45 {% if case_suites.has_previous %} 46 <a class='active' href="?page={{ case_suites.previous_page_number }}">上一页</a> 47 {% endif %} 48 49 <span class="current"> 50 第 {{ case_suites.number }} 页/共 {{ case_suites.paginator.num_pages }} 页</span> 51 52 {% if case_suites.has_next %} 53 <a class='active' href="?page={{ case_suites.next_page_number }}">下一页</a> 54 {% endif %} 55 </li> 56 </ul> 57 </nav> 58 </div> 59 {% endblock %}
2)修改 base 模板:菜单栏新增“用例集合”。
1 <!DOCTYPE html>2 <html lang="zh-CN">3 {% load static %}4 <head>5 <meta charset="utf-8">6 <meta http-equiv="X-UA-Compatible" content="IE=edge">7 <meta name="viewport" content="width=device-width, initial-scale=1">8 <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->9 <title>{% block title %}base{% endblock %}</title> 10 11 <!-- Bootstrap --> 12 <link href="{% static 'bootstrap/css/bootstrap.min.css' %}" rel="stylesheet"> 13 14 15 <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> 16 <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> 17 <!--[if lt IE 9]> 18 <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script> 19 <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script> 20 <![endif]--> 21 {% block css %}{% endblock %} 22 </head> 23 <body> 24 <nav class="navbar navbar-default"> 25 <div class="container-fluid"> 26 <!-- Brand and toggle get grouped for better mobile display --> 27 <div class="navbar-header"> 28 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#my-nav" 29 aria-expanded="false"> 30 <span class="sr-only">切换导航条</span> 31 <span class="icon-bar"></span> 32 <span class="icon-bar"></span> 33 <span class="icon-bar"></span> 34 </button> 35 <a class="navbar-brand" href="/">自动化测试平台</a> 36 </div> 37 38 <div class="collapse navbar-collapse" id="my-nav"> 39 <ul class="nav navbar-nav"> 40 <li class="active"><a href="/project/">项目</a></li> 41 <li class="active"><a href="/module/">模块</a></li> 42 <li class="active"><a href="/test_case/">测试用例</a></li> 43 <li class="active"><a href="/case_suite/">用例集合</a></li> 44 </ul> 45 <ul class="nav navbar-nav navbar-right"> 46 {% if request.user.is_authenticated %} 47 <li><a href="#">当前在线:{{ request.user.username }}</a></li> 48 <li><a href="/logout">登出</a></li> 49 {% else %} 50 <li><a href="/login">登录</a></li> 51 52 {% endif %} 53 </ul> 54 </div><!-- /.navbar-collapse --> 55 </div><!-- /.container-fluid --> 56 </nav> 57 58 {% block content %}{% endblock %} 59 60 61 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> 62 <script src="{% static 'js/jquery-3.4.1.js' %}"></script> 63 <!-- Include all compiled plugins (below), or include individual files as needed --> 64 <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script> 65 </body> 66 </html>
6. 用例集合添加测试用例
预期效果如下:
6.1 定义模型类
1)在 models.py 中,增加模型类 SuiteCase,记录用例集合所关联的用例。
1 from django.db import models2 from smart_selects.db_fields import GroupedForeignKey # pip install django-smart-selects:后台级联选择3 from django.contrib.auth.models import User4 5 6 class Project(models.Model):7 id = models.AutoField(primary_key=True)8 name = models.CharField('项目名称', max_length=50, unique=True, null=False)9 proj_owner = models.CharField('项目负责人', max_length=20, null=False) 10 test_owner = models.CharField('测试负责人', max_length=20, null=False) 11 dev_owner = models.CharField('开发负责人', max_length=20, null=False) 12 desc = models.CharField('项目描述', max_length=100, null=True) 13 create_time = models.DateTimeField('项目创建时间', auto_now_add=True) 14 update_time = models.DateTimeField('项目更新时间', auto_now=True, null=True) 15 16 def __str__(self): 17 return self.name 18 19 class Meta: 20 verbose_name = '项目信息表' 21 verbose_name_plural = '项目信息表' 22 23 24 class Module(models.Model): 25 id = models.AutoField(primary_key=True) 26 name = models.CharField('模块名称', max_length=50, null=False) 27 belong_project = models.ForeignKey(Project, on_delete=models.CASCADE) 28 test_owner = models.CharField('测试负责人', max_length=50, null=False) 29 desc = models.CharField('简要描述', max_length=100, null=True) 30 create_time = models.DateTimeField('创建时间', auto_now_add=True) 31 update_time = models.DateTimeField('更新时间', auto_now=True, null=True) 32 33 def __str__(self): 34 return self.name 35 36 class Meta: 37 verbose_name = '模块信息表' 38 verbose_name_plural = '模块信息表' 39 40 41 class TestCase(models.Model): 42 id = models.AutoField(primary_key=True) 43 case_name = models.CharField('用例名称', max_length=50, null=False) # 如 register 44 belong_project = models.ForeignKey(Project, on_delete=models.CASCADE, verbose_name='所属项目') 45 belong_module = GroupedForeignKey(Module, "belong_project", on_delete=models.CASCADE, verbose_name='所属模块') 46 request_data = models.CharField('请求数据', max_length=1024, null=False, default='') 47 uri = models.CharField('接口地址', max_length=1024, null=False, default='') 48 assert_key = models.CharField('断言内容', max_length=1024, null=True) 49 maintainer = models.CharField('编写人员', max_length=1024, null=False, default='') 50 extract_var = models.CharField('提取变量表达式', max_length=1024, null=True) # 示例:userid||userid": (\d+) 51 request_method = models.CharField('请求方式', max_length=1024, null=True) 52 status = models.IntegerField(null=True, help_text="0:表示有效,1:表示无效,用于软删除") 53 created_time = models.DateTimeField('创建时间', auto_now_add=True) 54 updated_time = models.DateTimeField('更新时间', auto_now=True, null=True) 55 user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='责任人', null=True) 56 57 def __str__(self): 58 return self.case_name 59 60 class Meta: 61 verbose_name = '测试用例表' 62 verbose_name_plural = '测试用例表' 63 64 65 class CaseSuite(models.Model): 66 id = models.AutoField(primary_key=True) 67 suite_desc = models.CharField('用例集合描述', max_length=100, blank=True, null=True) 68 if_execute = models.IntegerField(verbose_name='是否执行', null=False, default=0, help_text='0:执行;1:不执行') 69 test_case_model = models.CharField('测试执行模式', max_length=100, blank=True, null=True, help_text='data/keyword') 70 creator = models.CharField(max_length=50, blank=True, null=True) 71 create_time = models.DateTimeField('创建时间', auto_now=True) # 创建时间-自动获取当前时间 72 73 class Meta: 74 verbose_name = "用例集合表" 75 verbose_name_plural = '用例集合表' 76 77 78 class SuiteCase(models.Model): 79 id = models.AutoField(primary_key=True) 80 case_suite = models.ForeignKey(CaseSuite, on_delete=models.CASCADE, verbose_name='用例集合') 81 test_case = models.ForeignKey(TestCase, on_delete=models.CASCADE, verbose_name='测试用例') 82 status = models.IntegerField(verbose_name='是否有效', null=False, default=1, help_text='0:有效,1:无效') 83 create_time = models.DateTimeField('创建时间', auto_now=True) # 创建时间-自动获取当前时间
2)数据迁移
在项目目录下,执行以下两个命令进行数据迁移(将模型类转换成数据库表):
python manage.py makemigrations # 生成迁移文件(模型类的信息) python manage.py migrate # 执行开始迁移(将模型类信息转换成数据库表)
6.2 定义路由
from django.urls import path, re_path from . import viewsurlpatterns = [path('', views.index),path('login/', views.login),path('logout/', views.logout),path('project/', views.project, name='project'),path('module/', views.module, name='module'),path('test_case/', views.test_case, name="test_case"),re_path('test_case_detail/(?P<test_case_id>[0-9]+)', views.test_case_detail, name="test_case_detail"),re_path('module_test_cases/(?P<module_id>[0-9]+)/$', views.module_test_cases, name="module_test_cases"),path('case_suite/', views.case_suite, name="case_suite"),re_path('add_case_in_suite/(?P<suite_id>[0-9]+)', views.add_case_in_suite, name="add_case_in_suite"), ]
6.3 定义视图
1 from django.shortcuts import render, redirect, HttpResponse2 from django.contrib import auth # Django用户认证(Auth)组件一般用在用户的登录注册上,用于判断当前的用户是否合法3 from django.contrib.auth.decorators import login_required4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage5 from .form import UserForm6 import traceback7 from .models import Project, Module, TestCase, CaseSuite, SuiteCase8 9 10 # 封装分页处理11 def get_paginator(request, data):12 paginator = Paginator(data, 10) # 默认每页展示10条数据13 # 获取 url 后面的 page 参数的值, 首页不显示 page 参数, 默认值是 114 page = request.GET.get('page')15 try:16 paginator_pages = paginator.page(page)17 except PageNotAnInteger:18 # 如果请求的页数不是整数, 返回第一页。19 paginator_pages = paginator.page(1)20 except InvalidPage:21 # 如果请求的页数不存在, 重定向页面22 return HttpResponse('找不到页面的内容')23 return paginator_pages24 25 26 # 项目菜单27 @login_required28 def project(request):29 print("request.user.is_authenticated: ", request.user.is_authenticated)30 projects = Project.objects.filter().order_by('-id')31 print("projects:", projects)32 return render(request, 'project.html', {'projects': get_paginator(request, projects)})33 34 35 # 模块菜单36 @login_required37 def module(request):38 if request.method == "GET": # 请求get时候,id倒序查询所有的模块数据39 modules = Module.objects.filter().order_by('-id')40 return render(request, 'module.html', {'modules': get_paginator(request, modules)})41 else: # 否则就是Post请求,会根据输入内容,使用模糊的方式查找所有的项目42 proj_name = request.POST['proj_name']43 projects = Project.objects.filter(name__contains=proj_name.strip())44 projs = [proj.id for proj in projects]45 modules = Module.objects.filter(belong_project__in=projs) # 把项目中所有的模块都找出来46 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})47 48 49 # 测试用例页50 @login_required51 def test_case(request):52 print("request.session['is_login']: {}".format(request.session['is_login']))53 test_cases = ""54 if request.method == "GET":55 test_cases = TestCase.objects.filter().order_by('id')56 print("testcases in testcase: {}".format(test_cases))57 elif request.method == "POST":58 print("request.POST: {}".format(request.POST))59 test_case_id_list = request.POST.getlist('testcases_list')60 if test_case_id_list:61 test_case_id_list.sort()62 print("test_case_id_list: {}".format(test_case_id_list))63 test_cases = TestCase.objects.filter().order_by('id')64 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})65 66 67 # 用例详情页68 @login_required69 def test_case_detail(request, test_case_id):70 test_case_id = int(test_case_id)71 test_case = TestCase.objects.get(id=test_case_id)72 print("test_case: {}".format(test_case))73 print("test_case.id: {}".format(test_case.id))74 print("test_case.belong_project: {}".format(test_case.belong_project))75 76 return render(request, 'test_case_detail.html', {'test_case': test_case})77 78 79 # 模块页展示测试用例80 @login_required81 def module_test_cases(request, module_id):82 module = ""83 if module_id: # 访问的时候,会从url中提取模块的id,根据模块id查询到模块数据,在模板中展现84 module = Module.objects.get(id=int(module_id))85 test_cases = TestCase.objects.filter(belong_module=module).order_by('-id')86 print("test_case in module_test_cases: {}".format(test_cases))87 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})88 89 90 # 用例集合页91 @login_required92 def case_suite(request):93 case_suites = CaseSuite.objects.filter()94 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})95 96 97 # 用例集合-添加测试用例页98 @login_required99 def add_case_in_suite(request, suite_id): 100 # 查询指定的用例集合 101 case_suite = CaseSuite.objects.get(id=suite_id) 102 # 根据id号查询所有的用例 103 test_cases = TestCase.objects.filter().order_by('id') 104 if request.method == "GET": 105 print("test cases:", test_cases) 106 elif request.method == "POST": 107 test_cases_list = request.POST.getlist('testcases_list') 108 # 如果页面勾选了用例 109 if test_cases_list: 110 print("勾选用例id:", test_cases_list) 111 # 根据页面勾选的用例与查询出的所有用例一一比较 112 for test_case in test_cases_list: 113 test_case = TestCase.objects.get(id=int(test_case)) 114 # 匹配成功则添加用例 115 SuiteCase.objects.create(case_suite=case_suite, test_case=test_case) 116 # 未勾选用例 117 else: 118 print("添加测试用例失败") 119 return HttpResponse("添加的测试用例为空,请选择用例后再添加!") 120 return render(request, 'add_case_in_suite.html', 121 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite}) 122 123 124 # 默认页的视图函数 125 @login_required 126 def index(request): 127 return render(request, 'index.html') 128 129 130 # 登录页的视图函数 131 def login(request): 132 print("request.session.items(): {}".format(request.session.items())) # 打印session信息 133 if request.session.get('is_login', None): 134 return redirect('/') 135 # 如果是表单提交行为,则进行登录校验 136 if request.method == "POST": 137 login_form = UserForm(request.POST) 138 message = "请检查填写的内容!" 139 if login_form.is_valid(): 140 username = login_form.cleaned_data['username'] 141 password = login_form.cleaned_data['password'] 142 try: 143 # 使用django提供的身份验证功能 144 user = auth.authenticate(username=username, password=password) # 从auth_user表中匹配信息,匹配到则返回用户对象 145 if user is not None: 146 print("用户【%s】登录成功" % username) 147 auth.login(request, user) 148 request.session['is_login'] = True 149 # 登录成功,跳转主页 150 return redirect('/') 151 else: 152 message = "用户名不存在或者密码不正确!" 153 except: 154 traceback.print_exc() 155 message = "登录程序出现异常" 156 # 用户名或密码为空,返回登录页和错误提示信息 157 else: 158 return render(request, 'login.html', locals()) 159 # 不是表单提交,代表只是访问登录页 160 else: 161 login_form = UserForm() 162 return render(request, 'login.html', locals()) 163 164 165 # 注册页的视图函数 166 def register(request): 167 return render(request, 'register.html') 168 169 170 # 登出的视图函数:重定向至login视图函数 171 @login_required 172 def logout(request): 173 auth.logout(request) 174 request.session.flush() 175 return redirect("/login/")
6.4 定义模板文件
1)新增添加测试用例页的模板文件 templates/add_case_in_suite.html:
1 {% extends 'base.html' %}2 {% load static %}3 {% block title %}管理测试集合{% endblock %}4 {% block content %}5 6 <script type="text/javascript">7 //页面加载的时候,所有的复选框都是未选中的状态8 function checkOrCancelAll() {9 var all_check = document.getElementById("all_check");//1.获取all的元素对象 10 var all_check = all_check.checked;//2.获取选中状态 11 var allCheck = document.getElementsByName("testcases_list");//3.若checked=true,将所有的复选框选中,checked=false,将所有的复选框取消 12 //4.循环遍历取出每一个复选框中的元素 13 if (all_check)//全选 14 { 15 for (var i = 0; i < allCheck.length; i++) { 16 //设置复选框的选中状态 17 allCheck[i].checked = true; 18 } 19 } else//取消全选 20 { 21 for (var i = 0; i < allCheck.length; i++) { 22 allCheck[i].checked = false; 23 } 24 } 25 } 26 27 function ischecked() { 28 var allCheck = document.getElementsByName("testcases_list");//3.若checked=true,将所有的复选框选中,checked=false,将所有的复选框取消 29 for (var i = 0; i < allCheck.length; i++) { 30 if (allCheck[i].checked == true) { 31 alert("成功添加所选测试用例至测试集合【{{case_suite.suite_desc}}】"); 32 return true 33 } 34 } 35 alert("请选择要添加的测试用例!") 36 return false 37 } 38 39 40 41 </script> 42 <form action="" method="POST"> 43 {% csrf_token %} 44 <input type="submit" id="all_check1" value='添加测试用例' onclick="return ischecked()"/> 45 <div class="table-responsive"> 46 <table class="table table-striped"> 47 <thead> 48 <tr> 49 <th><input type="checkbox" id="all_check" onclick="checkOrCancelAll();"/>id</th> 50 <th>用例名称</th> 51 <th>所属项目</th> 52 <th>所属模块</th> 53 <th>编写人员</th> 54 <th>创建时间</th> 55 <th>更新时间</th> 56 <th>创建用例用户名</th> 57 </tr> 58 </thead> 59 <tbody> 60 {% for test_case in test_cases %} 61 <tr> 62 <td><input type="checkbox" value="{{ test_case.id }}" name="testcases_list"> {{ test_case.id }}</td> 63 <td><a href="{% url 'test_case_detail' test_case.id%}">{{ test_case.case_name }}</a></td> 64 <td>{{ test_case.belong_project.name }}</td> 65 <td>{{ test_case.belong_module.name }}</td> 66 <td>{{ test_case.maintainer }}</td> 67 <td>{{ test_case.created_time|date:"Y-n-d H:i" }}</td> 68 <td>{{ test_case.updated_time|date:"Y-n-d H:i" }}</td> 69 <td>{{ test_case.user.username }}</td> 70 </tr> 71 {% endfor %} 72 </tbody> 73 </table> 74 </div> 75 </form> 76 {# 实现分页标签的代码 #} 77 {# 这里使用 bootstrap 渲染页面 #} 78 <div id="pages" class="text-center"> 79 <nav> 80 <ul class="pagination"> 81 <li class="step-links"> 82 {% if test_cases.has_previous %} 83 <a class='active' href="?page={{ test_cases.previous_page_number }}">上一页</a> 84 {% endif %} 85 <span class="current"> 86 第 {{ test_cases.number }} 页 / 共 {{ test_cases.paginator.num_pages }} 页</span> 87 {% if test_cases.has_next %} 88 <a class='active' href="?page={{ test_cases.next_page_number }}">下一页</a> 89 {% endif %} 90 </li> 91 </ul> 92 </nav> 93 </div> 94 {% endblock %}
2)修改用例集合模板文件 templates/case_suite.html:修改“添加测试用例”的链接地址。
1 {% extends 'base.html' %}2 {% load static %}3 {% block title %}用例集合{% endblock %}4 {% block content %}5 <form action="" method="POST">6 {% csrf_token %}7 8 <div class="table-responsive">9 <table class="table table-striped"> 10 <thead> 11 <tr> 12 <th>id</th> 13 <th>测试集合名称</th> 14 <th>创建者</th> 15 <th>创建时间</th> 16 <th>查看/删除测试用例</th> 17 <th>添加测试用例</th> 18 <th>用例集合执行结果</th> 19 </tr> 20 </thead> 21 <tbody> 22 23 {% for case_suite in case_suites %} 24 <tr> 25 <td>{{ case_suite.id }}</td> 26 <td>{{ case_suite.suite_desc }}</td> 27 <td>{{ case_suite.creator }}</td> 28 <td>{{ case_suite.create_time|date:"Y-n-d H:i" }}</td> 29 <td><a href="">查看/删除测试用例</a></td> 30 <td><a href="{% url 'add_case_in_suite' case_suite.id %}">添加测试用例</a></td> 31 <td><a href="">查看用例集合执行结果</a></td> 32 </tr> 33 {% endfor %} 34 </tbody> 35 </table> 36 </div> 37 </form> 38 39 {# 实现分页标签的代码 #} 40 {# 这里使用 bootstrap 渲染页面 #} 41 <div id="pages" class="text-center"> 42 <nav> 43 <ul class="pagination"> 44 <li class="step-links"> 45 {% if case_suites.has_previous %} 46 <a class='active' href="?page={{ case_suites.previous_page_number }}">上一页</a> 47 {% endif %} 48 49 <span class="current"> 50 第 {{ case_suites.number }} 页 / 共 {{ case_suites.paginator.num_pages }} 页</span> 51 52 {% if case_suites.has_next %} 53 <a class='active' href="?page={{ case_suites.next_page_number }}">下一页</a> 54 {% endif %} 55 </li> 56 </ul> 57 </nav> 58 </div> 59 {% endblock %}
7. 用例集合查看/删除测试用例
预期效果如下:
7.1 定义路由
from django.urls import path, re_path from . import viewsurlpatterns = [path('', views.index),path('login/', views.login),path('logout/', views.logout),path('project/', views.project, name='project'),path('module/', views.module, name='module'),path('test_case/', views.test_case, name="test_case"),re_path('test_case_detail/(?P<test_case_id>[0-9]+)', views.test_case_detail, name="test_case_detail"),re_path('module_test_cases/(?P<module_id>[0-9]+)/$', views.module_test_cases, name="module_test_cases"),path('case_suite/', views.case_suite, name="case_suite"),re_path('add_case_in_suite/(?P<suite_id>[0-9]+)', views.add_case_in_suite, name="add_case_in_suite"),re_path('show_and_delete_case_in_suite/(?P<suite_id>[0-9]+)', views.show_and_delete_case_in_suite, name="show_and_delete_case_in_suite"), ]
7.2 定义视图函数
1 from django.shortcuts import render, redirect, HttpResponse2 from django.contrib import auth # Django用户认证(Auth)组件一般用在用户的登录注册上,用于判断当前的用户是否合法3 from django.contrib.auth.decorators import login_required4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage5 from .form import UserForm6 import traceback7 from .models import Project, Module, TestCase, CaseSuite, SuiteCase, InterfaceServer8 from .task import case_task9 10 11 # 封装分页处理12 def get_paginator(request, data):13 paginator = Paginator(data, 10) # 默认每页展示10条数据14 # 获取 url 后面的 page 参数的值, 首页不显示 page 参数, 默认值是 115 page = request.GET.get('page')16 try:17 paginator_pages = paginator.page(page)18 except PageNotAnInteger:19 # 如果请求的页数不是整数, 返回第一页。20 paginator_pages = paginator.page(1)21 except InvalidPage:22 # 如果请求的页数不存在, 重定向页面23 return HttpResponse('找不到页面的内容')24 return paginator_pages25 26 27 # 项目页28 @login_required29 def project(request):30 print("request.user.is_authenticated: ", request.user.is_authenticated)31 projects = Project.objects.filter().order_by('-id')32 print("projects:", projects)33 return render(request, 'project.html', {'projects': get_paginator(request, projects)})34 35 36 # 模块页37 @login_required38 def module(request):39 if request.method == "GET": # 请求get时候,id倒序查询所有的模块数据40 modules = Module.objects.filter().order_by('-id')41 return render(request, 'module.html', {'modules': get_paginator(request, modules)})42 else: # 否则就是Post请求,会根据输入内容,使用模糊的方式查找所有的项目43 proj_name = request.POST['proj_name']44 projects = Project.objects.filter(name__contains=proj_name.strip())45 projs = [proj.id for proj in projects]46 modules = Module.objects.filter(belong_project__in=projs) # 把项目中所有的模块都找出来47 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})48 49 50 # 获取测试用例执行的接口地址51 def get_server_address(env):52 if env: # 环境处理53 env_data = InterfaceServer.objects.filter(env=env[0])54 print("env_data: {}".format(env_data))55 if env_data:56 ip = env_data[0].ip57 port = env_data[0].port58 print("ip: {}, port: {}".format(ip, port))59 server_address = "http://{}:{}".format(ip, port)60 print("server_address: {}".format(server_address))61 return server_address62 else:63 return ""64 else:65 return ""66 67 68 # 测试用例页69 @login_required70 def test_case(request):71 print("request.session['is_login']: {}".format(request.session['is_login']))72 test_cases = ""73 if request.method == "GET":74 test_cases = TestCase.objects.filter().order_by('id')75 print("testcases in testcase: {}".format(test_cases))76 elif request.method == "POST":77 print("request.POST: {}".format(request.POST))78 test_case_id_list = request.POST.getlist('testcases_list')79 if test_case_id_list:80 test_case_id_list.sort()81 print("test_case_id_list: {}".format(test_case_id_list))82 test_cases = TestCase.objects.filter().order_by('id')83 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})84 85 86 # 用例详情页87 @login_required88 def test_case_detail(request, test_case_id):89 test_case_id = int(test_case_id)90 test_case = TestCase.objects.get(id=test_case_id)91 print("test_case: {}".format(test_case))92 print("test_case.id: {}".format(test_case.id))93 print("test_case.belong_project: {}".format(test_case.belong_project))94 95 return render(request, 'test_case_detail.html', {'test_case': test_case})96 97 98 # 模块页展示测试用例99 @login_required 100 def module_test_cases(request, module_id): 101 module = "" 102 if module_id: # 访问的时候,会从url中提取模块的id,根据模块id查询到模块数据,在模板中展现 103 module = Module.objects.get(id=int(module_id)) 104 test_cases = TestCase.objects.filter(belong_module=module).order_by('-id') 105 print("test_case in module_test_cases: {}".format(test_cases)) 106 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)}) 107 108 109 # 用例集合页 110 @login_required 111 def case_suite(request): 112 case_suites = CaseSuite.objects.filter() 113 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)}) 114 115 116 # 用例集合-添加测试用例页 117 @login_required 118 def add_case_in_suite(request, suite_id): 119 # 查询指定的用例集合 120 case_suite = CaseSuite.objects.get(id=suite_id) 121 # 根据id号查询所有的用例 122 test_cases = TestCase.objects.filter().order_by('id') 123 if request.method == "GET": 124 print("test cases:", test_cases) 125 elif request.method == "POST": 126 test_cases_list = request.POST.getlist('testcases_list') 127 # 如果页面勾选了用例 128 if test_cases_list: 129 print("勾选用例id:", test_cases_list) 130 # 根据页面勾选的用例与查询出的所有用例一一比较 131 for test_case in test_cases_list: 132 test_case = TestCase.objects.get(id=int(test_case)) 133 # 匹配成功则添加用例 134 SuiteCase.objects.create(case_suite=case_suite, test_case=test_case) 135 # 未勾选用例 136 else: 137 print("添加测试用例失败") 138 return HttpResponse("添加的测试用例为空,请选择用例后再添加!") 139 return render(request, 'add_case_in_suite.html', 140 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite}) 141 142 143 # 用例集合页-查看/删除用例 144 @login_required 145 def show_and_delete_case_in_suite(request, suite_id): 146 case_suite = CaseSuite.objects.get(id=suite_id) 147 test_cases = SuiteCase.objects.filter(case_suite=case_suite) 148 if request.method == "POST": 149 test_cases_list = request.POST.getlist('test_cases_list') 150 if test_cases_list: 151 print("勾选用例:", test_cases_list) 152 for test_case in test_cases_list: 153 test_case = TestCase.objects.get(id=int(test_case)) 154 SuiteCase.objects.filter(case_suite=case_suite, test_case=test_case).first().delete() 155 else: 156 print("测试用例删除失败") 157 return HttpResponse("所选测试用例为空,请选择用例后再进行删除!") 158 case_suite = CaseSuite.objects.get(id=suite_id) 159 return render(request, 'show_and_delete_case_in_suite.html', 160 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite}) 161 162 163 # 默认页的视图函数 164 @login_required 165 def index(request): 166 return render(request, 'index.html') 167 168 169 # 登录页的视图函数 170 def login(request): 171 print("request.session.items(): {}".format(request.session.items())) # 打印session信息 172 if request.session.get('is_login', None): 173 return redirect('/') 174 # 如果是表单提交行为,则进行登录校验 175 if request.method == "POST": 176 login_form = UserForm(request.POST) 177 message = "请检查填写的内容!" 178 if login_form.is_valid(): 179 username = login_form.cleaned_data['username'] 180 password = login_form.cleaned_data['password'] 181 try: 182 # 使用django提供的身份验证功能 183 user = auth.authenticate(username=username, password=password) # 从auth_user表中匹配信息,匹配到则返回用户对象 184 if user is not None: 185 print("用户【%s】登录成功" % username) 186 auth.login(request, user) 187 request.session['is_login'] = True 188 # 登录成功,跳转主页 189 return redirect('/') 190 else: 191 message = "用户名不存在或者密码不正确!" 192 except: 193 traceback.print_exc() 194 message = "登录程序出现异常" 195 # 用户名或密码为空,返回登录页和错误提示信息 196 else: 197 return render(request, 'login.html', locals()) 198 # 不是表单提交,代表只是访问登录页 199 else: 200 login_form = UserForm() 201 return render(request, 'login.html', locals()) 202 203 204 # 注册页的视图函数 205 def register(request): 206 return render(request, 'register.html') 207 208 209 # 登出的视图函数:重定向至login视图函数 210 @login_required 211 def logout(request): 212 auth.logout(request) 213 request.session.flush() 214 return redirect("/login/")
7.2 定义模板文件
1)新建 templates/show_and_delete_case_in_suite.html:
1 {% extends 'base.html' %}2 {% load static %}3 {% block title %}查看/删除测试用例{% endblock %}4 {% block content %}5 6 <script type="text/javascript">7 //页面加载的时候,所有的复选框都是未选中的状态8 function checkOrCancelAll() {9 var all_check = document.getElementById("all_check");//1.获取all的元素对象10 var all_check = all_check.checked;//2.获取选中状态11 var allCheck = document.getElementsByName("test_cases_list");//3.若checked=true,将所有的复选框选中,checked=false,将所有的复选框取消12 //4.循环遍历取出每一个复选框中的元素13 if (all_check)//全选14 {15 16 for (var i = 0; i < allCheck.length; i++) {17 //设置复选框的选中状态18 allCheck[i].checked = true;19 }20 21 } else//取消全选22 {23 for (var i = 0; i < allCheck.length; i++) {24 allCheck[i].checked = false;25 }26 }27 }28 29 function ischecked() {30 31 var allCheck = document.getElementsByName("test_cases_list");//3.若checked=true,将所有的复选框选中,checked=false,将所有的复选框取消32 for (var i = 0; i < allCheck.length; i++) {33 34 if (allCheck[i].checked == true) {35 alert("所选用例删除成功!");36 return true37 }38 }39 alert("请选择要删除的测试用例!")40 return false41 }42 43 44 </script>45 46 <div><p style="margin-left: 5px;">测试集合名称:<b>{{case_suite.suite_desc}}</b></p>47 <div>48 <form action="" method="POST">49 {% csrf_token %}50 <input style="margin-left: 5px;" type="submit" id="all_check1" value='删除测试集合用例' onclick="return ischecked()"/>51 <div class="table-responsive">52 <table class="table table-striped">53 <thead>54 <tr>55 <th width="4%"><input type="checkbox" id="all_check" onclick="checkOrCancelAll();"/>全选</th>56 <th width="6%">用例序号</th>57 <th>用例名称</th>58 <th>所属项目</th>59 <th>所属模块</th>60 <th>编写人员</th>61 <th>创建时间</th>62 <th>更新时间</th>63 <th>创建用例用户名</th>64 </tr>65 </thead>66 <tbody>67 68 {% for test_case in test_cases %}69 <tr>70 <td><input type="checkbox" value="{{ test_case.test_case.id }}" name="test_cases_list"></td>71 <td>{{ test_case.test_case.id }}</td>72 <td><a href="{% url 'test_case_detail' test_case.test_case.id%}">{{ test_case.test_case.case_name }}</a></td>73 <td>{{ test_case.test_case.belong_project.name }}</td>74 <td>{{ test_case.test_case.belong_module.name }}</td>75 <td>{{ test_case.test_case.maintainer }}</td>76 <td>{{ test_case.test_case.created_time|date:"Y-n-d H:i" }}</td>77 <td>{{ test_case.test_case.updated_time|date:"Y-n-d H:i" }}</td>78 <td>{{ test_case.test_case.user.username }}</td>79 </tr>80 {% endfor %}81 </tbody>82 </table>83 </div>84 </form>85 86 {# 实现分页标签的代码 #}87 {# 这里使用 bootstrap 渲染页面 #}88 <div id="pages" class="text-center">89 <nav>90 <ul class="pagination">91 <li class="step-links">92 {% if test_cases.has_previous %}93 <a class='active' href="?page={{ test_cases.previous_page_number }}">上一页</a>94 {% endif %}95 96 <span class="current">97 第 {{ test_cases.number }} 页 / 共 {{ test_cases.paginator.num_pages }} 页</span>98 99 {% if test_cases.has_next %} 100 <a class='active' href="?page={{ test_cases.next_page_number }}">下一页</a> 101 {% endif %} 102 </li> 103 </ul> 104 </nav> 105 </div> 106 </div> 107 </div> 108 {% endblock %}
2)修改 templates/case_suite.html:增加“查看/删除测试用例”链接
1 {% extends 'base.html' %}2 {% load static %}3 {% block title %}用例集合{% endblock %}4 {% block content %}5 <form action="" method="POST">6 {% csrf_token %}7 8 <div class="table-responsive">9 <table class="table table-striped"> 10 <thead> 11 <tr> 12 <th>id</th> 13 <th>测试集合名称</th> 14 <th>创建者</th> 15 <th>创建时间</th> 16 <th>查看/删除测试用例</th> 17 <th>添加测试用例</th> 18 <th>用例集合执行结果</th> 19 </tr> 20 </thead> 21 <tbody> 22 23 {% for case_suite in case_suites %} 24 <tr> 25 <td>{{ case_suite.id }}</td> 26 <td>{{ case_suite.suite_desc }}</td> 27 <td>{{ case_suite.creator }}</td> 28 <td>{{ case_suite.create_time|date:"Y-n-d H:i" }}</td> 29 <td><a href="{% url 'show_and_delete_case_in_suite' case_suite.id %}">查看/删除测试用例</a></td> 30 <td><a href="{% url 'add_case_in_suite' case_suite.id %}">添加测试用例</a></td> 31 <td><a href="">查看用例集合执行结果</a></td> 32 </tr> 33 {% endfor %} 34 </tbody> 35 </table> 36 </div> 37 </form> 38 39 {# 实现分页标签的代码 #} 40 {# 这里使用 bootstrap 渲染页面 #} 41 <div id="pages" class="text-center"> 42 <nav> 43 <ul class="pagination"> 44 <li class="step-links"> 45 {% if case_suites.has_previous %} 46 <a class='active' href="?page={{ case_suites.previous_page_number }}">上一页</a> 47 {% endif %} 48 49 <span class="current"> 50 第 {{ case_suites.number }} 页 / 共 {{ case_suites.paginator.num_pages }} 页</span> 51 52 {% if case_suites.has_next %} 53 <a class='active' href="?page={{ case_suites.next_page_number }}">下一页</a> 54 {% endif %} 55 </li> 56 </ul> 57 </nav> 58 </div> 59 {% endblock %}