5. 用例集合
预期效果如下:

5.1 定义模型类
1)models.py 中新增 case_suite 模型类:
1 from django.db import models
2 from smart_selects.db_fields import GroupedForeignKey # pip install django-smart-selects:后台级联选择
3 from django.contrib.auth.models import User
4
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 admin
2 from .import models
3
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 views
urlpatterns = [
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, HttpResponse
2 from django.contrib import auth # Django用户认证(Auth)组件一般用在用户的登录注册上,用于判断当前的用户是否合法
3 from django.contrib.auth.decorators import login_required
4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage
5 from .form import UserForm
6 import traceback
7 from .models import Project, Module, TestCase, CaseSuite
8
9
10 # 封装分页处理
11 def get_paginator(request, data):
12 paginator = Paginator(data, 10) # 默认每页展示10条数据
13 # 获取 url 后面的 page 参数的值, 首页不显示 page 参数, 默认值是 1
14 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_pages
24
25
26 # 项目菜单
27 @login_required
28 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_required
37 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_required
51 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_required
69 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_required
81 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_required
91 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_required
98 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 models
2 from smart_selects.db_fields import GroupedForeignKey # pip install django-smart-selects:后台级联选择
3 from django.contrib.auth.models import User
4
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 views
urlpatterns = [
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, HttpResponse
2 from django.contrib import auth # Django用户认证(Auth)组件一般用在用户的登录注册上,用于判断当前的用户是否合法
3 from django.contrib.auth.decorators import login_required
4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage
5 from .form import UserForm
6 import traceback
7 from .models import Project, Module, TestCase, CaseSuite, SuiteCase
8
9
10 # 封装分页处理
11 def get_paginator(request, data):
12 paginator = Paginator(data, 10) # 默认每页展示10条数据
13 # 获取 url 后面的 page 参数的值, 首页不显示 page 参数, 默认值是 1
14 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_pages
24
25
26 # 项目菜单
27 @login_required
28 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_required
37 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_required
51 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_required
69 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_required
81 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_required
92 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_required
99 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 views
urlpatterns = [
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, HttpResponse
2 from django.contrib import auth # Django用户认证(Auth)组件一般用在用户的登录注册上,用于判断当前的用户是否合法
3 from django.contrib.auth.decorators import login_required
4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage
5 from .form import UserForm
6 import traceback
7 from .models import Project, Module, TestCase, CaseSuite, SuiteCase, InterfaceServer
8 from .task import case_task
9
10
11 # 封装分页处理
12 def get_paginator(request, data):
13 paginator = Paginator(data, 10) # 默认每页展示10条数据
14 # 获取 url 后面的 page 参数的值, 首页不显示 page 参数, 默认值是 1
15 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_pages
25
26
27 # 项目页
28 @login_required
29 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_required
38 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].ip
57 port = env_data[0].port
58 print("ip: {}, port: {}".format(ip, port))
59 server_address = "http://{}:{}".format(ip, port)
60 print("server_address: {}".format(server_address))
61 return server_address
62 else:
63 return ""
64 else:
65 return ""
66
67
68 # 测试用例页
69 @login_required
70 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_required
88 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 true
37 }
38 }
39 alert("请选择要删除的测试用例!")
40 return false
41 }
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 %}