本專案工程 github 地址:https://github.com/juno3550/InterfaceAutoTestPlatform

0. 引言

1. 登入功能

2. 專案

3. 模組

4. 測試用例

5. 用例集合

6. 用例集合新增測試用例

7. 用例集合檢視/刪除測試用例

8. 測試用例執行

9. 用例執行結果展示

10. 測試集合執行

11. 用例集合執行結果展示

12. 用例集合歷史執行結果統計

13. 用例集合單次執行結果統計

14. 模組測試結果統計

15. 專案測試結果統計

16. Celery 非同步執行用例

0. 引言

0.1 平臺功能概述

本介面自動化測試平臺的整體功能如下圖所示:

0.2 建立專案與應用

本專案環境如下:

  • python 3.6.5
  • pip install django==2.2.4
  • pip install redis==2.10.6
  • pip install eventlet==0.25.2
  • pip install celery==3.1.26.post2

對於 Django 的基礎使用教程,可以參考本部落格的《Django》系列博文。

1)建立本專案工程

# 方式一
django-admin startproject InterfaceAutoTest # 方式二
python -m django startproject interface_test_platform

此時工程結構如下所示:

2)建立應用

在專案目錄下,使用如下命令建立應用:

django-admin startapp interfacetestplatform

此時工程結構目錄如下:

3)註冊應用

在專案目錄 InterfaceAutoTest/settings.py 中,找到 INSTALLED_APPS 配置項,在列表末尾新增新建的應用名稱“interfacetestplatform”:

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'interfacetestplatform,'
]

新建的應用需要在 INSTALLED_APPS 配置項中註冊,Django 會根據該配置項到應用下查詢模板等資訊。

4)使用 Bootstrap

Bootstrap 是流行的 HTML、CSS 和 JS 框架,用於開發響應式佈局、移動裝置優先的 Web 專案。

在專案目錄下新建一個 static 目錄,將解壓後的如 bootstrap-3.3.7-dist 目錄整體拷貝到 static 目錄中,並將檔名改為 bootstrap。

由於 bootstrap 依賴 jQuery,我們需要提前下載並引入 jQuery。在 static 目錄下,新建 css 和 js 目錄,作為後面的樣式檔案和 js 檔案的存放地,將我們的 jQuery 檔案拷貝到 static/js/ 目錄下。

ECharts 是一個使用 JavaScript 實現的開源視覺化庫,在本專案中用於用例/集合的執行結果統計視覺化。於是我們引入 echarts 檔案到 static/js/ 目錄下。

此時 static 目錄結構如下圖所示:

在專案目錄下的 settings.py 檔案的末尾新增如下配置,用於指定靜態檔案的搜尋目錄:

STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]

通過 STATICFILES_DIRS 屬性,可以指定靜態資源的路徑,此處配置的是專案根目錄下的 static 資料夾下。

預設情況下,Django 只能識別專案應用目錄下的 static 目錄的靜態資源,不會識別到專案目錄下的 static目錄,因此通過 STATICFILES_DIR 屬性可以解決這個問題。

5)建立應用的模板目錄

在應用目錄下新建一個 templates 目錄,專門存放本應用所有的模板檔案。

預設情況下,Django 只能識別專案應用目錄下的 templates 目錄的靜態資源。本工程因僅涉及一個應用,因此選用此方案。

若應用較多,為了易於維護,可將各應用的模板檔案進行統一處理,則在專案目錄下新建 templates 目錄,並在 settings.py 中新增配置:

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'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',
],
},
},
]

6)啟動 Django 的 Web 服務

在專案目錄下,執行如下命令啟動埠號為 8000 的 Django 的 Web 服務:

python manage.py runsever 8000  # 埠號可自定義,不寫則預設8000

啟動日誌如下:

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
July 13, 2021 - 10:03:23
Django version 3.2.5, using settings 'InterfaceAutoTest.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

啟動成功後,用瀏覽器訪問 127.0.0.1:8000,可以看到 Django 預設的頁面,如下所示即代表服務啟動成功:

7)配置 Mysql 資料庫

修改專案目錄下的 settings.py 檔案,將 DATABASES 配置項改為如下配置:

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'interface_platform', # 已存在的庫名
'USER': 'root', # 資料庫賬號
'PASSWORD': '123', # 資料庫密碼
'HOST': '127.0.0.1', # 資料庫IP
'PORT': '3306', # 資料庫埠
}
}

若使用的是 Python 3 的 pymysql,則需要在專案的 __init__.py 檔案中新增如下內容,避免報錯:

import pymysql
pymysql.version_info = (1, 4, 13, "final", 0) # 指定版本。在出現“mysqlclient 1.4.0 or newer is required; you have 0.9.3.”報錯時加上此行
pymysql.install_as_MySQLdb()

8)建立 Admin 站點超級使用者

首先在專案目錄下進行資料遷移,生成 auth_user 等基礎表:

python manage.py migrate

在工程目錄下,執行以下命令建立超級使用者,用於登入 Django 的 Admin 站點:

D:\InterfaceAutoTest>python manage.py createsuperuser
Username (leave blank to use 'administrator'): admin # 使用者名稱
Email address: [email protected] # 符合郵箱格式即可
Password: # 密碼
Password (again): # 二次確認密碼
The password is too similar to the username.
This password is too common.
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.

建立好 superuser 後,訪問 127.0.0.1:8000/admin,用註冊的 admin 賬戶進行登入(資料參考的就是 auth_user 表,並且必須是管理員使用者才能進入)。

配置 admin 語言和時區

登入 admin 頁面,可以看到頁面的標題、選單顯示的語言是英文,那麼如何展示為中文呢?

在 settings.py 中,找到 MIDDLEWARE 屬性,新增中介軟體。

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

同時,在後續新增專案資訊表資料時,可以發現數據的建立時間、更新時間欄位的時間不是本地時間,而是 UTC 時間,那麼怎麼展示為國內本地時間呢?

也是 settings.py 中,修改 TIME_ZONE 和 USE_TZ 的配置項:

TIME_ZONE = 'Asia/Shanghai'

USE_TZ = False

這樣配置後,admin 頁面中資料表的時間欄位以及前端模板中的時間欄位都是本地時區的時間。

  • 當 USE_TZ 為 False 時,TIME_ZONE值是 Django 儲存所有日期時間的時區。
  • 當 USE_TZ 為 True 時,TIME_ZONE值是 Django 在模板中顯示日期時間和解釋表單中輸入的日期時間的預設時區。簡單起見,USE_TZ 直接設為 False。

1. 登入功能

預期效果如下:

1.1 定義路由

1) 路由定義規則說明

路由的基本資訊包含路由地址和檢視函式,路由地址即訪問網址,檢視函式即客戶端在訪問指定網址後,伺服器端所對應的處理邏輯。

在應用目錄(interfacetestplatform 目錄)下新建 urls.py,將所有屬於該應用的路由都寫入該檔案中,這樣更容易管理和區分每個應用的路由地址,而專案目錄(InterfaceAutoTest)下的 urls.py 是將每個應用的 urls.py 進行統一管理。

這種路由設計模式是 Django 常用的,其工作原理如下:

  1. 執行 InterfaceAutoTest 專案時,Django 從 interfacetestplatform (應用)目錄的 urls.py 找到對應應用所定義的路由資訊,生成完整的路由表。
  2. 當用戶在瀏覽器上訪問某個路由地址時,Django 服務端就會收到該使用者的請求資訊。Django 從當前請求資訊獲取路由地址,首先匹配專案目錄下的 urls.py 的路由列表。轉發到指定應用的 urls.py 的路由列表。
  3. 再執行應用下的路由資訊所指向的檢視函式,從而完成整個請求響應過程。

2)路由配置

配置專案 urls.py:InterfaceAutoTest/urls.py

from django.contrib import admin
from django.urls import path, include urlpatterns = [
path('admin/', admin.site.urls), # 指向內建Admin後臺系統的路由檔案sites.py
path('', include('interfacetestplatform.urls')), # 指向interfacetestplatform應用的urls.py
]

由於預設地址分發給了 interfacetestplatform(應用)的 urls.py 進行處理,因此需要對 interfacetestplatform/urls.py 編寫路由資訊,程式碼如下:

from django.urls import path
from . import views urlpatterns = [
path('', views.index),
path('login/', views.login),
path('logout/', views.logout),
]

如路由資訊 path('', views.index) 的 views.index 是指專門處理網站預設頁的使用者請求和響應過程的檢視函式名稱 index,其他路由規則原理一樣。

1.2 定義檢視函式

1)定義 Form 表單類

在定義檢視函式之前,我們先定義 Django 提供的表單模型類,來代替原生的前端 Form 表單。

在應用目錄下,新建 form.py:

1 from django import forms
2
3
4 class UserForm(forms.Form):
5 username = forms.CharField(label="使用者名稱", max_length=128, widget=forms.TextInput(attrs={'class': 'form-control'}))
6 password = forms.CharField(label="密碼", max_length=256, widget=forms.PasswordInput(attrs={'class': 'form-control'}))

2)定義檢視函式

在應用的 interfacepestplatform/views.py (檢視模組)中新增如下程式碼:

 1 from django.shortcuts import render, redirect
2 from django.contrib import auth # Django使用者認證(Auth)元件一般用在使用者的登入註冊上,用於判斷當前的使用者是否合法
3 from django.contrib.auth.decorators import login_required
4 from .form import UserForm
5 import traceback
6
7
8 # Create your views here.
9 # 預設頁的檢視函式
10 @login_required
11 def index(request):
12 return render(request, 'index.html')
13
14
15 # 登入頁的檢視函式
16 def login(request):
17 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
18 if request.session.get('is_login', None):
19 return redirect('/')
20 # 如果是表單提交行為,則進行登入校驗
21 if request.method == "POST":
22 login_form = UserForm(request.POST)
23 message = "請檢查填寫的內容!"
24 if login_form.is_valid():
25 username = login_form.cleaned_data['username']
26 password = login_form.cleaned_data['password']
27 try:
28 # 使用django提供的身份驗證功能
29 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配成功則返回使用者物件;反之返回None
30 if user is not None:
31 print("使用者【%s】登入成功" % username)
32 auth.login(request, user)
33 request.session['is_login'] = True
34 # 登入成功,跳轉主頁
35 return redirect('/')
36 else:
37 message = "使用者名稱不存在或者密碼不正確!"
38 except:
39 traceback.print_exc()
40 message = "登入程式出現異常"
41 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
42 else:
43 return render(request, 'login.html', locals())
44 # 不是表單提交,代表只是訪問登入頁
45 else:
46 login_form = UserForm()
47 return render(request, 'login.html', locals())
48
49
50 # 註冊頁的檢視函式
51 def register(request):
52 return render(request, 'register.html')
53
54
55 # 登出的檢視函式:重定向至login檢視函式
56 @login_required
57 def logout(request):
58 auth.logout(request)
59 request.session.flush()
60 return redirect("/login/")

render 方法

用來生成網頁內容並返回給客戶端,其兩個必須引數表示:第一個引數是瀏覽器想伺服器傳送的請求物件;第二個引數是模板名稱,用於生成網頁內容。

auth 模組

auth 模組是 cookie 和 session 的升級版。

auth 模組是對登入認證方法的一種封裝,之前我們獲取使用者輸入的使用者名稱及密碼後需要自己從 user 表裡查詢有沒有使用者名稱和密碼符合的物件,而有了 auth 模組之後就可以很輕鬆地去驗證使用者的登入資訊是否存在於資料庫(auth_user 表)中。

除此之外,auth 還對 session 做了一些封裝,方便我們校驗使用者是否已登入。

login 方法

對於非 POST 方法的請求(如 GET 請求),直接返回空的表單,讓使用者可以填入資料。

對於 POST 方法請求,接收表單資料,並驗證:

  1. 使用表單類自帶的 is_valid() 方法進一步完成資料驗證工作;
  2. 驗證成功後可以從表單物件的 cleand_data 資料字典中獲取表單的具體值;
  3. 如果驗證不通過,則返回一個包含先前資料的表單給前端頁面,方便使用者修改。也就是說,它會幫你保留先前填寫的資料內容,而不是返回一個空表。
  4. 另外,這裡使用了一個小技巧,python 內建了一個 locals() 函式,它返回當前所有的本地變數字典,我們可以偷懶的將這作為 render 函式的資料字典引數值,就不用費勁去構造一個形如 {'message':message, 'login_form':login_form} 的字典了。這樣做的好處是大大方便了我們;但同時也可能往模板傳入一些多餘的變數資料,造成資料冗餘降低效率。

@login_required

為函式增加裝飾器 @login_required,這種方式可以實現未登入禁止訪問首頁的功能。

此種方式需要在專案 settings.py 中新增如下配置:

LOGIN_URL = '/login/'

通過 LOGIN_URL 告訴 Django 在使用者沒登入的情況下,重定向的登入地址;如果沒配置該屬性,Django 會重定向到預設地址。

如果不用裝飾器方式,也可以在函式內部判斷使用者狀態,並實現重定向。如下所示,效果是一樣的:

def index(request):
print("request.user.is_authenticated: {}".format(request.user.is_authenticated))
if not request.user.is_authenticated:
return redirect("/login/")
return render(request,'index.html')

1.3 定義模板檔案

1)定義 base 基礎模板,供其他模板繼承

新建 interfacetestplatform/templates/base.html,用作平臺前端的基礎模板,程式碼如下:

 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 <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
15 <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
16 <!--[if lt IE 9]>
17 <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>
18 <script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
19 <![endif]-->
20 {% block css %}{% endblock %}
21 </head>
22 <body>
23 <nav class="navbar navbar-default">
24 <div class="container-fluid">
25 <!-- Brand and toggle get grouped for better mobile display -->
26 <div class="navbar-header">
27 <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#my-nav"
28 aria-expanded="false">
29 <span class="sr-only">切換導航條</span>
30 <span class="icon-bar"></span>
31 <span class="icon-bar"></span>
32 <span class="icon-bar"></span>
33 </button>
34 <a class="navbar-brand" href="#">自動化測試平臺</a>
35 </div>
36
37 <div class="collapse navbar-collapse" id="my-nav">
38 <ul class="nav navbar-nav">
39 <li class="active"><a href="/">主頁</a></li>
40 </ul>
41 <ul class="nav navbar-nav navbar-right">
42 {% if request.user.is_authenticated %}
43 <li><a href="#">當前線上:{{ request.user.username }}</a></li>
44 <li><a href="/logout">登出</a></li>
45 {% else %}
46 <li><a href="/login">登入</a></li>
47
48 {% endif %}
49 </ul>
50 </div><!-- /.navbar-collapse -->
51 </div><!-- /.container-fluid -->
52 </nav>
53
54 {% block content %}{% endblock %}
55
56
57 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
58 <script src="{% static 'js/jquery-3.4.1.js' %}"></script>
59 <!-- Include all compiled plugins (below), or include individual files as needed -->
60 <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
61 </body>
62 </html>

在 base 模板中,通過 if 判斷,當登入成功時,顯示當前使用者名稱和登出按鈕;未登入時,則顯示登入按鈕。

注意,request 這個變數是預設被傳入模板中的,可以通過圓點的呼叫方式獲取物件中的屬性,如 {{ reques.user.username }} 表示當前登入的使用者名稱。

2)定義 login 登入模板

首先在 static/css/ 目錄中,新建一個 login.css 樣式檔案,配置一些簡單樣式:

 1 body {
2 background-color: #eee;
3 }
4 .form-login {
5 max-width: 330px;
6 padding: 15px;
7 margin: 0 auto;
8 }
9 .form-login .form-control {
10 position: relative;
11 height: auto;
12 -webkit-box-sizing: border-box;
13 -moz-box-sizing: border-box;
14 box-sizing: border-box;
15 padding: 10px;
16 font-size: 16px;
17 }
18 .form-login .form-control:focus {
19 z-index: 2;
20 }
21 .form-login input[type="text"] {
22 margin-bottom: -1px;
23 border-bottom-right-radius: 0;
24 border-bottom-left-radius: 0;
25 }
26 .form-login input[type="password"] {
27 margin-bottom: 10px;
28 border-top-left-radius: 0;
29 border-top-right-radius: 0;
30 }

然後新建 templates/login.html:

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}登入{% endblock %}
4 {% block css %}
5 <link rel="stylesheet" href="{% static 'css/login.css' %}">
6 {% endblock %}
7
8 {% block content %}
9 <div class="container">
10 <div class="col-md-4 col-md-offset-4">
11 <form class='form-login' action="/login/" method="post">
12 {% csrf_token %}
13
14 <!-- 如果有登入資訊,則提示 -->
15 {% if message %}
16 <div class="alert alert-warning">{{ message }}</div>
17 {% endif %}
18
19 <h2 class="text-center">歡迎登入</h2>
20
21 <div class="form-group">
22 {{ login_form.username.label_tag }}
23 {{ login_form.username}}
24 </div>
25 <div class="form-group">
26 {{ login_form.password.label_tag }}
27 {{ login_form.password }}
28 </div>
29
30 <button type="reset" class="btn btn-default pull-left">重置</button>
31 <button type="submit" class="btn btn-primary pull-right">提交</button>
32 </form>
33 </div>
34 </div> <!-- /container -->
35 {% endblock %}

login 模板說明:

  • 通過 “{% extends 'base.html' %}” 繼承了“base.html”模板的內容。
  • 通過 “{% block title %} 登入 {% endblock %}” 設定了專門的 title。
  • 若頁面提示 CSRF 驗證失敗,我們需要在前端頁面的 form 表單內新增一個 {% csrf_token %} 標籤,CSRF(Cross-site request forgery)跨站請求偽造,是一種常見的網路攻擊手段。為此,Django 自帶了許多常見攻擊手段的防禦機制,CSRF 就是其中一種,還有防 XSS、SQL 注入等。
  • 通過 “{% block css %}”引入了針對性的 login.css 樣式檔案。
  • 主體內容定義在“{% block content %}”中。
  • 使用 {{ login_form.name_of_field }} 方式分別獲取每一個欄位,然後分別進行渲染。

3)定義 index 首頁模板

新增 templates/index.html:

1 {% extends 'base.html' %}
2 {% block title %}主頁{% endblock %}
3 {% block content %}
4 {% if request.user.is_authenticated %}
5 <h1>你好,{{ request.user.username }}!歡迎回來!</h1>
6 {% endif %}
7 {% endblock %}

根據登入狀態的不同,顯示不同的內容。

2. 專案

預期效果如下:

2.1 定義模型類

Django 的模型類提供以面向物件的方式管理資料庫資料,即 ORM(關係物件對映)的思想。

1)編寫模型類

開啟應用目錄下的 models.py,定義專案資訊的模型類:

 1 from django.db import models
2
3
4 class Project(models.Model):
5 id = models.AutoField(primary_key=True)
6 name = models.CharField('專案名稱', max_length=50, unique=True, null=False)
7 proj_owner = models.CharField('專案負責人', max_length=20, null=False)
8 test_owner = models.CharField('測試負責人', max_length=20, null=False)
9 dev_owner = models.CharField('開發負責人', max_length=20, null=False)
10 desc = models.CharField('專案描述', max_length=100, null=True)
11 create_time = models.DateTimeField('專案建立時間', auto_now_add=True)
12 update_time = models.DateTimeField('專案更新時間', auto_now=True, null=True)
13
14 # 列印物件時返回專案名稱
15 def __str__(self):
16 return self.name
17
18 class Meta:
19 verbose_name = '專案資訊表'
20 verbose_name_plural = '專案資訊表'

欄位說明:

  • name:專案名稱,必填,最長不超過 128 個字元,並且唯一,也就是不能有相同專案名稱。
  • proj_owner:專案負責人,必填,最長不超過 20 個字元(實際可能不需要這麼長)。
  • test_owner:測試負責人。必填,最長不超過 20 個字元。
  • dev_owner:開發負責人,必填,最長不超過 20 個字元。
  • desc :專案描述,選填,最長不超過 100 個字元。
  • create_time:建立時間,必填,預設使用當前時間。
  • update_time:更新時間,選填,預設為當前時間。
  • 使用 __str__ 更友好地列印物件資訊。
  • Meta 類屬性 verbose_name 指定了後臺 Admin 頁面展示的表名稱。

2)資料遷移

在專案目錄下,執行以下兩個命令進行資料遷移(將模型類轉換成資料庫表):

python manage.py makemigrations  # 生成遷移檔案(模型類的資訊)
python manage.py migrate # 執行開始遷移(將模型類資訊轉換成資料庫表)

從資料庫遷移過程來看,遷移檔案的內容是 Django 與 Mysql 之間的溝通語言,Django 通過 ORM(物件關係對映)功能,把模型類生成的遷移檔案,對映到 Mysql 中,Mysql 通過遷移檔案內容,建立對應的資料庫表資訊。

ORM 的好處就是,我們不需要編寫 SQL 語句,而是通過 Django 的模型類,就可以在 Mysql 中建立和操作想要的表物件及其資料。

除了模型類中定義的表,初次執行遷移命令也會生成 Django 內建的表,這些表是服務於 Django 內建的功能。

2)Admin 站點增加專案資料

在應用目錄下的 admin.py 中,新增如下程式碼:

from django.contrib import admin
from .import models class ProjectAdmin(admin.ModelAdmin):
list_display = ("id", "name", "proj_owner", "test_owner", "dev_owner", "desc", "create_time", "update_time") admin.site.register(models.Project, ProjectAdmin)

admin.site.register() 方法把模型類 Project 和 ProjectAdmin 進行繫結,並註冊到 Admin 系統。通過 ProjectAdmin 類中的 list_display 欄位,定義了後臺 Admin 頁面展示 Project 表的欄位列表。

進入 Admin 頁面並新增資料:

可以看到,在應用名稱下展示了“專案資訊表”,接著可以依次點選“專案資訊表”—“ADD 專案資訊表 +”—填寫所需新增的專案資訊—“SAVE”,完成專案資訊的資料新增。

2.2 定義路由

新增專案管理頁面的路由資訊:

from django.urls import path
from . import views urlpatterns = [
path('',views.index),
path('login/', views.login),
path('logout/', views.logout),
path('project/', views.project, name="project"),
]

2.3 定義檢視函式

 1 from django.shortcuts import render, redirect
2 from django.contrib import auth # Django使用者認證(Auth)元件一般用在使用者的登入註冊上,用於判斷當前的使用者是否合法
3 from django.contrib.auth.decorators import login_required
4 from .form import UserForm
5 import traceback
6 from .models import Project
7
8
9 # 專案管理頁的檢視函式
10 @login_required
11 def project(request):
12 print("request.user.is_authenticated: ", request.user.is_authenticated) # 列印使用者是否已登入
13 projects = Project.objects.filter().order_by('-id') # 使用負id是為了倒序取出專案資料
14 print("projects:", projects) # 列印專案名稱
15 return render(request, 'project.html', {'projects': projects})
16
17
18 # 預設頁的檢視函式
19 @login_required
20 def index(request):
21 return render(request, 'index.html')
22
23
24 # 登入頁的檢視函式
25 def login(request):
26 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
27 if request.session.get('is_login', None):
28 return redirect('/')
29 # 如果是表單提交行為,則進行登入校驗
30 if request.method == "POST":
31 login_form = UserForm(request.POST)
32 message = "請檢查填寫的內容!"
33 if login_form.is_valid():
34 username = login_form.cleaned_data['username']
35 password = login_form.cleaned_data['password']
36 try:
37 # 使用django提供的身份驗證功能
38 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
39 if user is not None:
40 print("使用者【%s】登入成功" % username)
41 auth.login(request, user)
42 request.session['is_login'] = True
43 # 登入成功,跳轉主頁
44 return redirect('/')
45 else:
46 message = "使用者名稱不存在或者密碼不正確!"
47 except:
48 traceback.print_exc()
49 message = "登入程式出現異常"
50 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
51 else:
52 return render(request, 'login.html', locals())
53 # 不是表單提交,代表只是訪問登入頁
54 else:
55 login_form = UserForm()
56 return render(request, 'login.html', locals())
57
58
59 # 註冊頁的檢視函式
60 def register(request):
61 return render(request, 'register.html')
62
63
64 # 登出的檢視函式:重定向至login檢視函式
65 @login_required
66 def logout(request):
67 auth.logout(request)
68 request.session.flush()
69 return redirect("/login/")

2.4 定義模板檔案

1)新增 templates/project.html 模板,用於展示專案資訊:

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}專案{% endblock %}
4
5 {% block content %}
6 <div class="table-responsive">
7 <table class="table table-striped">
8 <thead>
9 <tr>
10 <th>id</th>
11 <th>專案名稱</th>
12 <th>專案負責人</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 project in projects %}
24 <tr>
25 <td>{{ project.id }}</td>
26 <td>{{ project.name }}</td>
27 <td>{{ project.proj_owner }}</td>
28 <td>{{ project.test_owner }}</td>
29 <td>{{ project.dev_owner }}</td>
30 <td>{{ project.desc }}</td>
31 <td>{{ project.create_time|date:"Y-n-d H:i" }}</td>
32 <td>{{ project.update_time|date:"Y-n-d H:i" }}</td>
33 <td><a href=""> 檢視</a></td>
34 </tr>
35 {% endfor %}
36 </tbody>
37 </table>
38 </div>
39 {% endblock %}

2)調整 base.html 模板:導航欄增加專案選單,把“主頁”選單改為“專案”,同時把“自動化測試平臺”選單連線調整為”/”,即首頁頁面。

 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 </ul>
42 <ul class="nav navbar-nav navbar-right">
43 {% if request.user.is_authenticated %}
44 <li><a href="#">當前線上:{{ request.user.username }}</a></li>
45 <li><a href="/logout">登出</a></li>
46 {% else %}
47 <li><a href="/login">登入</a></li>
48
49 {% endif %}
50 </ul>
51 </div><!-- /.navbar-collapse -->
52 </div><!-- /.container-fluid -->
53 </nav>
54
55 {% block content %}{% endblock %}
56
57
58 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
59 <script src="{% static 'js/jquery-3.4.1.js' %}"></script>
60 <!-- Include all compiled plugins (below), or include individual files as needed -->
61 <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
62 </body>
63 </html>

2.5 處理分頁

1)新增封裝分頁處理物件的檢視函式:

 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
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 @login_required
27 def project(request):
28 print("request.user.is_authenticated: ", request.user.is_authenticated)
29 projects = Project.objects.filter().order_by('-id')
30 print("projects:", projects)
31 return render(request, 'project.html', {'projects': get_paginator(request, projects)})
32
33
34 # 預設頁的檢視函式
35 @login_required
36 def index(request):
37 return render(request, 'index.html')
38
39
40 # 登入頁的檢視函式
41 def login(request):
42 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
43 if request.session.get('is_login', None):
44 return redirect('/')
45 # 如果是表單提交行為,則進行登入校驗
46 if request.method == "POST":
47 login_form = UserForm(request.POST)
48 message = "請檢查填寫的內容!"
49 if login_form.is_valid():
50 username = login_form.cleaned_data['username']
51 password = login_form.cleaned_data['password']
52 try:
53 # 使用django提供的身份驗證功能
54 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
55 if user is not None:
56 print("使用者【%s】登入成功" % username)
57 auth.login(request, user)
58 request.session['is_login'] = True
59 # 登入成功,跳轉主頁
60 return redirect('/')
61 else:
62 message = "使用者名稱不存在或者密碼不正確!"
63 except:
64 traceback.print_exc()
65 message = "登入程式出現異常"
66 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
67 else:
68 return render(request, 'login.html', locals())
69 # 不是表單提交,代表只是訪問登入頁
70 else:
71 login_form = UserForm()
72 return render(request, 'login.html', locals())
73
74
75 # 註冊頁的檢視函式
76 def register(request):
77 return render(request, 'register.html')
78
79
80 # 登出的檢視函式:重定向至login檢視函式
81 @login_required
82 def logout(request):
83 auth.logout(request)
84 request.session.flush()
85 return redirect("/login/")

2)修改 project.html 模板,新增分頁處理內容:

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}主頁{% endblock %}
4
5 {% block content %}
6 <div class="table-responsive">
7 <table class="table table-striped">
8 <thead>
9 <tr>
10 <th>id</th>
11 <th>專案名稱</th>
12 <th>專案負責人</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 project in projects %}
24 <tr>
25 <td>{{ project.id }}</td>
26 <td>{{ project.name }}</td>
27 <td>{{ project.proj_owner }}</td>
28 <td>{{ project.test_owner }}</td>
29 <td>{{ project.dev_owner }}</td>
30 <td>{{ project.desc }}</td>
31 <td>{{ project.create_time|date:"Y-n-d H:i" }}</td>
32 <td>{{ project.update_time|date:"Y-n-d H:i" }}</td>
33 <td><a href=""> 檢視</a></td>
34 </tr>
35 {% endfor %}
36 </tbody>
37 </table>
38 </div>
39
40 {# 實現分頁標籤的程式碼 #}
41 {# 這裡使用 bootstrap 渲染頁面 #}
42 <div id="pages" class="text-center">
43 <nav>
44 <ul class="pagination">
45 <li class="step-links">
46 {% if projects.has_previous %}
47 <a class='active' href="?page={{ projects.previous_page_number }}">上一頁</a>
48 {% endif %}
49
50 <span class="current">
51 第 {{ projects.number }} 頁 / 共 {{ projects.paginator.num_pages }} 頁</span>
52
53 {% if projects.has_next %}
54 <a class='active' href="?page={{ projects.next_page_number }}">下一頁</a>
55 {% endif %}
56 </li>
57 </ul>
58 </nav>
59 </div>
60 {% endblock %}

3. 模組

預期效果如下:

模組管理的實現思路與專案管理相似,我們依然遵循模型類 —> admin 增加資料 —> 路由 —> 檢視函式 —> 模板檔案的步驟來實現。

3.1 定義模型類

1)應用的 models.py 中增加 Module 模型類:

 1 from django.db import models
2
3
4 class Project(models.Model):
5 id = models.AutoField(primary_key=True)
6 name = models.CharField('專案名稱', max_length=50, unique=True, null=False)
7 proj_owner = models.CharField('專案負責人', max_length=20, null=False)
8 test_owner = models.CharField('測試負責人', max_length=20, null=False)
9 dev_owner = models.CharField('開發負責人', max_length=20, null=False)
10 desc = models.CharField('專案描述', max_length=100, null=True)
11 create_time = models.DateTimeField('專案建立時間', auto_now_add=True)
12 update_time = models.DateTimeField('專案更新時間', auto_now=True, null=True)
13
14 # 列印時返回專案名稱
15 def __str__(self):
16 return self.name
17
18 class Meta:
19 verbose_name = '專案資訊表'
20 verbose_name_plural = '專案資訊表'
21
22
23 class Module(models.Model):
24 id = models.AutoField(primary_key=True)
25 name = models.CharField('模組名稱', max_length=50, null=False)
26 belong_project = models.ForeignKey(Project, on_delete=models.CASCADE)
27 test_owner = models.CharField('測試負責人', max_length=50, null=False)
28 desc = models.CharField('簡要描述', max_length=100, null=True)
29 create_time = models.DateTimeField('建立時間', auto_now_add=True)
30 update_time = models.DateTimeField('更新時間', auto_now=True, null=True)
31
32 def __str__(self):
33 return self.name
34
35 class Meta:
36 verbose_name = '模組資訊表'
37 verbose_name_plural = '模組資訊表'

2)資料遷移

在專案目錄下,執行以下兩個命令進行資料遷移(將模型類轉換成資料庫表):

python manage.py makemigrations  # 生成遷移檔案(模型類的資訊)
python manage.py migrate # 執行開始遷移(將模型類資訊轉換成資料庫表)

3.2 後臺 admin 新增資料

1)註冊模型類到 admin:

 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)

2)登入 admin 系統,新增模組資料

訪問:http://127.0.0.1:8000/admin/,進入模組資訊表,新增資料。

3.3 定義路由

新增應用 urls.py 的路由配置:

from django.urls import 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"),
]

路由地址“module”對應的檢視函式,指向 views.py 中的 module 方法,下一步我們新增下該方法的處理。

3.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
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 @login_required
27 def project(request):
28 print("request.user.is_authenticated: ", request.user.is_authenticated)
29 projects = Project.objects.filter().order_by('-id')
30 print("projects:", projects)
31 return render(request, 'project.html', {'projects': get_paginator(request, projects)})
32
33
34 @login_required
35 def module(request):
36 if request.method == "GET": # 請求get時候,id倒序查詢所有的模組資料
37 modules = Module.objects.filter().order_by('-id')
38 return render(request, 'module.html', {'modules': get_paginator(request, modules)})
39 else: # 否則就是Post請求,會根據輸入內容,使用模糊的方式查詢所有的專案
40 proj_name = request.POST['proj_name']
41 projects = Project.objects.filter(name__contains=proj_name.strip())
42 projs = [proj.id for proj in projects]
43 modules = Module.objects.filter(belong_project__in=projs) # 把專案中所有的模組都找出來
44 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
45
46
47 # 預設頁的檢視函式
48 @login_required
49 def index(request):
50 return render(request, 'index.html')
51
52
53 # 登入頁的檢視函式
54 def login(request):
55 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
56 if request.session.get('is_login', None):
57 return redirect('/')
58 # 如果是表單提交行為,則進行登入校驗
59 if request.method == "POST":
60 login_form = UserForm(request.POST)
61 message = "請檢查填寫的內容!"
62 if login_form.is_valid():
63 username = login_form.cleaned_data['username']
64 password = login_form.cleaned_data['password']
65 try:
66 # 使用django提供的身份驗證功能
67 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
68 if user is not None:
69 print("使用者【%s】登入成功" % username)
70 auth.login(request, user)
71 request.session['is_login'] = True
72 # 登入成功,跳轉主頁
73 return redirect('/')
74 else:
75 message = "使用者名稱不存在或者密碼不正確!"
76 except:
77 traceback.print_exc()
78 message = "登入程式出現異常"
79 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
80 else:
81 return render(request, 'login.html', locals())
82 # 不是表單提交,代表只是訪問登入頁
83 else:
84 login_form = UserForm()
85 return render(request, 'login.html', locals())
86
87
88 # 註冊頁的檢視函式
89 def register(request):
90 return render(request, 'register.html')
91
92
93 # 登出的檢視函式:重定向至login檢視函式
94 @login_required
95 def logout(request):
96 auth.logout(request)
97 request.session.flush()
98 return redirect("/login/")

3.5 定義模板

1)新增 templates/module.html 模板

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}模組{% endblock %}
4
5 {% block content %}
6 <form action="{% url 'module'%}" method="POST">
7 {% csrf_token %}
8 <input style="margin-left: 5px;" type="text" name="proj_name" value="{{ proj_name }}" placeholder="輸入專案名稱搜尋模組">
9 <input type="submit" value="搜尋">
10 </form>
11
12 <div class="table-responsive">
13
14 <table class="table table-striped">
15 <thead>
16 <tr>
17 <th>id</th>
18 <th>模組名稱</th>
19 <th>所屬專案</th>
20 <th>測試負責人</th>
21 <th>模組描述</th>
22 <th>建立時間</th>
23 <th>更新時間</th>
24 <th>測試結果統計</th>
25 </tr>
26 </thead>
27 <tbody>
28
29 {% for module in modules %}
30 <tr>
31 <td>{{ module.id }}</td>
32 <td><a href="">{{ module.name }}</a></td>
33 <td>{{ module.belong_project.name }}</td>
34 <td>{{ module.test_owner }}</td>
35 <td>{{ module.desc }}</td>
36 <td>{{ module.create_time|date:"Y-n-d H:i" }}</td>
37 <td>{{ module.update_time|date:"Y-n-d H:i" }}</td>
38 <td><a href="">檢視</a></td>
39 </tr>
40 {% endfor %}
41
42 </tbody>
43 </table>
44 </div>
45
46 {# 實現分頁標籤的程式碼 #}
47 {# 這裡使用 bootstrap 渲染頁面 #}
48 <div id="pages" class="text-center">
49 <nav>
50 <ul class="pagination">
51 <li class="step-links">
52 {% if modules.has_previous %}
53 <a class='active' href="?page={{ modules.previous_page_number }}">上一頁</a>
54 {% endif %}
55
56 <span class="current">
57 第 {{ modules.number }} 頁 / 共 {{ modules.paginator.num_pages }} 頁</span>
58
59 {% if modules.has_next %}
60 <a class='active' href="?page={{ modules.next_page_number }}">下一頁</a>
61 {% endif %}
62 </li>
63 </ul>
64 </nav>
65 </div>
66 {% endblock %}

2)修改 base.html 模板,新增模組選單欄:

 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 </ul>
43 <ul class="nav navbar-nav navbar-right">
44 {% if request.user.is_authenticated %}
45 <li><a href="#">當前線上:{{ request.user.username }}</a></li>
46 <li><a href="/logout">登出</a></li>
47 {% else %}
48 <li><a href="/login">登入</a></li>
49
50 {% endif %}
51 </ul>
52 </div><!-- /.navbar-collapse -->
53 </div><!-- /.container-fluid -->
54 </nav>
55
56 {% block content %}{% endblock %}
57
58
59 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
60 <script src="{% static 'js/jquery-3.4.1.js' %}"></script>
61 <!-- Include all compiled plugins (below), or include individual files as needed -->
62 <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
63 </body>
64 </html>

4. 測試用例

4.1 定義模型類

1)在應用 models.py 中增加 TestCase 模型類:

 1 from django.db import models
2 from smart_selects.db_fields import GroupedForeignKey # 後臺級聯選擇
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 = '測試用例表'

GroupedForeignKey 可以支援在 admin 新增資料時,展示該模型類的關聯表資料。(需提前安裝:pip install django-smart-selects)

2)資料遷移

在專案目錄下,執行以下兩個命令進行資料遷移(將模型類轉換成資料庫表):

python manage.py makemigrations  # 生成遷移檔案(模型類的資訊)
python manage.py migrate # 執行開始遷移(將模型類資訊轉換成資料庫表)

4.2 後臺 admin 新增資料

1)註冊模型類到 admin

應用 admin.py 檔案中增加如下程式碼:註冊 TestCase 模型類到 admin 後臺系統。

 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)

2)登入 admin 系統,新增用例資料

訪問 http://127.0.0.1:8000/admin/,進入測試用例表,新增資料:

新增用例資料時,頁面如下:

  • 所屬專案和所屬模組下拉選項是根據模型類中的 GroupedForeignKey 屬性生成的,方便我們正確的關聯資料。
  • 請求資料、斷言內容、提取變量表達式等欄位的定義,需要根據介面業務邏輯,以及後續執行邏輯的設計來輸入。

新增資料後如下所示:

4.3 定義路由

應用 urls.py:

from django.urls import 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"),
]

4.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
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 index(request):
70 return render(request, 'index.html')
71
72
73 # 登入頁的檢視函式
74 def login(request):
75 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
76 if request.session.get('is_login', None):
77 return redirect('/')
78 # 如果是表單提交行為,則進行登入校驗
79 if request.method == "POST":
80 login_form = UserForm(request.POST)
81 message = "請檢查填寫的內容!"
82 if login_form.is_valid():
83 username = login_form.cleaned_data['username']
84 password = login_form.cleaned_data['password']
85 try:
86 # 使用django提供的身份驗證功能
87 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
88 if user is not None:
89 print("使用者【%s】登入成功" % username)
90 auth.login(request, user)
91 request.session['is_login'] = True
92 # 登入成功,跳轉主頁
93 return redirect('/')
94 else:
95 message = "使用者名稱不存在或者密碼不正確!"
96 except:
97 traceback.print_exc()
98 message = "登入程式出現異常"
99 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
100 else:
101 return render(request, 'login.html', locals())
102 # 不是表單提交,代表只是訪問登入頁
103 else:
104 login_form = UserForm()
105 return render(request, 'login.html', locals())
106
107
108 # 註冊頁的檢視函式
109 def register(request):
110 return render(request, 'register.html')
111
112
113 # 登出的檢視函式:重定向至login檢視函式
114 @login_required
115 def logout(request):
116 auth.logout(request)
117 request.session.flush()
118 return redirect("/login/")

4.5 定義模板檔案

1)新增 templates/test_case.html 模板:

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}測試用例{% endblock %}
4
5 {% block content %}
6 <form action="" method="POST">
7 {% csrf_token %}
8 <div class="table-responsive">
9 <table class="table table-striped">
10 <thead>
11 <tr>
12 <th>用例名稱</th>
13 <th>所屬專案</th>
14 <th>所屬模組</th>
15 <th>介面地址</th>
16 <th>請求方式</th>
17 <th>請求資料</th>
18 <th>斷言key</th>
19 <th>提取變量表達式</th>
20 </tr>
21 </thead>
22 <tbody>
23
24 {% for test_case in test_cases %}
25 <tr>
26 <td><a href="{% url 'test_case_detail' test_case.id%}">{{ test_case.case_name }}</a></td>
27 <td>{{ test_case.belong_project.name }}</td>
28 <td>{{ test_case.belong_module.name }}</td>
29 <td>{{ test_case.uri }}</td>
30 <td>{{ test_case.request_method }}</td>
31 <td>{{ test_case.request_data }}</td>
32 <td>{{ test_case.assert_key }}</td>
33 <td>{{ test_case.extract_var }}</td>
34 </tr>
35 {% endfor %}
36 </tbody>
37 </table>
38
39 </div>
40 </form>
41 {# 實現分頁標籤的程式碼 #}
42 {# 這裡使用 bootstrap 渲染頁面 #}
43 <div id="pages" class="text-center">
44 <nav>
45 <ul class="pagination">
46 <li class="step-links">
47 {% if test_cases.has_previous %}
48 <a class='active' href="?page={{ test_cases.previous_page_number }}">上一頁</a>
49 {% endif %}
50
51 <span class="current">
52 第 {{ test_cases.number }} 頁 / 共 {{ test_cases.paginator.num_pages }} 頁</span>
53
54 {% if test_cases.has_next %}
55 <a class='active' href="?page={{ test_cases.next_page_number }}">下一頁</a>
56 {% endif %}
57 </li>
58 </ul>
59 </nav>
60 </div>
61 {% endblock %}

2)修改 base.html 模板:增加測試用例選單。

 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 </ul>
44 <ul class="nav navbar-nav navbar-right">
45 {% if request.user.is_authenticated %}
46 <li><a href="#">當前線上:{{ request.user.username }}</a></li>
47 <li><a href="/logout">登出</a></li>
48 {% else %}
49 <li><a href="/login">登入</a></li>
50
51 {% endif %}
52 </ul>
53 </div><!-- /.navbar-collapse -->
54 </div><!-- /.container-fluid -->
55 </nav>
56
57 {% block content %}{% endblock %}
58
59
60 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
61 <script src="{% static 'js/jquery-3.4.1.js' %}"></script>
62 <!-- Include all compiled plugins (below), or include individual files as needed -->
63 <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
64 </body>
65 </html>

4.6 用例詳情

目前用例列表中的欄位僅包含用例的基本資訊,下面繼續加一下(點選用例名稱跳轉)用例詳情頁面,用於展示用例的全部欄位資訊,如建立時間、更新時間、維護人、建立人。

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"),
]

由於用例詳情路由地址需要傳路由變數“test_case_id”,該變數需要通過正則表示式進行匹配,在 Django2.0 後,路由地址用到正則時需要用到 re_path 來解析,便於 Django 正確的匹配檢視函式,以及在瀏覽器位址列正確的展示 url 地址。

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
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 index(request):
82 return render(request, 'index.html')
83
84
85 # 登入頁的檢視函式
86 def login(request):
87 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
88 if request.session.get('is_login', None):
89 return redirect('/')
90 # 如果是表單提交行為,則進行登入校驗
91 if request.method == "POST":
92 login_form = UserForm(request.POST)
93 message = "請檢查填寫的內容!"
94 if login_form.is_valid():
95 username = login_form.cleaned_data['username']
96 password = login_form.cleaned_data['password']
97 try:
98 # 使用django提供的身份驗證功能
99 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
100 if user is not None:
101 print("使用者【%s】登入成功" % username)
102 auth.login(request, user)
103 request.session['is_login'] = True
104 # 登入成功,跳轉主頁
105 return redirect('/')
106 else:
107 message = "使用者名稱不存在或者密碼不正確!"
108 except:
109 traceback.print_exc()
110 message = "登入程式出現異常"
111 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
112 else:
113 return render(request, 'login.html', locals())
114 # 不是表單提交,代表只是訪問登入頁
115 else:
116 login_form = UserForm()
117 return render(request, 'login.html', locals())
118
119
120 # 註冊頁的檢視函式
121 def register(request):
122 return render(request, 'register.html')
123
124
125 # 登出的檢視函式:重定向至login檢視函式
126 @login_required
127 def logout(request):
128 auth.logout(request)
129 request.session.flush()
130 return redirect("/login/")

3)新增 templates/test_case_detail.html 模板

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}用例詳情{% endblock %}
4
5 {% block content %}
6 <div class="table-responsive">
7 <table class="table table-striped">
8 <thead>
9 <tr>
10 <th width="3%">id</th>
11 <th width="4%">介面名稱</th>
12 <th width="6%">所屬專案</th>
13 <th width="6%">所屬模組</th>
14 <th width="6%">介面地址</th>
15 <th width="10%">請求資料</th>
16 <th width="8%">斷言內容</th>
17 <th width="4%">編寫人員</th>
18 <th width="8%">提取變量表達式</th>
19 <th width="4%">維護人</th>
20 <th width="4%">建立人</th>
21 <th width="6%">建立時間</th>
22 <th width="6%">更新時間</th>
23 </tr>
24 </thead>
25 <tbody>
26 <tr>
27 <td>{{ test_case.id }}</td>
28 <td>{{ test_case.case_name }}</td>
29 <td>{{ test_case.belong_project }}</td>
30 <td>{{ test_case.belong_module }}</td>
31 <td>{{ test_case.uri }}</td>
32 <td>{{ test_case.request_data }}</td>
33 <td>{{ test_case.assert_key }}</td>
34 <td>{{ test_case.maintainer }}</td>
35 <td>{{ test_case.extract_var }}</td>
36 <td>{{ test_case.maintainer }}</td>
37 <td>{{ test_case.user.username }}</td>
38 <td>{{ test_case.created_time|date:"Y-n-d H:i" }}</td>
39 <td>{{ test_case.updated_time|date:"Y-n-d H:i" }}</td>
40 </tr>
41 </tbody>
42 </table>
43 </div>
44 {% endblock %}

4)修改 test_case.html 模板

新增用例名稱的連結為用例詳情路由地址:

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}測試用例{% endblock %}
4
5 {% block content %}
6 <form action="" method="POST">
7 {% csrf_token %}
8 <div class="table-responsive">
9 <table class="table table-striped">
10 <thead>
11 <tr>
12 <th>用例名稱</th>
13 <th>所屬專案</th>
14 <th>所屬模組</th>
15 <th>介面地址</th>
16 <th>請求方式</th>
17 <th>請求資料</th>
18 <th>斷言key</th>
19 <th>提取變量表達式</th>
20 </tr>
21 </thead>
22 <tbody>
23
24 {% for test_case in test_cases %}
25 <tr>
26 <td><a href="{% url 'test_case_detail' test_case.id%}">{{ test_case.case_name }}</a></td>
27 <td>{{ test_case.belong_project.name }}</td>
28 <td>{{ test_case.belong_module.name }}</td>
29 <td>{{ test_case.uri }}</td>
30 <td>{{ test_case.request_method }}</td>
31 <td>{{ test_case.request_data }}</td>
32 <td>{{ test_case.assert_key }}</td>
33 <td>{{ test_case.extract_var }}</td>
34 </tr>
35 {% endfor %}
36 </tbody>
37 </table>
38
39 </div>
40 </form>
41 {# 實現分頁標籤的程式碼 #}
42 {# 這裡使用 bootstrap 渲染頁面 #}
43 <div id="pages" class="text-center">
44 <nav>
45 <ul class="pagination">
46 <li class="step-links">
47 {% if test_cases.has_previous %}
48 <a class='active' href="?page={{ test_cases.previous_page_number }}">上一頁</a>
49 {% endif %}
50
51 <span class="current">
52 第 {{ test_cases.number }} 頁 / 共 {{ test_cases.paginator.num_pages }} 頁</span>
53
54 {% if test_cases.has_next %}
55 <a class='active' href="?page={{ test_cases.next_page_number }}">下一頁</a>
56 {% endif %}
57 </li>
58 </ul>
59 </nav>
60 </div>
61 {% endblock %}

4.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"),
]

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
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 index(request):
93 return render(request, 'index.html')
94
95
96 # 登入頁的檢視函式
97 def login(request):
98 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
99 if request.session.get('is_login', None):
100 return redirect('/')
101 # 如果是表單提交行為,則進行登入校驗
102 if request.method == "POST":
103 login_form = UserForm(request.POST)
104 message = "請檢查填寫的內容!"
105 if login_form.is_valid():
106 username = login_form.cleaned_data['username']
107 password = login_form.cleaned_data['password']
108 try:
109 # 使用django提供的身份驗證功能
110 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
111 if user is not None:
112 print("使用者【%s】登入成功" % username)
113 auth.login(request, user)
114 request.session['is_login'] = True
115 # 登入成功,跳轉主頁
116 return redirect('/')
117 else:
118 message = "使用者名稱不存在或者密碼不正確!"
119 except:
120 traceback.print_exc()
121 message = "登入程式出現異常"
122 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
123 else:
124 return render(request, 'login.html', locals())
125 # 不是表單提交,代表只是訪問登入頁
126 else:
127 login_form = UserForm()
128 return render(request, 'login.html', locals())
129
130
131 # 註冊頁的檢視函式
132 def register(request):
133 return render(request, 'register.html')
134
135
136 # 登出的檢視函式:重定向至index檢視函式
137 @login_required
138 def logout(request):
139 auth.logout(request)
140 request.session.flush()
141 return redirect("/login/")

3)修改模組的模板檔案:在模組名稱連結中,新增對應測試用例的路由地址。

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}模組{% endblock %}
4
5 {% block content %}
6 <form action="{% url 'module'%}" method="POST">
7 {% csrf_token %}
8 <input style="margin-left: 5px;" type="text" name="proj_name" value="{{ proj_name }}" placeholder="輸入專案名稱搜尋模組">
9 <input type="submit" value="搜尋">
10 </form>
11
12 <div class="table-responsive">
13
14 <table class="table table-striped">
15 <thead>
16 <tr>
17 <th>id</th>
18 <th>模組名稱</th>
19 <th>所屬專案</th>
20 <th>測試負責人</th>
21 <th>模組描述</th>
22 <th>建立時間</th>
23 <th>更新時間</th>
24 <th>測試結果統計</th>
25 </tr>
26 </thead>
27 <tbody>
28
29 {% for module in modules %}
30 <tr>
31 <td>{{ module.id }}</td>
32 <td><a href="{% url 'module_test_cases' module.id %}">{{ module.name }}</a></td>
33 <td>{{ module.belong_project.name }}</td>
34 <td>{{ module.test_owner }}</td>
35 <td>{{ module.desc }}</td>
36 <td>{{ module.create_time|date:"Y-n-d H:i" }}</td>
37 <td>{{ module.update_time|date:"Y-n-d H:i" }}</td>
38 <td><a href="">檢視</a></td>
39 </tr>
40 {% endfor %}
41
42 </tbody>
43 </table>
44 </div>
45
46 {# 實現分頁標籤的程式碼 #}
47 {# 這裡使用 bootstrap 渲染頁面 #}
48 <div id="pages" class="text-center">
49 <nav>
50 <ul class="pagination">
51 <li class="step-links">
52 {% if modules.has_previous %}
53 <a class='active' href="?page={{ modules.previous_page_number }}">上一頁</a>
54 {% endif %}
55
56 <span class="current">
57 第 {{ modules.number }} 頁 / 共 {{ modules.paginator.num_pages }} 頁</span>
58
59 {% if modules.has_next %}
60 <a class='active' href="?page={{ modules.next_page_number }}">下一頁</a>
61 {% endif %}
62 </li>
63 </ul>
64 </nav>
65 </div>
66 {% endblock %}

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 %}

8. 測試用例執行

預期效果如下:

用例執行邏輯如下:

  1. 前端提交用例 id 列表到後臺,後臺獲取每一條用例的資訊;
  2. 後臺獲取域名資訊、用例 id 列表;
  3. 對用例的請求資料進行變數的引數化、函式化等預處理操作;
  4. 根據先後順序進行介面請求,並對響應資料進行斷言;
  5. 根據用例中的提取變量表達式,從斷言成功的響應資料中提取關聯變數值用於後續用例使用。

8.1 修改測試用例頁模板檔案:前端提交用例資訊

templates/test_case.html:

  1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}測試用例{% endblock %}
4
5 {% block content %}
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 //3.若checked=true,將所有的複選框選中;checked=false,將所有的複選框取消
12 var allCheck = document.getElementsByName("test_cases_list");
13 //4.迴圈遍歷取出每一個複選框中的元素
14 if (all_check)//全選
15 {
16 for (var i = 0; i < allCheck.length; i++) {
17 //設定複選框的選中狀態
18 allCheck[i].checked = true;
19 }
20 } else//取消全選
21 {
22 for (var i = 0; i < allCheck.length; i++) {
23 allCheck[i].checked = false;
24 }
25 }
26 }
27
28 function ischecked() {
29 //3.若checked=true,將所有的複選框選中,checked=false,將所有的複選框取消
30 var allCheck = document.getElementsByName("test_cases_list");
31 for (var i = 0; i < allCheck.length; i++) {
32 if (allCheck[i].checked == true) {
33 alert("所需執行的測試用例提交成功!");
34 return true
35 }
36 }
37 alert("請選擇要執行的測試用例!")
38 return false
39 }
40
41 </script>
42
43 <form action="" method="POST">
44 {% csrf_token %}
45 <input style="margin-left: 5px;" type="submit" value='執行測試用例' onclick="return ischecked()"/>
46 <span style="margin-left: 5px;">執行環境:</span>
47 <select name="env">
48 <option selected value="dev">dev</option>
49 <option value="prod">prod</option>
50 </select>
51 <div class="table-responsive">
52 <table class="table table-striped">
53 <thead>
54 <tr>
55 <th><input type="checkbox" id="all_check" onclick="checkOrCancelAll();"/>全選</th>
56 <th>用例名稱</th>
57 <th>所屬專案</th>
58 <th>所屬模組</th>
59 <th>介面地址</th>
60 <th>請求方式</th>
61 <th>請求資料</th>
62 <th>斷言key</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.id }}" name="test_cases_list"> {{ test_case.id }}</td>
71 <td><a href="{% url 'test_case_detail' test_case.id%}">{{ test_case.case_name }}</a></td>
72 <td>{{ test_case.belong_project.name }}</td>
73 <td>{{ test_case.belong_module.name }}</td>
74 <td>{{ test_case.uri }}</td>
75 <td>{{ test_case.request_method }}</td>
76 <td>{{ test_case.request_data }}</td>
77 <td>{{ test_case.assert_key }}</td>
78 <td>{{ test_case.extract_var }}</td>
79 </tr>
80 {% endfor %}
81 </tbody>
82 </table>
83
84 </div>
85 </form>
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 {% endblock %}

8.2 定義介面地址模型類

models.py:

  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) # 建立時間-自動獲取當前時間
84
85
86 class InterfaceServer(models.Model):
87 id = models.AutoField(primary_key=True)
88 env = models.CharField('環境', max_length=50, null=False, default='')
89 ip = models.CharField('ip', max_length=50, null=False, default='')
90 port = models.CharField('埠', max_length=100, null=False, default='')
91 remark = models.CharField('備註', max_length=100, null=True)
92 create_time = models.DateTimeField('建立時間', auto_now_add=True)
93 update_time = models.DateTimeField('更新時間', auto_now=True, null=True)
94
95 def __str__(self):
96 return self.env
97
98 class Meta:
99 verbose_name = '介面地址配置表'
100 verbose_name_plural = '介面地址配置表'

執行資料遷移:

python manage.py makemigrations
python manage.py migrate

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)
29
30
31 class InterfaceServerAdmin(admin.ModelAdmin):
32 list_display = ("id", "env", "ip", "port", "remark", "create_time")
33
34 admin.site.register(models.InterfaceServer, InterfaceServerAdmin)

登入 admin 系統,新增地址配置資料:

8.3 修改測試用例檢視函式,後臺執行用例

1)Redis 持久化遞增唯一數

在本工程中,我們使用 Redis 來維護一個每次呼叫函式來就會遞增的數值,供註冊介面的註冊使用者名稱拼接使用,避免註冊介面請求資料重複使用問題。

1.1)Redis 持久化配置

修改 redis.windows.conf:

appendonly yes  # 每次更新操作後進行日誌記錄
appendfsync everysec # 每秒同步一次(預設值)

1.2)啟動 Redis 服務端:

redis-server.exe redis.windows.conf

2)請求/響應資料處理

在應用目錄下新建 utils 包,用於封裝介面請求的相關函式。

data_process.py

該模組實現了對介面請求的所需工具函式,如獲取遞增唯一數(供註冊使用者名稱使用)、md5 加密(用於登入密碼加密)、請求資料預處理、響應資料斷言等功能。

  • get_unique_num_value():用於獲取每次遞增的唯一數

    • 該函式的目標是解決註冊使用者名稱重複的問題。
    • 雖然可以在賦值註冊使用者名稱變數時,採用字首字串拼接隨機數的方式,但是用隨機數的方式仍然是有可能出現使用者名稱重複的情況。因此,可以在單獨的一個檔案中維護一個數字,每次請求註冊介面之前,先讀取該檔案中的數字,拼接使用者名稱字首字串。讀取完之後,再把這個數字進行加一的操作並儲存,即每讀取一次這個數字之後,就做一次修改,進而保證每次拼接的使用者名稱都是唯一的,避免出現因為使用者名稱重複導致用例執行失敗的情況。
  • data_preprocess():對請求資料進行預處理:引數化及函式化。
  • data_postprocess():將響應資料需要關聯的引數儲存進全域性變數,供後續介面使用。
  • assert_result():對響應資料進行關鍵字斷言。
  1 import re
2 import hashlib
3 import os
4 import json
5 import traceback
6 import redis
7 from InterfaceAutoTest.settings import redis_port
8
9
10 # 連線redis
11 pool = redis.ConnectionPool(host='localhost', port=redis_port, decode_responses=True)
12 redis_obj = redis.Redis(connection_pool=pool)
13
14
15 # 初始化框架工程中的全域性變數,儲存在測試資料中的唯一值資料
16 # 框架工程中若要使用字典中的任意一個變數,則每次使用後,均需要將字典中的value值進行加1操作。
17 def get_unique_number_value(unique_number):
18 data = None
19 try:
20 redis_value = redis_obj.get(unique_number) # {"unique_number": 666}
21 if redis_value:
22 data = redis_value
23 print("全域性唯一數當前生成的值是:%s" % data)
24 # 把redis中key為unique_number的值進行加一操作,以便下提取時保持唯一
25 redis_obj.set(unique_number, int(redis_value) + 1)
26 else:
27 data = 1000 # 初始化遞增數值
28 redis_obj.set(unique_number, data)
29 except Exception as e:
30 print("獲取全域性唯一數變數值失敗,請求的全域性唯一數變數是%s,異常原因如下:%s" % (unique_number, traceback.format_exc()))
31 data = None
32 finally:
33 return data
34
35
36 def md5(s):
37 m5 = hashlib.md5()
38 m5.update(s.encode("utf-8"))
39 md5_value = m5.hexdigest()
40 return md5_value
41
42
43 # 請求資料預處理:引數化、函式化
44 # 將請求資料中包含的${變數名}的字串部分,替換為唯一數或者全域性變數字典中對應的全域性變數
45 def data_preprocess(global_key, requestData):
46 try:
47 # 匹配註冊使用者名稱引數,即"${unique_num...}"的格式,並取出本次請求的隨機數供後續介面的使用者名稱引數使用
48 if re.search(r"\$\{unique_num\d+\}", requestData):
49 var_name = re.search(r"\$\{(unique_num\d+)\}", requestData).group(1) # 獲取使用者名稱引數
50 print("使用者名稱變數:%s" % var_name)
51 var_value = get_unique_number_value(var_name)
52 print("使用者名稱變數值: %s" % var_value)
53 requestData = re.sub(r"\$\{unique_num\d+\}", str(var_value), requestData)
54 var_name = var_name.split("_")[1]
55 print("關聯的使用者名稱變數: %s" % var_name)
56 # "xxxkey" : "{'var_name': var_value}"
57 global_var = json.loads(os.environ[global_key])
58 global_var[var_name] = var_value
59 os.environ[global_key] = json.dumps(global_var)
60 print("使用者名稱唯一數引數化後的全域性變數【os.environ[global_key]】: {}".format(os.environ[global_key]))
61 # 函式化,如密碼加密"${md5(...)}"的格式
62 if re.search(r"\$\{\w+\(.+\)\}", requestData):
63 var_pass = re.search(r"\$\{(\w+\(.+\))\}", requestData).group(1) # 獲取密碼引數
64 print("需要函式化的變數: %s" % var_pass)
65 print("函式化後的結果: %s" % eval(var_pass))
66 requestData = re.sub(r"\$\{\w+\(.+\)\}", eval(var_pass), requestData) # 將requestBody裡面的引數內容通過eval修改為實際變數值
67 print("函式化後的請求資料: %s" % requestData) # requestBody是拿到的請求時傳送的資料
68 # 其餘變數引數化
69 if re.search(r"\$\{(\w+)\}", requestData):
70 print("需要引數化的變數: %s" % (re.findall(r"\$\{(\w+)\}", requestData)))
71 for var_name in re.findall(r"\$\{(\w+)\}", requestData):
72 requestData = re.sub(r"\$\{%s\}" % var_name, str(json.loads(os.environ[global_key])[var_name]), requestData)
73 print("變數引數化後的最終請求資料: %s" % requestData)
74 print("資料引數後的最終全域性變數【os.environ[global_key]】: {}".format(os.environ[global_key]))
75 return 0, requestData, ""
76 except Exception as e:
77 print("請求資料預處理髮生異常,error:{}".format(traceback.format_exc()))
78 return 1, {}, traceback.format_exc()
79
80
81 # 響應資料提取關聯引數
82 def data_postprocess(global_key, response_data, extract_var):
83 print("需提取的關聯變數:%s" % extract_var)
84 var_name = extract_var.split("||")[0]
85 print("關聯變數名:%s" % var_name)
86 regx_exp = extract_var.split("||")[1]
87 print("關聯變數正則:%s" % regx_exp)
88 if re.search(regx_exp, response_data):
89 global_vars = json.loads(os.environ[global_key])
90 print("關聯前的全域性變數:{}".format(global_vars))
91 global_vars[var_name] = re.search(regx_exp, response_data).group(1)
92 os.environ[global_key] = json.dumps(global_vars)
93 print("關聯前的全域性變數:{}".format(os.environ[global_key]))
94 return
95
96
97 # 響應資料 斷言處理
98 def assert_result(response_obj, key_word):
99 try:
100 # 多個斷言關鍵字
101 if '&&' in key_word:
102 key_word_list = key_word.split('&&')
103 print("斷言關鍵字列表:%s" % key_word_list)
104 # 斷言結果識別符號
105 flag = True
106 exception_info = ''
107 # 遍歷分隔出來的斷言關鍵詞列表
108 for key_word in key_word_list:
109 # 如果斷言詞非空,則進行斷言
110 if key_word:
111 # 沒查到斷言詞則認為是斷言失敗
112 if not (key_word in json.dumps(response_obj.json(), ensure_ascii=False)):
113 print("斷言關鍵字【{}】匹配失敗".format(key_word))
114 flag = False # 只要有一個斷言詞匹配失敗,則整個介面斷言失敗
115 exception_info = "keyword: {} not matched from response, assert failed".format(key_word)
116 else:
117 print("斷言關鍵字【{}】匹配成功".format(key_word))
118 if flag:
119 print("介面斷言成功!")
120 else:
121 print("介面斷言失敗!")
122 return flag, exception_info
123 # 單個斷言關鍵字
124 else:
125 if key_word in json.dumps(response_obj.json(), ensure_ascii=False):
126 print("介面斷言【{}】匹配成功!".format(key_word))
127 return True, ''
128 else:
129 print("介面斷言【{}】匹配失敗!".format(key_word))
130 return False, ''
131 except Exception as e:
132 return False, traceback.format_exc()
133
134
135 # 測試程式碼
136 if __name__ == "__main__":
137 print(get_unique_number_value("unique_num1"))

request_process.py

該模組實現了對介面請求的封裝。

 1 import requests
2 import json
3 # from Util.Log import logger
4
5
6 # 此函式封裝了get請求、post和put請求的方法
7 def request_process(url, request_method, request_content):
8 print("-------- 開始呼叫介面 --------")
9 if request_method == "get":
10 try:
11 if isinstance(request_content, dict):
12 print("介面地址:%s" % url)
13 print("請求資料:%s" % request_content)
14 r = requests.get(url, params=json.dumps(request_content))
15 else:
16 r = requests.get(url+str(request_content))
17 print("介面地址:%s" % r.url)
18 print("請求資料:%s" % request_content)
19
20 except Exception as e:
21 print("get方法請求發生異常:請求的url是%s, 請求的內容是%s\n發生的異常資訊如下:%s" % (url, request_content, e))
22 r = None
23 return r
24 elif request_method == "post":
25 try:
26 if isinstance(request_content, dict):
27 print("介面地址:%s" % url)
28 print("請求資料:%s" % json.dumps(request_content))
29 r = requests.post(url, data=json.dumps(request_content))
30 else:
31 raise ValueError
32 except ValueError as e:
33 print("post方法請求發生異常:請求的url是%s, 請求的內容是%s\n發生的異常資訊如下:%s" % (url, request_content, "請求引數不是字典型別"))
34 r = None
35 except Exception as e:
36 print("post方法請求發生異常:請求的url是%s, 請求的內容是%s\n發生的異常資訊如下:%s" % (url, request_content, e))
37 r = None
38 return r
39 elif request_method == "put":
40 try:
41 if isinstance(request_content, dict):
42 print("介面地址:%s" % url)
43 print("請求資料:%s" % json.dumps(request_content))
44 r = requests.put(url, data=json.dumps(request_content))
45 else:
46 raise ValueError
47 except ValueError as e:
48 print("put方法請求發生異常:請求的url是%s, 請求的內容是%s\n發生的異常資訊如下:%s" % (url, request_content, "請求引數不是字典型別"))
49 r = None
50 except Exception as e:
51 print("put方法請求發生異常:請求的url是%s, 請求的內容是%s\n發生的異常資訊如下:%s" % (url, request_content, e))
52 r = None
53 return r

3)封裝介面用例執行方法

在應用目錄下新建 task.py:

 1 import time
2 import os
3 import traceback
4 import json
5 from . import models
6 from .utils.data_process import data_preprocess, assert_result, data_postprocess
7 from .utils.request_process import request_process
8
9
10 def case_task(test_case_id_list, server_address):
11 global_key = 'case'+ str(int(time.time() * 100000))
12 os.environ[global_key] = '{}'
13 print()
14 print("全域性變數識別符號【global_key】: {}".format(global_key))
15 print("全域性變數內容【os.environ[global_key]】: {}".format(os.environ[global_key]))
16 for test_case_id in test_case_id_list:
17 print()
18 test_case = models.TestCase.objects.filter(id=int(test_case_id))[0]
19 print("######### 開始執行用例【{}】 #########".format(test_case))
20 execute_start_time = time.time() # 記錄時間戳,便於計算總耗時(毫秒)
21 request_data = test_case.request_data
22 extract_var = test_case.extract_var
23 assert_key = test_case.assert_key
24 interface_name = test_case.uri
25 belong_project = test_case.belong_project
26 belong_module = test_case.belong_module
27 maintainer = test_case.maintainer
28 request_method = test_case.request_method
29 print("初始請求資料: {}".format(request_data))
30 print("關聯引數: {}".format(extract_var))
31 print("斷言關鍵字: {}".format(assert_key))
32 print("介面名稱: {}".format(interface_name))
33 print("所屬專案: {}".format(belong_project))
34 print("所屬模組: {}".format(belong_module))
35 print("用例維護人: {}".format(maintainer))
36 print("請求方法: {}".format(request_method))
37 url = "{}{}".format(server_address, interface_name)
38 print("介面地址: {}".format(url))
39 code, request_data, error_msg = data_preprocess(global_key, str(request_data))
40 try:
41 res_data = request_process(url, request_method, json.loads(request_data))
42 print("響應資料: {}".format(json.dumps(res_data.json(), ensure_ascii=False))) # ensure_ascii:相容中文
43 result_flag, exception_info = assert_result(res_data, assert_key)
44 if result_flag:
45 print("用例【%s】執行成功!" % test_case)
46 if extract_var.strip() != "None":
47 data_postprocess(global_key, json.dumps(res_data.json(), ensure_ascii=False), extract_var)
48 else:
49 print("用例【%s】執行失敗!" % test_case)
50 except Exception as e:
51 print("介面請求異常,error: {}".format(traceback.format_exc()))

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, 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: {}".format(test_cases))
76 elif request.method == "POST":
77 print("request.POST: {}".format(request.POST))
78 test_case_id_list = request.POST.getlist('test_cases_list')
79 env = request.POST.getlist('env')
80 print("env: {}".format(env))
81 server_address = get_server_address(env)
82 if not server_address:
83 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
84 if test_case_id_list:
85 test_case_id_list.sort()
86 print("test_case_id_list: {}".format(test_case_id_list))
87 print("獲取到用例,開始用例執行")
88 case_task(test_case_id_list, server_address)
89 else:
90 print("執行測試用例失敗")
91 return HttpResponse("提交的執行測試用例為空,請選擇用例後在提交!")
92 test_cases = TestCase.objects.filter().order_by('id')
93 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
94
95
96 # 用例詳情頁
97 @login_required
98 def test_case_detail(request, test_case_id):
99 test_case_id = int(test_case_id)
100 test_case = TestCase.objects.get(id=test_case_id)
101 print("test_case: {}".format(test_case))
102 print("test_case.id: {}".format(test_case.id))
103 print("test_case.belong_project: {}".format(test_case.belong_project))
104
105 return render(request, 'test_case_detail.html', {'test_case': test_case})
106
107
108 # 模組頁展示測試用例
109 @login_required
110 def module_test_cases(request, module_id):
111 module = ""
112 if module_id: # 訪問的時候,會從url中提取模組的id,根據模組id查詢到模組資料,在模板中展現
113 module = Module.objects.get(id=int(module_id))
114 test_cases = TestCase.objects.filter(belong_module=module).order_by('-id')
115 print("test_case in module_test_cases: {}".format(test_cases))
116 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
117
118
119 # 用例集合選單項
120 @login_required
121 def case_suite(request):
122 case_suites = CaseSuite.objects.filter()
123 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
124
125
126 # 用例集合-新增測試用例頁
127 @login_required
128 def add_case_in_suite(request, suite_id):
129 # 查詢指定的用例集合
130 case_suite = CaseSuite.objects.get(id=suite_id)
131 # 根據id號查詢所有的用例
132 test_cases = TestCase.objects.filter().order_by('id')
133 if request.method == "GET":
134 print("test cases:", test_cases)
135 elif request.method == "POST":
136 test_cases_list = request.POST.getlist('testcases_list')
137 # 如果頁面勾選了用例
138 if test_cases_list:
139 print("勾選用例id:", test_cases_list)
140 # 根據頁面勾選的用例與查詢出的所有用例一一比較
141 for test_case in test_cases_list:
142 test_case = TestCase.objects.get(id=int(test_case))
143 # 匹配成功則新增用例
144 SuiteCase.objects.create(case_suite=case_suite, test_case=test_case)
145 # 未勾選用例
146 else:
147 print("新增測試用例失敗")
148 return HttpResponse("新增的測試用例為空,請選擇用例後再新增!")
149 return render(request, 'add_case_in_suite.html',
150 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
151
152
153 # 用例集合頁-檢視/刪除用例
154 @login_required
155 def show_and_delete_case_in_suite(request, suite_id):
156 case_suite = CaseSuite.objects.get(id=suite_id)
157 test_cases = SuiteCase.objects.filter(case_suite=case_suite)
158 if request.method == "POST":
159 test_cases_list = request.POST.getlist('test_cases_list')
160 if test_cases_list:
161 print("勾選用例:", test_cases_list)
162 for test_case in test_cases_list:
163 test_case = TestCase.objects.get(id=int(test_case))
164 SuiteCase.objects.filter(case_suite=case_suite, test_case=test_case).first().delete()
165 else:
166 print("測試用例刪除失敗")
167 return HttpResponse("所選測試用例為空,請選擇用例後再進行刪除!")
168 case_suite = CaseSuite.objects.get(id=suite_id)
169 return render(request, 'show_and_delete_case_in_suite.html',
170 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
171
172
173 # 預設頁的檢視函式
174 @login_required
175 def index(request):
176 return render(request, 'index.html')
177
178
179 # 登入頁的檢視函式
180 def login(request):
181 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
182 if request.session.get('is_login', None):
183 return redirect('/')
184 # 如果是表單提交行為,則進行登入校驗
185 if request.method == "POST":
186 login_form = UserForm(request.POST)
187 message = "請檢查填寫的內容!"
188 if login_form.is_valid():
189 username = login_form.cleaned_data['username']
190 password = login_form.cleaned_data['password']
191 try:
192 # 使用django提供的身份驗證功能
193 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
194 if user is not None:
195 print("使用者【%s】登入成功" % username)
196 auth.login(request, user)
197 request.session['is_login'] = True
198 # 登入成功,跳轉主頁
199 return redirect('/')
200 else:
201 message = "使用者名稱不存在或者密碼不正確!"
202 except:
203 traceback.print_exc()
204 message = "登入程式出現異常"
205 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
206 else:
207 return render(request, 'login.html', locals())
208 # 不是表單提交,代表只是訪問登入頁
209 else:
210 login_form = UserForm()
211 return render(request, 'login.html', locals())
212
213
214 # 註冊頁的檢視函式
215 def register(request):
216 return render(request, 'register.html')
217
218
219 # 登出的檢視函式:重定向至login檢視函式
220 @login_required
221 def logout(request):
222 auth.logout(request)
223 request.session.flush()
224 return redirect("/login/")

9. 用例執行結果展示

9.1 定義模型類

1)models.py 中增加 TestCaseExecuteResult 模型類,用於記錄用例執行結果。

  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) # 建立時間-自動獲取當前時間
84
85
86 class InterfaceServer(models.Model):
87 id = models.AutoField(primary_key=True)
88 env = models.CharField('環境', max_length=50, null=False, default='')
89 ip = models.CharField('ip', max_length=50, null=False, default='')
90 port = models.CharField('埠', max_length=100, null=False, default='')
91 remark = models.CharField('備註', max_length=100, null=True)
92 create_time = models.DateTimeField('建立時間', auto_now_add=True)
93 update_time = models.DateTimeField('更新時間', auto_now=True, null=True)
94
95 def __str__(self):
96 return self.env
97
98 class Meta:
99 verbose_name = '介面地址配置表'
100 verbose_name_plural = '介面地址配置表'
101
102
103 class TestCaseExecuteResult(models.Model):
104 id = models.AutoField(primary_key=True)
105 belong_test_case = GroupedForeignKey(TestCase, "belong_test_case", on_delete=models.CASCADE, verbose_name='所屬用例')
106 status = models.IntegerField(null=True, help_text="0:表示未執行,1:表示已執行")
107 exception_info = models.CharField(max_length=2048, blank=True, null=True)
108 request_data = models.CharField('請求體', max_length=1024, null=True) # {"code": "00", "userid": 22889}
109 response_data = models.CharField('響應字串', max_length=1024, null=True) # {"code": "00", "userid": 22889}
110 execute_result = models.CharField('執行結果', max_length=1024, null=True) # 成功/失敗
111 extract_var = models.CharField('關聯引數', max_length=1024, null=True) # 響應成功後提取變數
112 last_time_response_data = models.CharField('上一次響應字串', max_length=1024, null=True) # {"code": "00", "userid": 22889}
113 execute_total_time = models.CharField('執行耗時', max_length=1024, null=True)
114 execute_start_time = models.CharField('執行開始時間', max_length=300, blank=True, null=True)
115 execute_end_time = models.CharField('執行結束時間', max_length=300, blank=True, null=True)
116 created_time = models.DateTimeField('建立時間', auto_now_add=True)
117 updated_time = models.DateTimeField('更新時間', auto_now=True, null=True)
118
119 def __str__(self):
120 return str(self.id)
121
122 class Meta:
123 verbose_name = '用例執行結果記錄表'
124 verbose_name_plural = '用例執行結果記錄表'

2)資料遷移

python manage.py makemigrations
python manage.py migrate

9.2 修改用例執行封裝函式,增加執行結果記錄

修改應用目錄下 task.py:

  1 import time
2 import os
3 import traceback
4 import json
5 from . import models
6 from .utils.data_process import data_preprocess, assert_result, data_postprocess
7 from .utils.request_process import request_process
8
9
10 def case_task(test_case_id_list, server_address):
11 global_key = 'case'+ str(int(time.time() * 100000))
12 os.environ[global_key] = '{}'
13 print()
14 print("全域性變數識別符號【global_key】: {}".format(global_key))
15 print("全域性變數內容【os.environ[global_key]】: {}".format(os.environ[global_key]))
16 for test_case_id in test_case_id_list:
17
18 test_case = models.TestCase.objects.filter(id=int(test_case_id))[0]
19 last_execute_record_data = models.TestCaseExecuteResult.objects.filter(
20 belong_test_case_id=test_case_id).order_by('-id')
21 if last_execute_record_data:
22 last_time_execute_response_data = last_execute_record_data[0].response_data
23 else:
24 last_time_execute_response_data = ''
25 print("上一次響應結果: {}".format(last_execute_record_data))
26 print("上一次響應時間: {}".format(last_time_execute_response_data))
27 execute_record = models.TestCaseExecuteResult.objects.create(belong_test_case=test_case)
28 execute_record.last_time_response_data = last_time_execute_response_data
29 # 獲取當前用例上一次執行結果
30 execute_record.save()
31
32 test_case = models.TestCase.objects.filter(id=int(test_case_id))[0]
33 print("\n######### 開始執行用例【{}】 #########".format(test_case))
34 execute_start_time = time.time() # 記錄時間戳,便於計算總耗時(毫秒)
35 execute_record.execute_start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(execute_start_time))
36
37 request_data = test_case.request_data
38 extract_var = test_case.extract_var
39 assert_key = test_case.assert_key
40 interface_name = test_case.uri
41 belong_project = test_case.belong_project
42 belong_module = test_case.belong_module
43 maintainer = test_case.maintainer
44 request_method = test_case.request_method
45 print("初始請求資料: {}".format(request_data))
46 print("關聯引數: {}".format(extract_var))
47 print("斷言關鍵字: {}".format(assert_key))
48 print("介面名稱: {}".format(interface_name))
49 print("所屬專案: {}".format(belong_project))
50 print("所屬模組: {}".format(belong_module))
51 print("用例維護人: {}".format(maintainer))
52 print("請求方法: {}".format(request_method))
53 url = "{}{}".format(server_address, interface_name)
54 print("介面地址: {}".format(url))
55 code, request_data, error_msg = data_preprocess(global_key, str(request_data))
56 # 請求資料預處理異常,結束用例執行
57 if code != 0:
58 print("資料處理異常,error: {}".format(error_msg))
59 execute_record.execute_result = "失敗"
60 execute_record.status = 1
61 execute_record.exception_info = error_msg
62 execute_end_time = time.time()
63 execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(execute_end_time))
64 execute_record.execute_total_time = int(execute_end_time - execute_start_time) * 1000
65 execute_record.save()
66 return
67 # 記錄請求預處理結果
68 else:
69 execute_record.request_data = request_data
70 # 呼叫介面
71 try:
72 res_data = request_process(url, request_method, json.loads(request_data))
73 print("響應資料: {}".format(json.dumps(res_data.json(), ensure_ascii=False))) # ensure_ascii:相容中文
74 result_flag, exception_info = assert_result(res_data, assert_key)
75 # 結果記錄儲存
76 if result_flag:
77 print("用例【%s】執行成功!" % test_case)
78 execute_record.execute_result = "成功"
79 if extract_var.strip() != "None":
80 var_value = data_postprocess(global_key, json.dumps(res_data.json(), ensure_ascii=False), extract_var)
81 execute_record.extract_var = var_value
82 else:
83 print("用例【%s】執行失敗!" % test_case)
84 execute_record.execute_result = "失敗"
85 execute_record.exception_info = exception_info
86 execute_record.response_data = json.dumps(res_data.json(), ensure_ascii=False)
87 execute_record.status = 1
88 execute_end_time = time.time()
89 execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(execute_end_time))
90 print("執行結果結束時間: {}".format(execute_record.execute_end_time))
91 execute_record.execute_total_time = int((execute_end_time - execute_start_time) * 1000)
92 print("用例執行耗時: {}".format(execute_record.execute_total_time))
93 execute_record.save()
94 except Exception as e:
95 print("介面請求異常,error: {}".format(traceback.format_exc()))
96 execute_record.execute_result = "失敗"
97 execute_record.exception_info = traceback.format_exc()
98 execute_record.status = 1
99 execute_end_time = time.time()
100 execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(execute_end_time))
101 print("執行結果結束時間: {}".format(execute_record.execute_end_time))
102 execute_record.execute_total_time = int(execute_end_time - execute_start_time) * 1000
103 print("用例執行耗時: {} 毫秒".format(execute_record.execute_total_time))
104 execute_record.save()

前端執行測試用例,檢視用例執行結果表資料:

9.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('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"),
path('test_case_execute_record/', views.test_case_execute_record, name="test_case_execute_record"),
]

9.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, SuiteCase, InterfaceServer, TestCaseExecuteResult
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: {}".format(test_cases))
76 elif request.method == "POST":
77 print("request.POST: {}".format(request.POST))
78 test_case_id_list = request.POST.getlist('test_cases_list')
79 env = request.POST.getlist('env')
80 print("env: {}".format(env))
81 server_address = get_server_address(env)
82 if not server_address:
83 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
84 if test_case_id_list:
85 test_case_id_list.sort()
86 print("test_case_id_list: {}".format(test_case_id_list))
87 print("獲取到用例,開始用例執行")
88 case_task(test_case_id_list, server_address)
89 else:
90 print("執行測試用例失敗")
91 return HttpResponse("提交的執行測試用例為空,請選擇用例後在提交!")
92 test_cases = TestCase.objects.filter().order_by('id')
93 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
94
95
96 # 用例詳情頁
97 @login_required
98 def test_case_detail(request, test_case_id):
99 test_case_id = int(test_case_id)
100 test_case = TestCase.objects.get(id=test_case_id)
101 print("test_case: {}".format(test_case))
102 print("test_case.id: {}".format(test_case.id))
103 print("test_case.belong_project: {}".format(test_case.belong_project))
104
105 return render(request, 'test_case_detail.html', {'test_case': test_case})
106
107
108 # 模組頁展示測試用例
109 @login_required
110 def module_test_cases(request, module_id):
111 module = ""
112 if module_id: # 訪問的時候,會從url中提取模組的id,根據模組id查詢到模組資料,在模板中展現
113 module = Module.objects.get(id=int(module_id))
114 test_cases = TestCase.objects.filter(belong_module=module).order_by('-id')
115 print("test_case in module_test_cases: {}".format(test_cases))
116 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
117
118
119 # 用例集合選單項
120 @login_required
121 def case_suite(request):
122 case_suites = CaseSuite.objects.filter()
123 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
124
125
126 # 用例集合-新增測試用例頁
127 @login_required
128 def add_case_in_suite(request, suite_id):
129 # 查詢指定的用例集合
130 case_suite = CaseSuite.objects.get(id=suite_id)
131 # 根據id號查詢所有的用例
132 test_cases = TestCase.objects.filter().order_by('id')
133 if request.method == "GET":
134 print("test cases:", test_cases)
135 elif request.method == "POST":
136 test_cases_list = request.POST.getlist('testcases_list')
137 # 如果頁面勾選了用例
138 if test_cases_list:
139 print("勾選用例id:", test_cases_list)
140 # 根據頁面勾選的用例與查詢出的所有用例一一比較
141 for test_case in test_cases_list:
142 test_case = TestCase.objects.get(id=int(test_case))
143 # 匹配成功則新增用例
144 SuiteCase.objects.create(case_suite=case_suite, test_case=test_case)
145 # 未勾選用例
146 else:
147 print("新增測試用例失敗")
148 return HttpResponse("新增的測試用例為空,請選擇用例後再新增!")
149 return render(request, 'add_case_in_suite.html',
150 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
151
152
153 # 用例集合頁-檢視/刪除用例
154 @login_required
155 def show_and_delete_case_in_suite(request, suite_id):
156 case_suite = CaseSuite.objects.get(id=suite_id)
157 test_cases = SuiteCase.objects.filter(case_suite=case_suite)
158 if request.method == "POST":
159 test_cases_list = request.POST.getlist('test_cases_list')
160 if test_cases_list:
161 print("勾選用例:", test_cases_list)
162 for test_case in test_cases_list:
163 test_case = TestCase.objects.get(id=int(test_case))
164 SuiteCase.objects.filter(case_suite=case_suite, test_case=test_case).first().delete()
165 else:
166 print("測試用例刪除失敗")
167 return HttpResponse("所選測試用例為空,請選擇用例後再進行刪除!")
168 case_suite = CaseSuite.objects.get(id=suite_id)
169 return render(request, 'show_and_delete_case_in_suite.html',
170 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
171
172
173 @login_required
174 def test_case_execute_record(request):
175 test_case_execute_records = TestCaseExecuteResult.objects.filter().order_by('-id')
176 return render(request, 'test_case_execute_records.html', {'test_case_execute_records': get_paginator(request, test_case_execute_records)})
177
178
179 # 預設頁的檢視函式
180 @login_required
181 def index(request):
182 return render(request, 'index.html')
183
184
185 # 登入頁的檢視函式
186 def login(request):
187 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
188 if request.session.get('is_login', None):
189 return redirect('/')
190 # 如果是表單提交行為,則進行登入校驗
191 if request.method == "POST":
192 login_form = UserForm(request.POST)
193 message = "請檢查填寫的內容!"
194 if login_form.is_valid():
195 username = login_form.cleaned_data['username']
196 password = login_form.cleaned_data['password']
197 try:
198 # 使用django提供的身份驗證功能
199 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
200 if user is not None:
201 print("使用者【%s】登入成功" % username)
202 auth.login(request, user)
203 request.session['is_login'] = True
204 # 登入成功,跳轉主頁
205 return redirect('/')
206 else:
207 message = "使用者名稱不存在或者密碼不正確!"
208 except:
209 traceback.print_exc()
210 message = "登入程式出現異常"
211 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
212 else:
213 return render(request, 'login.html', locals())
214 # 不是表單提交,代表只是訪問登入頁
215 else:
216 login_form = UserForm()
217 return render(request, 'login.html', locals())
218
219
220 # 註冊頁的檢視函式
221 def register(request):
222 return render(request, 'register.html')
223
224
225 # 登出的檢視函式:重定向至login檢視函式
226 @login_required
227 def logout(request):
228 auth.logout(request)
229 request.session.flush()
230 return redirect("/login/")

9.5 定義模板

1)新增”測試執行記錄“模板檔案:templates/test_case_execute_records.html

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}用例執行記錄{% endblock %}
4 {% block content %}
5
6 <div class="table-responsive">
7 <table class="table table-striped">
8 <thead>
9 <tr>
10 <th width="4%">id</th>
11 <th width="4%">名稱</th>
12 <th width="20%">請求資料</th>
13 <th width="20%">執行返回結果</th>
14 <th width="5%">操作</th>
15 <th>斷言內容</th>
16 <th width="5%">執行結果</th>
17 <th width="5%">異常資訊</th>
18 <th width="10%">請求後提取變數</th>
19 <th width="8%">開始時間</th>
20 <th width="8%">執行耗時(ms)</th>
21 </tr>
22 </thead>
23 <tbody>
24
25 {% for testrecord in test_case_execute_records %}
26 <tr>
27 <td>{{ testrecord.id }}</td>
28 <td><a href="{% url 'test_case_detail' testrecord.belong_test_case.id%}" target="_blank">{{ testrecord.belong_test_case.case_name }}</a></td>
29 <td>{{ testrecord.request_data }}</td>
30 <td>{{ testrecord.response_data }}</td>
31 <td><a href="" target="_blank">對比差異</a></td>
32 <td>{{ testrecord.belong_test_case.assert_key }}</td>
33 <td>{{ testrecord.execute_result|default_if_none:"" }}</td>
34 {% if testrecord.exception_info %}
35 <td><a href="" target="_blank">顯示異常資訊</a></td>
36 {% else %}
37 <td>無</td>
38 {% endif %}
39
40 <td>{{ testrecord.extract_var }}</td>
41 <td>{{ testrecord.execute_start_time }}</td>
42 <td>{{ testrecord.execute_total_time }}</td>
43 </tr>
44 {% endfor %}
45
46 </tbody>
47 </table>
48
49 {# 實現分頁標籤的程式碼 #}
50 {# 這裡使用 bootstrap 渲染頁面 #}
51 <div id="pages" class="text-center">
52 <nav>
53 <ul class="pagination">
54 <li class="step-links">
55 {% if test_case_execute_records.has_previous %}
56 <a class='active' href="?page={{ test_case_execute_records.previous_page_number }}">上一頁</a>
57 {% endif %}
58
59 <span class="current">
60 第 {{ test_case_execute_records.number }} 頁 / 共 {{ test_case_execute_records.paginator.num_pages }} 頁</span>
61
62 {% if test_case_execute_records.has_next %}
63 <a class='active' href="?page={{ test_case_execute_records.next_page_number }}">下一頁</a>
64 {% endif %}
65 </li>
66 </ul>
67 </nav>
68 </div>
69 </div>
70 {% endblock %}

2)修改 base.html:新增“用例執行結果”選單項

 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 <li class="active"><a href="/test_case_execute_record/">用例執行結果</a></li>
45 </ul>
46 <ul class="nav navbar-nav navbar-right">
47 {% if request.user.is_authenticated %}
48 <li><a href="#">當前線上:{{ request.user.username }}</a></li>
49 <li><a href="/logout">登出</a></li>
50 {% else %}
51 <li><a href="/login">登入</a></li>
52
53 {% endif %}
54 </ul>
55 </div><!-- /.navbar-collapse -->
56 </div><!-- /.container-fluid -->
57 </nav>
58
59 {% block content %}{% endblock %}
60
61
62 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
63 <script src="{% static 'js/jquery-3.4.1.js' %}"></script>
64 <!-- Include all compiled plugins (below), or include individual files as needed -->
65 <script src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
66 </body>
67 </html>

頁面效果如下:

9.6 結果對比差異

在用例執行結果頁面,可以看到在“操作”列,有“對比差異”連結,該功能用於對比當前用例上一次的執行結果與當前的執行結果,便於檢視結果的差異。

由於在前面用例執行時,已經在結果記錄環節獲取到當前用例上一次的結果並記錄到當前用例記錄資料中,下面來處理一下這個頁面的展示。

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"),
path('test_case_execute_record/', views.test_case_execute_record, name="test_case_execute_record"),
re_path('case_result_diff/(?P<test_record_id>[0-9]+)', views.case_result_diff, name="case_result_diff"),
]

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 import json
8 from .models import Project, Module, TestCase, CaseSuite, SuiteCase, InterfaceServer, TestCaseExecuteResult
9 from .task import case_task
10
11
12 # 封裝分頁處理
13 def get_paginator(request, data):
14 paginator = Paginator(data, 10) # 預設每頁展示10條資料
15 # 獲取 url 後面的 page 引數的值, 首頁不顯示 page 引數, 預設值是 1
16 page = request.GET.get('page')
17 try:
18 paginator_pages = paginator.page(page)
19 except PageNotAnInteger:
20 # 如果請求的頁數不是整數, 返回第一頁。
21 paginator_pages = paginator.page(1)
22 except InvalidPage:
23 # 如果請求的頁數不存在, 重定向頁面
24 return HttpResponse('找不到頁面的內容')
25 return paginator_pages
26
27
28 # 專案選單項
29 @login_required
30 def project(request):
31 print("request.user.is_authenticated: ", request.user.is_authenticated)
32 projects = Project.objects.filter().order_by('-id')
33 print("projects:", projects)
34 return render(request, 'project.html', {'projects': get_paginator(request, projects)})
35
36
37 # 模組選單項
38 @login_required
39 def module(request):
40 if request.method == "GET": # 請求get時候,id倒序查詢所有的模組資料
41 modules = Module.objects.filter().order_by('-id')
42 return render(request, 'module.html', {'modules': get_paginator(request, modules)})
43 else: # 否則就是Post請求,會根據輸入內容,使用模糊的方式查詢所有的專案
44 proj_name = request.POST['proj_name']
45 projects = Project.objects.filter(name__contains=proj_name.strip())
46 projs = [proj.id for proj in projects]
47 modules = Module.objects.filter(belong_project__in=projs) # 把專案中所有的模組都找出來
48 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
49
50
51 # 獲取測試用例執行的介面地址
52 def get_server_address(env):
53 if env: # 環境處理
54 env_data = InterfaceServer.objects.filter(env=env[0])
55 print("env_data: {}".format(env_data))
56 if env_data:
57 ip = env_data[0].ip
58 port = env_data[0].port
59 print("ip: {}, port: {}".format(ip, port))
60 server_address = "http://{}:{}".format(ip, port)
61 print("server_address: {}".format(server_address))
62 return server_address
63 else:
64 return ""
65 else:
66 return ""
67
68
69 # 測試用例選單項
70 @login_required
71 def test_case(request):
72 print("request.session['is_login']: {}".format(request.session['is_login']))
73 test_cases = ""
74 if request.method == "GET":
75 test_cases = TestCase.objects.filter().order_by('id')
76 print("testcases: {}".format(test_cases))
77 elif request.method == "POST":
78 print("request.POST: {}".format(request.POST))
79 test_case_id_list = request.POST.getlist('test_cases_list')
80 env = request.POST.getlist('env')
81 print("env: {}".format(env))
82 server_address = get_server_address(env)
83 if not server_address:
84 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
85 if test_case_id_list:
86 test_case_id_list.sort()
87 print("test_case_id_list: {}".format(test_case_id_list))
88 print("獲取到用例,開始用例執行")
89 case_task(test_case_id_list, server_address)
90 else:
91 print("執行測試用例失敗")
92 return HttpResponse("提交的執行測試用例為空,請選擇用例後在提交!")
93 test_cases = TestCase.objects.filter().order_by('id')
94 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
95
96
97 # 用例詳情頁
98 @login_required
99 def test_case_detail(request, test_case_id):
100 test_case_id = int(test_case_id)
101 test_case = TestCase.objects.get(id=test_case_id)
102 print("test_case: {}".format(test_case))
103 print("test_case.id: {}".format(test_case.id))
104 print("test_case.belong_project: {}".format(test_case.belong_project))
105
106 return render(request, 'test_case_detail.html', {'test_case': test_case})
107
108
109 # 模組頁展示測試用例
110 @login_required
111 def module_test_cases(request, module_id):
112 module = ""
113 if module_id: # 訪問的時候,會從url中提取模組的id,根據模組id查詢到模組資料,在模板中展現
114 module = Module.objects.get(id=int(module_id))
115 test_cases = TestCase.objects.filter(belong_module=module).order_by('-id')
116 print("test_case in module_test_cases: {}".format(test_cases))
117 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
118
119
120 # 用例集合選單項
121 @login_required
122 def case_suite(request):
123 case_suites = CaseSuite.objects.filter()
124 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
125
126
127 # 用例集合-新增測試用例頁
128 @login_required
129 def add_case_in_suite(request, suite_id):
130 # 查詢指定的用例集合
131 case_suite = CaseSuite.objects.get(id=suite_id)
132 # 根據id號查詢所有的用例
133 test_cases = TestCase.objects.filter().order_by('id')
134 if request.method == "GET":
135 print("test cases:", test_cases)
136 elif request.method == "POST":
137 test_cases_list = request.POST.getlist('testcases_list')
138 # 如果頁面勾選了用例
139 if test_cases_list:
140 print("勾選用例id:", test_cases_list)
141 # 根據頁面勾選的用例與查詢出的所有用例一一比較
142 for test_case in test_cases_list:
143 test_case = TestCase.objects.get(id=int(test_case))
144 # 匹配成功則新增用例
145 SuiteCase.objects.create(case_suite=case_suite, test_case=test_case)
146 # 未勾選用例
147 else:
148 print("新增測試用例失敗")
149 return HttpResponse("新增的測試用例為空,請選擇用例後再新增!")
150 return render(request, 'add_case_in_suite.html',
151 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
152
153
154 # 用例集合頁-檢視/刪除用例
155 @login_required
156 def show_and_delete_case_in_suite(request, suite_id):
157 case_suite = CaseSuite.objects.get(id=suite_id)
158 test_cases = SuiteCase.objects.filter(case_suite=case_suite)
159 if request.method == "POST":
160 test_cases_list = request.POST.getlist('test_cases_list')
161 if test_cases_list:
162 print("勾選用例:", test_cases_list)
163 for test_case in test_cases_list:
164 test_case = TestCase.objects.get(id=int(test_case))
165 SuiteCase.objects.filter(case_suite=case_suite, test_case=test_case).first().delete()
166 else:
167 print("測試用例刪除失敗")
168 return HttpResponse("所選測試用例為空,請選擇用例後再進行刪除!")
169 case_suite = CaseSuite.objects.get(id=suite_id)
170 return render(request, 'show_and_delete_case_in_suite.html',
171 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
172
173
174 # 用例執行結果選單項
175 @login_required
176 def test_case_execute_record(request):
177 test_case_execute_records = TestCaseExecuteResult.objects.filter().order_by('-id')
178 return render(request, 'test_case_execute_records.html', {'test_case_execute_records': get_paginator(request, test_case_execute_records)})
179
180
181 # 用例執行結果-對比差異
182 @login_required
183 def diffCaseResponse(request, test_record_id):
184 test_record_data = TestCaseExecuteResult.objects.get(id=test_record_id)
185 print("用例執行結果記錄: {}".format(test_record_data))
186 present_response = test_record_data.response_data
187 if present_response:
188 present_response = json.dumps(json.loads(present_response), sort_keys=True, indent=4,
189 ensure_ascii=False) # 中文字元不轉ascii編碼
190 print("當前響應結果: {}".format(present_response))
191 last_time_execute_response = test_record_data.last_time_response_data
192 if last_time_execute_response:
193 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True, indent=4,
194 ensure_ascii=False)
195 print("上一次響應結果: {}".format(last_time_execute_response))
196 return render(request, 'case_result_diff.html', locals())
197
198
199 # 預設頁的檢視函式
200 @login_required
201 def index(request):
202 return render(request, 'index.html')
203
204
205 # 登入頁的檢視函式
206 def login(request):
207 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
208 if request.session.get('is_login', None):
209 return redirect('/')
210 # 如果是表單提交行為,則進行登入校驗
211 if request.method == "POST":
212 login_form = UserForm(request.POST)
213 message = "請檢查填寫的內容!"
214 if login_form.is_valid():
215 username = login_form.cleaned_data['username']
216 password = login_form.cleaned_data['password']
217 try:
218 # 使用django提供的身份驗證功能
219 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
220 if user is not None:
221 print("使用者【%s】登入成功" % username)
222 auth.login(request, user)
223 request.session['is_login'] = True
224 # 登入成功,跳轉主頁
225 return redirect('/')
226 else:
227 message = "使用者名稱不存在或者密碼不正確!"
228 except:
229 traceback.print_exc()
230 message = "登入程式出現異常"
231 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
232 else:
233 return render(request, 'login.html', locals())
234 # 不是表單提交,代表只是訪問登入頁
235 else:
236 login_form = UserForm()
237 return render(request, 'login.html', locals())
238
239
240 # 註冊頁的檢視函式
241 def register(request):
242 return render(request, 'register.html')
243
244
245 # 登出的檢視函式:重定向至login檢視函式
246 @login_required
247 def logout(request):
248 auth.logout(request)
249 request.session.flush()
250 return redirect("/login/")

3)定義模板

新增 case_result_diff.html:

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}結果對比差異{% endblock %}
4
5 {% block content %}
6 <table class="table table-striped">
7 <thead>
8 <tr>
9 <th width="50%">上次執行結果</th>
10 <th width="50%">本次執行結果</th>
11 </tr>
12 </thead>
13 <tbody>
14 <tr>
15 <td>
16 <div>
17 <pre style="height: 400px;">{{ last_time_execute_response | safe }}</pre>
18 </div>
19 </td>
20 <td>
21 <div><pre style="height: 400px;">{{ present_response | safe }}</pre></div>
22 </td>
23 </tr>
24 </tbody>
25 </table>
26
27 {% endblock %}

修改 test_case_execute_records.html:增加“對比差異”連結

{% extends 'base.html' %}
{% load static %}
{% block title %}用例執行記錄{% endblock %}
{% block content %} <div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th width="4%">id</th>
<th width="4%">名稱</th>
<th width="20%">請求資料</th>
<th width="20%">執行返回結果</th>
<th width="5%">操作</th>
<th>斷言內容</th>
<th width="5%">執行結果</th>
<th width="5%">異常資訊</th>
<th width="10%">請求後提取變數</th>
<th width="8%">開始時間</th>
<th width="8%">執行耗時(ms)</th>
</tr>
</thead>
<tbody> {% for testrecord in test_case_execute_records %}
<tr>
<td>{{ testrecord.id }}</td>
<td><a href="{% url 'test_case_detail' testrecord.belong_test_case.id%}" target="_blank">{{ testrecord.belong_test_case.case_name }}</a></td>
<td>{{ testrecord.request_data }}</td>
<td>{{ testrecord.response_data }}</td>
<td><a href="{% url 'case_result_diff' testrecord.id %}" target="_blank">對比差異</a></td>
<td>{{ testrecord.belong_test_case.assert_key }}</td>
<td>{{ testrecord.execute_result|default_if_none:"" }}</td>
{% if testrecord.exception_info %}
<td><a href="" target="_blank">顯示異常資訊</a></td>
{% else %}
<td>無</td>
{% endif %} <td>{{ testrecord.extract_var }}</td>
<td>{{ testrecord.execute_start_time }}</td>
<td>{{ testrecord.execute_total_time }}</td>
</tr>
{% endfor %} </tbody>
</table> {# 實現分頁標籤的程式碼 #}
{# 這裡使用 bootstrap 渲染頁面 #}
<div id="pages" class="text-center">
<nav>
<ul class="pagination">
<li class="step-links">
{% if test_case_execute_records.has_previous %}
<a class='active' href="?page={{ test_case_execute_records.previous_page_number }}">上一頁</a>
{% endif %} <span class="current">
第 {{ test_case_execute_records.number }} 頁 / 共 {{ test_case_execute_records.paginator.num_pages }} 頁</span> {% if test_case_execute_records.has_next %}
<a class='active' href="?page={{ test_case_execute_records.next_page_number }}">下一頁</a>
{% endif %}
</li>
</ul>
</nav>
</div>
</div>
{% endblock %}

9.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"),
path('test_case_execute_record/', views.test_case_execute_record, name="test_case_execute_record"),
re_path('case_result_diff/(?P<test_record_id>[0-9]+)', views.case_result_diff, name="case_result_diff"),
re_path('show_exception/(?P<execute_id>[0-9]+)$', views.show_exception, name="show_exception"),
]

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 import json
8 from .models import Project, Module, TestCase, CaseSuite, SuiteCase, InterfaceServer, TestCaseExecuteResult
9 from .task import case_task
10
11
12 # 封裝分頁處理
13 def get_paginator(request, data):
14 paginator = Paginator(data, 10) # 預設每頁展示10條資料
15 # 獲取 url 後面的 page 引數的值, 首頁不顯示 page 引數, 預設值是 1
16 page = request.GET.get('page')
17 try:
18 paginator_pages = paginator.page(page)
19 except PageNotAnInteger:
20 # 如果請求的頁數不是整數, 返回第一頁。
21 paginator_pages = paginator.page(1)
22 except InvalidPage:
23 # 如果請求的頁數不存在, 重定向頁面
24 return HttpResponse('找不到頁面的內容')
25 return paginator_pages
26
27
28 # 專案選單項
29 @login_required
30 def project(request):
31 print("request.user.is_authenticated: ", request.user.is_authenticated)
32 projects = Project.objects.filter().order_by('-id')
33 print("projects:", projects)
34 return render(request, 'project.html', {'projects': get_paginator(request, projects)})
35
36
37 # 模組選單項
38 @login_required
39 def module(request):
40 if request.method == "GET": # 請求get時候,id倒序查詢所有的模組資料
41 modules = Module.objects.filter().order_by('-id')
42 return render(request, 'module.html', {'modules': get_paginator(request, modules)})
43 else: # 否則就是Post請求,會根據輸入內容,使用模糊的方式查詢所有的專案
44 proj_name = request.POST['proj_name']
45 projects = Project.objects.filter(name__contains=proj_name.strip())
46 projs = [proj.id for proj in projects]
47 modules = Module.objects.filter(belong_project__in=projs) # 把專案中所有的模組都找出來
48 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
49
50
51 # 獲取測試用例執行的介面地址
52 def get_server_address(env):
53 if env: # 環境處理
54 env_data = InterfaceServer.objects.filter(env=env[0])
55 print("env_data: {}".format(env_data))
56 if env_data:
57 ip = env_data[0].ip
58 port = env_data[0].port
59 print("ip: {}, port: {}".format(ip, port))
60 server_address = "http://{}:{}".format(ip, port)
61 print("server_address: {}".format(server_address))
62 return server_address
63 else:
64 return ""
65 else:
66 return ""
67
68
69 # 測試用例選單項
70 @login_required
71 def test_case(request):
72 print("request.session['is_login']: {}".format(request.session['is_login']))
73 test_cases = ""
74 if request.method == "GET":
75 test_cases = TestCase.objects.filter().order_by('id')
76 print("testcases: {}".format(test_cases))
77 elif request.method == "POST":
78 print("request.POST: {}".format(request.POST))
79 test_case_id_list = request.POST.getlist('test_cases_list')
80 env = request.POST.getlist('env')
81 print("env: {}".format(env))
82 server_address = get_server_address(env)
83 if not server_address:
84 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
85 if test_case_id_list:
86 test_case_id_list.sort()
87 print("test_case_id_list: {}".format(test_case_id_list))
88 print("獲取到用例,開始用例執行")
89 case_task(test_case_id_list, server_address)
90 else:
91 print("執行測試用例失敗")
92 return HttpResponse("提交的執行測試用例為空,請選擇用例後在提交!")
93 test_cases = TestCase.objects.filter().order_by('id')
94 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
95
96
97 # 用例詳情頁
98 @login_required
99 def test_case_detail(request, test_case_id):
100 test_case_id = int(test_case_id)
101 test_case = TestCase.objects.get(id=test_case_id)
102 print("test_case: {}".format(test_case))
103 print("test_case.id: {}".format(test_case.id))
104 print("test_case.belong_project: {}".format(test_case.belong_project))
105
106 return render(request, 'test_case_detail.html', {'test_case': test_case})
107
108
109 # 模組頁展示測試用例
110 @login_required
111 def module_test_cases(request, module_id):
112 module = ""
113 if module_id: # 訪問的時候,會從url中提取模組的id,根據模組id查詢到模組資料,在模板中展現
114 module = Module.objects.get(id=int(module_id))
115 test_cases = TestCase.objects.filter(belong_module=module).order_by('-id')
116 print("test_case in module_test_cases: {}".format(test_cases))
117 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
118
119
120 # 用例集合選單項
121 @login_required
122 def case_suite(request):
123 case_suites = CaseSuite.objects.filter()
124 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
125
126
127 # 用例集合-新增測試用例頁
128 @login_required
129 def add_case_in_suite(request, suite_id):
130 # 查詢指定的用例集合
131 case_suite = CaseSuite.objects.get(id=suite_id)
132 # 根據id號查詢所有的用例
133 test_cases = TestCase.objects.filter().order_by('id')
134 if request.method == "GET":
135 print("test cases:", test_cases)
136 elif request.method == "POST":
137 test_cases_list = request.POST.getlist('testcases_list')
138 # 如果頁面勾選了用例
139 if test_cases_list:
140 print("勾選用例id:", test_cases_list)
141 # 根據頁面勾選的用例與查詢出的所有用例一一比較
142 for test_case in test_cases_list:
143 test_case = TestCase.objects.get(id=int(test_case))
144 # 匹配成功則新增用例
145 SuiteCase.objects.create(case_suite=case_suite, test_case=test_case)
146 # 未勾選用例
147 else:
148 print("新增測試用例失敗")
149 return HttpResponse("新增的測試用例為空,請選擇用例後再新增!")
150 return render(request, 'add_case_in_suite.html',
151 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
152
153
154 # 用例集合頁-檢視/刪除用例
155 @login_required
156 def show_and_delete_case_in_suite(request, suite_id):
157 case_suite = CaseSuite.objects.get(id=suite_id)
158 test_cases = SuiteCase.objects.filter(case_suite=case_suite)
159 if request.method == "POST":
160 test_cases_list = request.POST.getlist('test_cases_list')
161 if test_cases_list:
162 print("勾選用例:", test_cases_list)
163 for test_case in test_cases_list:
164 test_case = TestCase.objects.get(id=int(test_case))
165 SuiteCase.objects.filter(case_suite=case_suite, test_case=test_case).first().delete()
166 else:
167 print("測試用例刪除失敗")
168 return HttpResponse("所選測試用例為空,請選擇用例後再進行刪除!")
169 case_suite = CaseSuite.objects.get(id=suite_id)
170 return render(request, 'show_and_delete_case_in_suite.html',
171 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
172
173
174 # 用例執行結果選單項
175 @login_required
176 def test_case_execute_record(request):
177 test_case_execute_records = TestCaseExecuteResult.objects.filter().order_by('-id')
178 return render(request, 'test_case_execute_records.html', {'test_case_execute_records': get_paginator(request, test_case_execute_records)})
179
180
181 # 用例執行結果-對比差異
182 @login_required
183 def case_result_diff(request, test_record_id):
184 test_record_data = TestCaseExecuteResult.objects.get(id=test_record_id)
185 print("用例執行結果記錄: {}".format(test_record_data))
186 present_response = test_record_data.response_data
187 if present_response:
188 present_response = json.dumps(json.loads(present_response), sort_keys=True, indent=4,
189 ensure_ascii=False) # 中文字元不轉ascii編碼
190 print("當前響應結果: {}".format(present_response))
191 last_time_execute_response = test_record_data.last_time_response_data
192 if last_time_execute_response:
193 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True, indent=4,
194 ensure_ascii=False)
195 print("上一次響應結果: {}".format(last_time_execute_response))
196 return render(request, 'case_result_diff.html', locals())
197
198
199 # 用例執行結果-異常資訊展示
200 @login_required
201 def show_exception(request, execute_id):
202 test_record = TestCaseExecuteResult.objects.get(id=execute_id)
203 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
204
205
206 # 預設頁的檢視函式
207 @login_required
208 def index(request):
209 return render(request, 'index.html')
210
211
212 # 登入頁的檢視函式
213 def login(request):
214 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
215 if request.session.get('is_login', None):
216 return redirect('/')
217 # 如果是表單提交行為,則進行登入校驗
218 if request.method == "POST":
219 login_form = UserForm(request.POST)
220 message = "請檢查填寫的內容!"
221 if login_form.is_valid():
222 username = login_form.cleaned_data['username']
223 password = login_form.cleaned_data['password']
224 try:
225 # 使用django提供的身份驗證功能
226 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
227 if user is not None:
228 print("使用者【%s】登入成功" % username)
229 auth.login(request, user)
230 request.session['is_login'] = True
231 # 登入成功,跳轉主頁
232 return redirect('/')
233 else:
234 message = "使用者名稱不存在或者密碼不正確!"
235 except:
236 traceback.print_exc()
237 message = "登入程式出現異常"
238 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
239 else:
240 return render(request, 'login.html', locals())
241 # 不是表單提交,代表只是訪問登入頁
242 else:
243 login_form = UserForm()
244 return render(request, 'login.html', locals())
245
246
247 # 註冊頁的檢視函式
248 def register(request):
249 return render(request, 'register.html')
250
251
252 # 登出的檢視函式:重定向至login檢視函式
253 @login_required
254 def logout(request):
255 auth.logout(request)
256 request.session.flush()
257 return redirect("/login/")

3)定義模板

新增異常資訊展示模板:show_exception.html

1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}異常資訊{% endblock %}
4 {% block content %}
5
6 <p style="margin-left: 10px;">異常資訊如下:</p>
7 <p style="margin-left: 10px; width: 90%">{{ exception_info|default_if_none:"" }}</p>
8
9 {% endblock %}

修改用例執行記錄模板 test_case_execute_records.html:增加異常資訊展示連結

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}用例執行記錄{% endblock %}
4 {% block content %}
5
6 <div class="table-responsive">
7 <table class="table table-striped">
8 <thead>
9 <tr>
10 <th width="4%">id</th>
11 <th width="4%">名稱</th>
12 <th width="20%">請求資料</th>
13 <th width="20%">執行返回結果</th>
14 <th width="5%">操作</th>
15 <th>斷言內容</th>
16 <th width="5%">執行結果</th>
17 <th width="5%">異常資訊</th>
18 <th width="10%">請求後提取變數</th>
19 <th width="8%">開始時間</th>
20 <th width="8%">執行耗時(ms)</th>
21 </tr>
22 </thead>
23 <tbody>
24
25 {% for testrecord in test_case_execute_records %}
26 <tr>
27 <td>{{ testrecord.id }}</td>
28 <td><a href="{% url 'test_case_detail' testrecord.belong_test_case.id%}" target="_blank">{{ testrecord.belong_test_case.case_name }}</a></td>
29 <td>{{ testrecord.request_data }}</td>
30 <td>{{ testrecord.response_data }}</td>
31 <td><a href="{% url 'case_result_diff' testrecord.id %}" target="_blank">對比差異</a></td>
32 <td>{{ testrecord.belong_test_case.assert_key }}</td>
33
34 {% ifequal testrecord.execute_result '成功' %}
35 <td bgcolor='green'>{{ testrecord.execute_result}}</td>
36 {% else %}
37 <td bgcolor='red'>{{ testrecord.execute_result}}</td>
38 {% endifequal %}
39
40 {% if testrecord.exception_info %}
41 <td><a href="{% url 'show_exception' testrecord.id %}" target="_blank">顯示異常資訊</a></td>
42 {% else %}
43 <td>無</td>
44 {% endif %}
45
46 <td>{{ testrecord.extract_var }}</td>
47 <td>{{ testrecord.execute_start_time }}</td>
48 <td>{{ testrecord.execute_total_time }}</td>
49 </tr>
50 {% endfor %}
51
52 </tbody>
53 </table>
54
55 {# 實現分頁標籤的程式碼 #}
56 {# 這裡使用 bootstrap 渲染頁面 #}
57 <div id="pages" class="text-center">
58 <nav>
59 <ul class="pagination">
60 <li class="step-links">
61 {% if test_case_execute_records.has_previous %}
62 <a class='active' href="?page={{ test_case_execute_records.previous_page_number }}">上一頁</a>
63 {% endif %}
64
65 <span class="current">
66 第 {{ test_case_execute_records.number }} 頁 / 共 {{ test_case_execute_records.paginator.num_pages }} 頁</span>
67
68 {% if test_case_execute_records.has_next %}
69 <a class='active' href="?page={{ test_case_execute_records.next_page_number }}">下一頁</a>
70 {% endif %}
71 </li>
72 </ul>
73 </nav>
74 </div>
75 </div>
76 {% endblock %}

10. 測試集合執行

在前面的測試用例執行步驟,我們已經把請求資料處理、介面請求、斷言、變數提取等環節做了處理。那麼測試集合的執行也可以複用前面的邏輯,前端在提交測試集合到後端時,後端獲取到集合中包含的用例 ,呼叫用例執行的方法即可,然後記錄測試集合執行相關的結果記錄。

10.1 定義模型

models.py:

  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 # 專案
7 class Project(models.Model):
8 id = models.AutoField(primary_key=True)
9 name = models.CharField('專案名稱', max_length=50, unique=True, null=False)
10 proj_owner = models.CharField('專案負責人', max_length=20, null=False)
11 test_owner = models.CharField('測試負責人', max_length=20, null=False)
12 dev_owner = models.CharField('開發負責人', max_length=20, null=False)
13 desc = models.CharField('專案描述', max_length=100, null=True)
14 create_time = models.DateTimeField('專案建立時間', auto_now_add=True)
15 update_time = models.DateTimeField('專案更新時間', auto_now=True, null=True)
16
17 def __str__(self):
18 return self.name
19
20 class Meta:
21 verbose_name = '專案資訊表'
22 verbose_name_plural = '專案資訊表'
23
24
25 # 模組
26 class Module(models.Model):
27 id = models.AutoField(primary_key=True)
28 name = models.CharField('模組名稱', max_length=50, null=False)
29 belong_project = models.ForeignKey(Project, on_delete=models.CASCADE)
30 test_owner = models.CharField('測試負責人', max_length=50, null=False)
31 desc = models.CharField('簡要描述', max_length=100, null=True)
32 create_time = models.DateTimeField('建立時間', auto_now_add=True)
33 update_time = models.DateTimeField('更新時間', auto_now=True, null=True)
34
35 def __str__(self):
36 return self.name
37
38 class Meta:
39 verbose_name = '模組資訊表'
40 verbose_name_plural = '模組資訊表'
41
42
43 # 測試用例
44 class TestCase(models.Model):
45 id = models.AutoField(primary_key=True)
46 case_name = models.CharField('用例名稱', max_length=50, null=False) # 如 register
47 belong_project = models.ForeignKey(Project, on_delete=models.CASCADE, verbose_name='所屬專案')
48 belong_module = GroupedForeignKey(Module, "belong_project", on_delete=models.CASCADE, verbose_name='所屬模組')
49 request_data = models.CharField('請求資料', max_length=1024, null=False, default='')
50 uri = models.CharField('介面地址', max_length=1024, null=False, default='')
51 assert_key = models.CharField('斷言內容', max_length=1024, null=True)
52 maintainer = models.CharField('編寫人員', max_length=1024, null=False, default='')
53 extract_var = models.CharField('提取變量表達式', max_length=1024, null=True) # 示例:userid||userid": (\d+)
54 request_method = models.CharField('請求方式', max_length=1024, null=True)
55 status = models.IntegerField(null=True, help_text="0:表示有效,1:表示無效,用於軟刪除")
56 created_time = models.DateTimeField('建立時間', auto_now_add=True)
57 updated_time = models.DateTimeField('更新時間', auto_now=True, null=True)
58 user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='責任人', null=True)
59
60 def __str__(self):
61 return self.case_name
62
63 class Meta:
64 verbose_name = '測試用例表'
65 verbose_name_plural = '測試用例表'
66
67
68 # 用例集合
69 class CaseSuite(models.Model):
70 id = models.AutoField(primary_key=True)
71 suite_desc = models.CharField('用例集合描述', max_length=100, blank=True, null=True)
72 if_execute = models.IntegerField(verbose_name='是否執行', null=False, default=0, help_text='0:執行;1:不執行')
73 test_case_model = models.CharField('測試執行模式', max_length=100, blank=True, null=True, help_text='data/keyword')
74 creator = models.CharField(max_length=50, blank=True, null=True)
75 create_time = models.DateTimeField('建立時間', auto_now=True) # 建立時間-自動獲取當前時間
76
77 class Meta:
78 verbose_name = "用例集合表"
79 verbose_name_plural = '用例集合表'
80
81
82 # 用例集合關聯用例
83 class SuiteCase(models.Model):
84 id = models.AutoField(primary_key=True)
85 case_suite = models.ForeignKey(CaseSuite, on_delete=models.CASCADE, verbose_name='用例集合')
86 test_case = models.ForeignKey(TestCase, on_delete=models.CASCADE, verbose_name='測試用例')
87 status = models.IntegerField(verbose_name='是否有效', null=False, default=1, help_text='0:有效,1:無效')
88 create_time = models.DateTimeField('建立時間', auto_now=True) # 建立時間-自動獲取當前時間
89
90
91 # 介面伺服器配置
92 class InterfaceServer(models.Model):
93 id = models.AutoField(primary_key=True)
94 env = models.CharField('環境', max_length=50, null=False, default='')
95 ip = models.CharField('ip', max_length=50, null=False, default='')
96 port = models.CharField('埠', max_length=100, null=False, default='')
97 remark = models.CharField('備註', max_length=100, null=True)
98 create_time = models.DateTimeField('建立時間', auto_now_add=True)
99 update_time = models.DateTimeField('更新時間', auto_now=True, null=True)
100
101 def __str__(self):
102 return self.env
103
104 class Meta:
105 verbose_name = '介面地址配置表'
106 verbose_name_plural = '介面地址配置表'
107
108
109 # 測試用例執行記錄
110 class TestCaseExecuteResult(models.Model):
111 id = models.AutoField(primary_key=True)
112 belong_test_case = GroupedForeignKey(TestCase, "belong_test_case", on_delete=models.CASCADE, verbose_name='所屬用例')
113 status = models.IntegerField(null=True, help_text="0:表示未執行,1:表示已執行")
114 exception_info = models.CharField(max_length=2048, blank=True, null=True)
115 request_data = models.CharField('請求體', max_length=1024, null=True) # {"code": "00", "userid": 22889}
116 response_data = models.CharField('響應字串', max_length=1024, null=True) # {"code": "00", "userid": 22889}
117 execute_result = models.CharField('執行結果', max_length=1024, null=True) # 成功/失敗
118 extract_var = models.CharField('關聯引數', max_length=1024, null=True) # 響應成功後提取變數
119 last_time_response_data = models.CharField('上一次響應字串', max_length=1024, null=True) # {"code": "00", "userid": 22889}
120 execute_total_time = models.CharField('執行耗時', max_length=1024, null=True)
121 execute_start_time = models.CharField('執行開始時間', max_length=300, blank=True, null=True)
122 execute_end_time = models.CharField('執行結束時間', max_length=300, blank=True, null=True)
123 created_time = models.DateTimeField('建立時間', auto_now_add=True)
124 updated_time = models.DateTimeField('更新時間', auto_now=True, null=True)
125
126 def __str__(self):
127 return str(self.id)
128
129 class Meta:
130 verbose_name = '用例執行結果記錄表'
131 verbose_name_plural = '用例執行結果記錄表'
132
133
134 # 用例集合的執行記錄
135 class CaseSuiteExecuteRecord(models.Model):
136 id = models.AutoField(primary_key=True)
137 case_suite = models.ForeignKey(CaseSuite, on_delete=models.CASCADE, verbose_name='測試集合')
138 run_time_interval = models.IntegerField(verbose_name='延遲時間', null=True, default=0)
139 status = models.IntegerField(verbose_name='執行狀態', null=True, default=0)
140 test_result = models.CharField(max_length=50, blank=True, null=True)
141 creator = models.CharField(max_length=50, blank=True, null=True)
142 create_time = models.DateTimeField('建立時間', auto_now=True) # 建立時間-自動獲取當前時間
143 execute_start_time = models.CharField('執行開始時間', max_length=300, blank=True, null=True)
144
145
146 # 用例集合下的用例執行記錄
147 class CaseSuiteTestCaseExecuteRecord(models.Model):
148 id = models.AutoField(primary_key=True)
149 case_suite_record = models.ForeignKey(CaseSuiteExecuteRecord, on_delete=models.CASCADE, verbose_name='測試集合執行記錄')
150 test_case = models.ForeignKey(TestCase, on_delete=models.CASCADE, verbose_name='測試用例')
151 status = models.IntegerField(verbose_name='執行狀態', null=True, default=0)
152 exception_info = models.CharField(max_length=2048, blank=True, null=True)
153 request_data = models.CharField('請求體', max_length=1024, null=True) # {"code": "00", "userid": 22889}
154 response_data = models.CharField('響應字串', max_length=1024, null=True) # {"code": "00", "userid": 22889}
155 execute_result = models.CharField('執行結果', max_length=1024, null=True) # 成功/失敗
156 extract_var = models.CharField('關聯引數', max_length=1024, null=True) # 響應成功後提取變數
157 last_time_response_data = models.CharField('上一次響應字串', max_length=1024,
158 null=True) # {"code": "00", "userid": 22889}
159 execute_total_time = models.CharField('執行耗時', max_length=1024, null=True)
160 execute_start_time = models.CharField('執行開始時間', max_length=300, blank=True, null=True)
161 execute_end_time = models.CharField('執行結束時間', max_length=300, blank=True, null=True)

執行資料遷移:

python manage.py makemigrations
python manage.py migrate

10.2 修改模板

1)修改 case_suite.html 模板,增加提交測試集合相關元件。

  1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}用例集合{% endblock %}
4 {% block content %}
5 <script>
6 //頁面載入的時候,所有的複選框都是未選中的狀態
7 function checkOrCancelAll() {
8 var all_check = document.getElementById("all_check"); // 1.獲取all的元素物件
9 var all_check = all_check.checked; // 2.獲取選中狀態
10 // 3.若checked=true,將所有的複選框選中,checked=false,將所有的複選框取消
11 var allCheck = document.getElementsByName("case_suite_list");
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 function ischecked() {
27 // 3.若checked=true,將所有的複選框選中,checked=false,將所有的複選框取消
28 var allCheck = document.getElementsByName("case_suite_list");
29 for (var i = 0; i < allCheck.length; i++) {
30
31 if (allCheck[i].checked == true) {
32 alert("所需執行的測試集合提交成功!");
33 return true
34 }
35 }
36 alert("請選擇要執行的測試集合!")
37 return false
38 }
39 </script>
40
41 <form action="" method="POST">
42 {% csrf_token %}
43 <span style="margin-left: 5px;">延遲執行的時間(單位:秒):</span>
44 <input type="text" style="width: 70px; margin-left: 5px; margin-right: 10px;" placeholder="請輸入" name="delay_time"/>
45 <span style="margin-left: 5px;">執行環境:</span>
46 <select name="env">
47 <option selected value="dev">dev</option>
48 <option value="prod">prod</option>
49 </select>
50 <input style="margin-left: 10px;" type="submit" id="all_check1" value='執行測試集合' onclick="return ischecked()"/>
51
52 <div class="table-responsive">
53 <table class="table table-striped">
54 <thead>
55 <tr>
56 <th><input type="checkbox" id="all_check" onclick="checkOrCancelAll();"/>全選</th>
57 <th>id</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 case_suite in case_suites %}
69 <tr>
70 <td><input type="checkbox" value="{{ case_suite.id }}" name="case_suite_list"></td>
71 <td>{{ case_suite.id }}</td>
72 <td>{{ case_suite.suite_desc }}</td>
73 <td>{{ case_suite.creator }}</td>
74 <td>{{ case_suite.create_time|date:"Y-n-d H:i" }}</td>
75 <td><a href="{% url 'show_and_delete_case_in_suite' case_suite.id %}">檢視/刪除測試用例</a></td>
76 <td><a href="{% url 'add_case_in_suite' case_suite.id %}">新增測試用例</a></td>
77 <td><a href="">檢視用例集合執行結果</a></td>
78 </tr>
79 {% endfor %}
80 </tbody>
81 </table>
82 </div>
83 </form>
84
85 {# 實現分頁標籤的程式碼 #}
86 {# 這裡使用 bootstrap 渲染頁面 #}
87 <div id="pages" class="text-center">
88 <nav>
89 <ul class="pagination">
90 <li class="step-links">
91 {% if case_suites.has_previous %}
92 <a class='active' href="?page={{ case_suites.previous_page_number }}">上一頁</a>
93 {% endif %}
94
95 <span class="current">
96 第 {{ case_suites.number }} 頁 / 共 {{ case_suites.paginator.num_pages }} 頁</span>
97
98 {% if case_suites.has_next %}
99 <a class='active' href="?page={{ case_suites.next_page_number }}">下一頁</a>
100 {% endif %}
101 </li>
102 </ul>
103 </nav>
104 </div>
105 {% endblock %}

10.3 後端接收用例集合並處理

1)在應用 task.py 中增加用例集合執行的任務函式:

  1 import time
2 import os
3 import traceback
4 import json
5 from . import models
6 from .utils.data_process import data_preprocess, assert_result, data_postprocess
7 from .utils.request_process import request_process
8
9
10 # 測試用例執行
11 def case_task(test_case_id_list, server_address):
12 global_key = 'case'+ str(int(time.time() * 100000))
13 os.environ[global_key] = '{}'
14 print()
15 print("全域性變數識別符號【global_key】: {}".format(global_key))
16 print("全域性變數內容【os.environ[global_key]】: {}".format(os.environ[global_key]))
17 for test_case_id in test_case_id_list:
18
19 test_case = models.TestCase.objects.filter(id=int(test_case_id))[0]
20 last_execute_record_data = models.TestCaseExecuteResult.objects.filter(
21 belong_test_case_id=test_case_id).order_by('-id')
22 if last_execute_record_data:
23 last_time_execute_response_data = last_execute_record_data[0].response_data
24 else:
25 last_time_execute_response_data = ''
26 print("上一次響應結果: {}".format(last_execute_record_data))
27 print("上一次響應時間: {}".format(last_time_execute_response_data))
28 execute_record = models.TestCaseExecuteResult.objects.create(belong_test_case=test_case)
29 execute_record.last_time_response_data = last_time_execute_response_data
30 # 獲取當前用例上一次執行結果
31 execute_record.save()
32
33 test_case = models.TestCase.objects.filter(id=int(test_case_id))[0]
34 print("\n######### 開始執行用例【{}】 #########".format(test_case))
35 execute_start_time = time.time() # 記錄時間戳,便於計算總耗時(毫秒)
36 execute_record.execute_start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(execute_start_time))
37
38 request_data = test_case.request_data
39 extract_var = test_case.extract_var
40 assert_key = test_case.assert_key
41 interface_name = test_case.uri
42 belong_project = test_case.belong_project
43 belong_module = test_case.belong_module
44 maintainer = test_case.maintainer
45 request_method = test_case.request_method
46 print("初始請求資料: {}".format(request_data))
47 print("關聯引數: {}".format(extract_var))
48 print("斷言關鍵字: {}".format(assert_key))
49 print("介面名稱: {}".format(interface_name))
50 print("所屬專案: {}".format(belong_project))
51 print("所屬模組: {}".format(belong_module))
52 print("用例維護人: {}".format(maintainer))
53 print("請求方法: {}".format(request_method))
54 url = "{}{}".format(server_address, interface_name)
55 print("介面地址: {}".format(url))
56 code, request_data, error_msg = data_preprocess(global_key, str(request_data))
57 # 請求資料預處理異常,結束用例執行
58 if code != 0:
59 print("資料處理異常,error: {}".format(error_msg))
60 execute_record.execute_result = "失敗"
61 execute_record.status = 1
62 execute_record.exception_info = error_msg
63 execute_end_time = time.time()
64 execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(execute_end_time))
65 execute_record.execute_total_time = int(execute_end_time - execute_start_time) * 1000
66 execute_record.save()
67 return
68 # 記錄請求預處理結果
69 else:
70 execute_record.request_data = request_data
71 # 呼叫介面
72 try:
73 res_data = request_process(url, request_method, json.loads(request_data))
74 print("響應資料: {}".format(json.dumps(res_data.json(), ensure_ascii=False))) # ensure_ascii:相容中文
75 result_flag, exception_info = assert_result(res_data, assert_key)
76 # 結果記錄儲存
77 if result_flag:
78 print("用例【%s】執行成功!" % test_case)
79 execute_record.execute_result = "成功"
80 if extract_var.strip() != "None":
81 var_value = data_postprocess(global_key, json.dumps(res_data.json(), ensure_ascii=False), extract_var)
82 execute_record.extract_var = var_value
83 else:
84 print("用例【%s】執行失敗!" % test_case)
85 execute_record.execute_result = "失敗"
86 execute_record.exception_info = exception_info
87 execute_record.response_data = json.dumps(res_data.json(), ensure_ascii=False)
88 execute_record.status = 1
89 execute_end_time = time.time()
90 execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(execute_end_time))
91 print("執行結果結束時間: {}".format(execute_record.execute_end_time))
92 execute_record.execute_total_time = int((execute_end_time - execute_start_time) * 1000)
93 print("用例執行耗時: {}".format(execute_record.execute_total_time))
94 execute_record.save()
95 except Exception as e:
96 print("介面請求異常,error: {}".format(traceback.format_exc()))
97 execute_record.execute_result = "失敗"
98 execute_record.exception_info = traceback.format_exc()
99 execute_record.status = 1
100 execute_end_time = time.time()
101 execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(execute_end_time))
102 print("執行結果結束時間: {}".format(execute_record.execute_end_time))
103 execute_record.execute_total_time = int(execute_end_time - execute_start_time) * 1000
104 print("用例執行耗時: {} 毫秒".format(execute_record.execute_total_time))
105 execute_record.save()
106
107
108 # 用例集合執行
109 def suite_task(case_suite_record, case_suite, server_address):
110 global_key = case_suite.suite_desc + str(int(time.time() * 100000))
111 # global_vars = {"{}".format(global_key): {}}
112 os.environ[global_key] = '{}'
113 print("global_key: {}".format(global_key))
114 print("os.environ[global_key]: {}".format(os.environ[global_key]))
115 case_suite_test_cases = models.SuiteCase.objects.filter(case_suite=case_suite).order_by('id')
116 print("用例集合的測試用例列表: {}".format(case_suite_test_cases))
117 case_suite_record.test_result = "成功"
118 case_suite_record.execute_start_time = time.strftime("%Y-%m-%d %H:%M:%S")
119
120 for case_suite_test_case in case_suite_test_cases:
121 test_case = case_suite_test_case.test_case
122 print("\n######### 開始執行用例【{}】 #########".format(test_case))
123 last_execute_record_data = models.CaseSuiteTestCaseExecuteRecord.objects.filter(
124 test_case_id=test_case.id).order_by('-id')
125 if last_execute_record_data:
126 last_time_execute_response_data = last_execute_record_data[0].response_data
127 else:
128 last_time_execute_response_data = ''
129 print("上一次響應結果: {}".format(last_execute_record_data))
130 print("上一次響應時間: {}".format(last_time_execute_response_data))
131 suite_case_execute_record = models.CaseSuiteTestCaseExecuteRecord.objects.create(case_suite_record=case_suite_record,
132 test_case=test_case)
133 execute_start_time = time.time() # 記錄時間戳,便於計算總耗時(毫秒)
134 suite_case_execute_record.execute_start_time = time.strftime("%Y-%m-%d %H:%M:%S",
135 time.localtime(execute_start_time))
136 print("用例集合開始執行時間: {}".format(suite_case_execute_record.execute_start_time))
137 suite_case_execute_record.last_time_response_data = last_time_execute_response_data
138 suite_case_execute_record.save()
139 request_data = test_case.request_data
140 extract_var = test_case.extract_var
141 assert_key = test_case.assert_key
142 interface_name = test_case.uri
143 belong_project = test_case.belong_project
144 belong_module = test_case.belong_module
145 maintainer = test_case.maintainer
146 request_method = test_case.request_method
147 print("初始請求資料: {}".format(request_data))
148 print("關聯引數: {}".format(extract_var))
149 print("斷言關鍵字: {}".format(assert_key))
150 print("介面名稱: {}".format(interface_name))
151 print("所屬專案: {}".format(belong_project))
152 print("所屬模組: {}".format(belong_module))
153 print("用例維護人: {}".format(maintainer))
154 print("請求方法: {}".format(request_method))
155 url = "{}{}".format(server_address, interface_name)
156 print("介面地址: {}".format(url))
157 # 請求資料預處理
158 code, request_data, error_msg = data_preprocess(global_key, str(request_data))
159 # 請求資料預處理異常,結束用例執行
160 if code != 0:
161 print("資料處理異常,error: {}".format(error_msg))
162 suite_case_execute_record.execute_result = "失敗"
163 suite_case_execute_record.status = 1
164 suite_case_execute_record.exception_info = error_msg
165 execute_end_time = time.time()
166 suite_case_execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S",
167 time.localtime(execute_end_time))
168 suite_case_execute_record.execute_total_time = int(execute_end_time - execute_start_time) * 1000
169 suite_case_execute_record.save()
170 case_suite_record.test_result = "失敗"
171 # 記錄請求預處理的結果
172 suite_case_execute_record.request_data = request_data
173 try:
174 # 呼叫介面
175 res_data = request_process(url, request_method, json.loads(request_data))
176 print("響應資料: {}".format(json.dumps(res_data.json(), ensure_ascii=False)))
177
178 result_flag, exception_info = assert_result(res_data, assert_key)
179 # 結果記錄儲存
180 if result_flag:
181 print("用例【%s】執行成功!" % test_case)
182 suite_case_execute_record.execute_result = "成功"
183 if extract_var.strip() != "None":
184 var_value = data_postprocess(global_key, json.dumps(res_data.json(), ensure_ascii=False),
185 extract_var)
186 suite_case_execute_record.extract_var = var_value
187 else:
188 print("用例【%s】執行失敗!" % test_case)
189 suite_case_execute_record.execute_result = "失敗"
190 suite_case_execute_record.exception_info = exception_info
191 case_suite_record.test_result = "失敗"
192 suite_case_execute_record.response_data = json.dumps(res_data.json(), ensure_ascii=False)
193 suite_case_execute_record.status = 1
194 execute_end_time = time.time()
195 suite_case_execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S",
196 time.localtime(execute_end_time))
197 suite_case_execute_record.execute_total_time = int((execute_end_time - execute_start_time) * 1000)
198 print("用例執行耗時: {} 毫秒".format(
199 suite_case_execute_record.execute_total_time))
200 suite_case_execute_record.save()
201 except Exception as e:
202 print("介面請求異常,error: {}".format(e))
203 suite_case_execute_record.execute_result = "失敗"
204 suite_case_execute_record.exception_info = traceback.format_exc()
205 suite_case_execute_record.status = 1
206 execute_end_time = time.time()
207 suite_case_execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S",
208 time.localtime(execute_end_time))
209 suite_case_execute_record.execute_total_time = int(execute_end_time - execute_start_time) * 1000
210 print("用例集合執行總耗時: {} 毫秒".format(suite_case_execute_record.execute_total_time))
211 suite_case_execute_record.save()
212 case_suite_record.test_result = "失敗"
213
214 case_suite_record.status = 1 # 執行完畢
215 case_suite_record.save()

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 import json
8 from .models import Project, Module, TestCase, CaseSuite, SuiteCase, InterfaceServer, TestCaseExecuteResult, CaseSuiteExecuteRecord
9 from .task import case_task, suite_task
10
11
12 # 封裝分頁處理
13 def get_paginator(request, data):
14 paginator = Paginator(data, 10) # 預設每頁展示10條資料
15 # 獲取 url 後面的 page 引數的值, 首頁不顯示 page 引數, 預設值是 1
16 page = request.GET.get('page')
17 try:
18 paginator_pages = paginator.page(page)
19 except PageNotAnInteger:
20 # 如果請求的頁數不是整數, 返回第一頁。
21 paginator_pages = paginator.page(1)
22 except InvalidPage:
23 # 如果請求的頁數不存在, 重定向頁面
24 return HttpResponse('找不到頁面的內容')
25 return paginator_pages
26
27
28 # 專案選單項
29 @login_required
30 def project(request):
31 print("request.user.is_authenticated: ", request.user.is_authenticated)
32 projects = Project.objects.filter().order_by('-id')
33 print("projects:", projects)
34 return render(request, 'project.html', {'projects': get_paginator(request, projects)})
35
36
37 # 模組選單項
38 @login_required
39 def module(request):
40 if request.method == "GET": # 請求get時候,id倒序查詢所有的模組資料
41 modules = Module.objects.filter().order_by('-id')
42 return render(request, 'module.html', {'modules': get_paginator(request, modules)})
43 else: # 否則就是Post請求,會根據輸入內容,使用模糊的方式查詢所有的專案
44 proj_name = request.POST['proj_name']
45 projects = Project.objects.filter(name__contains=proj_name.strip())
46 projs = [proj.id for proj in projects]
47 modules = Module.objects.filter(belong_project__in=projs) # 把專案中所有的模組都找出來
48 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
49
50
51 # 獲取測試用例執行的介面地址
52 def get_server_address(env):
53 if env: # 環境處理
54 env_data = InterfaceServer.objects.filter(env=env[0])
55 print("env_data: {}".format(env_data))
56 if env_data:
57 ip = env_data[0].ip
58 port = env_data[0].port
59 print("ip: {}, port: {}".format(ip, port))
60 server_address = "http://{}:{}".format(ip, port)
61 print("server_address: {}".format(server_address))
62 return server_address
63 else:
64 return ""
65 else:
66 return ""
67
68
69 # 測試用例選單項
70 @login_required
71 def test_case(request):
72 print("request.session['is_login']: {}".format(request.session['is_login']))
73 test_cases = ""
74 if request.method == "GET":
75 test_cases = TestCase.objects.filter().order_by('id')
76 print("testcases: {}".format(test_cases))
77 elif request.method == "POST":
78 print("request.POST: {}".format(request.POST))
79 test_case_id_list = request.POST.getlist('test_cases_list')
80 env = request.POST.getlist('env')
81 print("env: {}".format(env))
82 server_address = get_server_address(env)
83 if not server_address:
84 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
85 if test_case_id_list:
86 test_case_id_list.sort()
87 print("test_case_id_list: {}".format(test_case_id_list))
88 print("獲取到用例,開始用例執行")
89 case_task(test_case_id_list, server_address)
90 else:
91 print("執行測試用例失敗")
92 return HttpResponse("提交的執行測試用例為空,請選擇用例後在提交!")
93 test_cases = TestCase.objects.filter().order_by('id')
94 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
95
96
97 # 用例詳情頁
98 @login_required
99 def test_case_detail(request, test_case_id):
100 test_case_id = int(test_case_id)
101 test_case = TestCase.objects.get(id=test_case_id)
102 print("test_case: {}".format(test_case))
103 print("test_case.id: {}".format(test_case.id))
104 print("test_case.belong_project: {}".format(test_case.belong_project))
105
106 return render(request, 'test_case_detail.html', {'test_case': test_case})
107
108
109 # 模組頁展示測試用例
110 @login_required
111 def module_test_cases(request, module_id):
112 module = ""
113 if module_id: # 訪問的時候,會從url中提取模組的id,根據模組id查詢到模組資料,在模板中展現
114 module = Module.objects.get(id=int(module_id))
115 test_cases = TestCase.objects.filter(belong_module=module).order_by('-id')
116 print("test_case in module_test_cases: {}".format(test_cases))
117 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
118
119
120 # 用例集合選單項
121 @login_required
122 def case_suite(request):
123 if request.method == "POST":
124 count_down_time = 0
125 if request.POST['delay_time']:
126 print("輸入的延遲時間是: {}".format(request.POST['delay_time']))
127 try:
128 count_down_time = int(request.POST['delay_time'])
129 except:
130 print("輸入的延遲時間是非數字!")
131 else:
132 print("沒有輸入延遲時間")
133 env = request.POST.getlist('env')
134 print("env: {}".format(env))
135 server_address = get_server_address(env)
136 if not server_address:
137 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
138 case_suite_list = request.POST.getlist('case_suite_list')
139 if case_suite_list:
140 print("所需執行的用例集合列表:", case_suite_list)
141 for suite_id in case_suite_list:
142 test_suite = CaseSuite.objects.get(id=int(suite_id))
143 print("所需執行的用例集合: {}".format(test_suite))
144 username = request.user.username
145 test_suite_record = CaseSuiteExecuteRecord.objects.create(case_suite=test_suite,
146 run_time_interval=count_down_time,
147 creator=username)
148 suite_task(test_suite_record, test_suite, server_address)
149 else:
150 print("執行測試集合用例失敗")
151 return HttpResponse("執行的測試集合為空,請選擇測試集合後再執行!")
152 case_suites = CaseSuite.objects.filter()
153 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
154
155
156 # 用例集合-新增測試用例頁
157 @login_required
158 def add_case_in_suite(request, suite_id):
159 # 查詢指定的用例集合
160 case_suite = CaseSuite.objects.get(id=suite_id)
161 # 根據id號查詢所有的用例
162 test_cases = TestCase.objects.filter().order_by('id')
163 if request.method == "GET":
164 print("test cases:", test_cases)
165 elif request.method == "POST":
166 test_cases_list = request.POST.getlist('testcases_list')
167 # 如果頁面勾選了用例
168 if test_cases_list:
169 print("勾選用例id:", test_cases_list)
170 # 根據頁面勾選的用例與查詢出的所有用例一一比較
171 for test_case in test_cases_list:
172 test_case = TestCase.objects.get(id=int(test_case))
173 # 匹配成功則新增用例
174 SuiteCase.objects.create(case_suite=case_suite, test_case=test_case)
175 # 未勾選用例
176 else:
177 print("新增測試用例失敗")
178 return HttpResponse("新增的測試用例為空,請選擇用例後再新增!")
179 return render(request, 'add_case_in_suite.html',
180 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
181
182
183 # 用例集合頁-檢視/刪除用例
184 @login_required
185 def show_and_delete_case_in_suite(request, suite_id):
186 case_suite = CaseSuite.objects.get(id=suite_id)
187 test_cases = SuiteCase.objects.filter(case_suite=case_suite)
188 if request.method == "POST":
189 test_cases_list = request.POST.getlist('test_cases_list')
190 if test_cases_list:
191 print("勾選用例:", test_cases_list)
192 for test_case in test_cases_list:
193 test_case = TestCase.objects.get(id=int(test_case))
194 SuiteCase.objects.filter(case_suite=case_suite, test_case=test_case).first().delete()
195 else:
196 print("測試用例刪除失敗")
197 return HttpResponse("所選測試用例為空,請選擇用例後再進行刪除!")
198 case_suite = CaseSuite.objects.get(id=suite_id)
199 return render(request, 'show_and_delete_case_in_suite.html',
200 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
201
202
203 # 用例執行結果選單項
204 @login_required
205 def test_case_execute_record(request):
206 test_case_execute_records = TestCaseExecuteResult.objects.filter().order_by('-id')
207 return render(request, 'test_case_execute_records.html', {'test_case_execute_records': get_paginator(request, test_case_execute_records)})
208
209
210 # 用例執行結果-對比差異
211 @login_required
212 def case_result_diff(request, test_record_id):
213 test_record_data = TestCaseExecuteResult.objects.get(id=test_record_id)
214 print("用例執行結果記錄: {}".format(test_record_data))
215 present_response = test_record_data.response_data
216 if present_response:
217 present_response = json.dumps(json.loads(present_response), sort_keys=True, indent=4,
218 ensure_ascii=False) # 中文字元不轉ascii編碼
219 print("當前響應結果: {}".format(present_response))
220 last_time_execute_response = test_record_data.last_time_response_data
221 if last_time_execute_response:
222 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True, indent=4,
223 ensure_ascii=False)
224 print("上一次響應結果: {}".format(last_time_execute_response))
225 return render(request, 'case_result_diff.html', locals())
226
227
228 # 用例執行結果-異常資訊展示
229 @login_required
230 def show_exception(request, execute_id):
231 test_record = TestCaseExecuteResult.objects.get(id=execute_id)
232 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
233
234
235 # 預設頁的檢視函式
236 @login_required
237 def index(request):
238 return render(request, 'index.html')
239
240
241 # 登入頁的檢視函式
242 def login(request):
243 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
244 if request.session.get('is_login', None):
245 return redirect('/')
246 # 如果是表單提交行為,則進行登入校驗
247 if request.method == "POST":
248 login_form = UserForm(request.POST)
249 message = "請檢查填寫的內容!"
250 if login_form.is_valid():
251 username = login_form.cleaned_data['username']
252 password = login_form.cleaned_data['password']
253 try:
254 # 使用django提供的身份驗證功能
255 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
256 if user is not None:
257 print("使用者【%s】登入成功" % username)
258 auth.login(request, user)
259 request.session['is_login'] = True
260 # 登入成功,跳轉主頁
261 return redirect('/')
262 else:
263 message = "使用者名稱不存在或者密碼不正確!"
264 except:
265 traceback.print_exc()
266 message = "登入程式出現異常"
267 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
268 else:
269 return render(request, 'login.html', locals())
270 # 不是表單提交,代表只是訪問登入頁
271 else:
272 login_form = UserForm()
273 return render(request, 'login.html', locals())
274
275
276 # 註冊頁的檢視函式
277 def register(request):
278 return render(request, 'register.html')
279
280
281 # 登出的檢視函式:重定向至login檢視函式
282 @login_required
283 def logout(request):
284 auth.logout(request)
285 request.session.flush()
286 return redirect("/login/")

前端頁面勾選測試集合,提交執行,生成表資料如下所示:

11. 用例集合執行結果展示

11.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"),
path('test_case_execute_record/', views.test_case_execute_record, name="test_case_execute_record"),
re_path('case_result_diff/(?P<test_record_id>[0-9]+)', views.case_result_diff, name="case_result_diff"),
re_path('show_exception/(?P<execute_id>[0-9]+)$', views.show_exception, name="show_exception"),
path('case_suite_execute_record/', views.case_suite_execute_record, name="case_suite_execute_record"),
]

11.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 import json
8 from . import models
9 from .task import case_task, suite_task
10
11
12 # 封裝分頁處理
13 def get_paginator(request, data):
14 paginator = Paginator(data, 10) # 預設每頁展示10條資料
15 # 獲取 url 後面的 page 引數的值, 首頁不顯示 page 引數, 預設值是 1
16 page = request.GET.get('page')
17 try:
18 paginator_pages = paginator.page(page)
19 except PageNotAnInteger:
20 # 如果請求的頁數不是整數, 返回第一頁。
21 paginator_pages = paginator.page(1)
22 except InvalidPage:
23 # 如果請求的頁數不存在, 重定向頁面
24 return HttpResponse('找不到頁面的內容')
25 return paginator_pages
26
27
28 # 專案-選單項
29 @login_required
30 def project(request):
31 print("request.user.is_authenticated: ", request.user.is_authenticated)
32 projects = models.Project.objects.filter().order_by('-id')
33 print("projects:", projects)
34 return render(request, 'project.html', {'projects': get_paginator(request, projects)})
35
36
37 # 模組-選單項
38 @login_required
39 def module(request):
40 if request.method == "GET": # 請求get時候,id倒序查詢所有的模組資料
41 modules = models.Module.objects.filter().order_by('-id')
42 return render(request, 'module.html', {'modules': get_paginator(request, modules)})
43 else: # 否則就是Post請求,會根據輸入內容,使用模糊的方式查詢所有的專案
44 proj_name = request.POST['proj_name']
45 projects = models.Project.objects.filter(name__contains=proj_name.strip())
46 projs = [proj.id for proj in projects]
47 modules = models.Module.objects.filter(belong_project__in=projs) # 把專案中所有的模組都找出來
48 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
49
50
51 # 獲取測試用例執行的介面地址
52 def get_server_address(env):
53 if env: # 環境處理
54 env_data = models.InterfaceServer.objects.filter(env=env[0])
55 print("env_data: {}".format(env_data))
56 if env_data:
57 ip = env_data[0].ip
58 port = env_data[0].port
59 print("ip: {}, port: {}".format(ip, port))
60 server_address = "http://{}:{}".format(ip, port)
61 print("server_address: {}".format(server_address))
62 return server_address
63 else:
64 return ""
65 else:
66 return ""
67
68
69 # 測試用例-選單項
70 @login_required
71 def test_case(request):
72 print("request.session['is_login']: {}".format(request.session['is_login']))
73 test_cases = ""
74 if request.method == "GET":
75 test_cases = models.TestCase.objects.filter().order_by('id')
76 print("testcases: {}".format(test_cases))
77 elif request.method == "POST":
78 print("request.POST: {}".format(request.POST))
79 test_case_id_list = request.POST.getlist('test_cases_list')
80 env = request.POST.getlist('env')
81 print("env: {}".format(env))
82 server_address = get_server_address(env)
83 if not server_address:
84 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
85 if test_case_id_list:
86 test_case_id_list.sort()
87 print("test_case_id_list: {}".format(test_case_id_list))
88 print("獲取到用例,開始用例執行")
89 case_task(test_case_id_list, server_address)
90 else:
91 print("執行測試用例失敗")
92 return HttpResponse("提交的執行測試用例為空,請選擇用例後在提交!")
93 test_cases = models.TestCase.objects.filter().order_by('id')
94 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
95
96
97 # 用例詳情頁
98 @login_required
99 def test_case_detail(request, test_case_id):
100 test_case_id = int(test_case_id)
101 test_case = models.TestCase.objects.get(id=test_case_id)
102 print("test_case: {}".format(test_case))
103 print("test_case.id: {}".format(test_case.id))
104 print("test_case.belong_project: {}".format(test_case.belong_project))
105
106 return render(request, 'test_case_detail.html', {'test_case': test_case})
107
108
109 # 模組頁展示測試用例
110 @login_required
111 def module_test_cases(request, module_id):
112 module = ""
113 if module_id: # 訪問的時候,會從url中提取模組的id,根據模組id查詢到模組資料,在模板中展現
114 module = models.Module.objects.get(id=int(module_id))
115 test_cases = models.TestCase.objects.filter(belong_module=module).order_by('-id')
116 print("test_case in module_test_cases: {}".format(test_cases))
117 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
118
119
120 # 用例集合-選單項
121 @login_required
122 def case_suite(request):
123 if request.method == "POST":
124 count_down_time = 0
125 if request.POST['delay_time']:
126 print("輸入的延遲時間是: {}".format(request.POST['delay_time']))
127 try:
128 count_down_time = int(request.POST['delay_time'])
129 except:
130 print("輸入的延遲時間是非數字!")
131 else:
132 print("沒有輸入延遲時間")
133 env = request.POST.getlist('env')
134 print("env: {}".format(env))
135 server_address = get_server_address(env)
136 if not server_address:
137 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
138 case_suite_list = request.POST.getlist('case_suite_list')
139 if case_suite_list:
140 print("所需執行的用例集合列表:", case_suite_list)
141 for suite_id in case_suite_list:
142 test_suite = models.CaseSuite.objects.get(id=int(suite_id))
143 print("所需執行的用例集合: {}".format(test_suite))
144 username = request.user.username
145 test_suite_record = models.CaseSuiteExecuteRecord.objects.create(case_suite=test_suite,
146 run_time_interval=count_down_time,
147 creator=username)
148 suite_task(test_suite_record, test_suite, server_address)
149 else:
150 print("執行測試集合用例失敗")
151 return HttpResponse("執行的測試集合為空,請選擇測試集合後再執行!")
152 case_suites = models.CaseSuite.objects.filter()
153 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
154
155
156 # 用例集合-新增測試用例頁
157 @login_required
158 def add_case_in_suite(request, suite_id):
159 # 查詢指定的用例集合
160 case_suite = models.CaseSuite.objects.get(id=suite_id)
161 # 根據id號查詢所有的用例
162 test_cases = models.TestCase.objects.filter().order_by('id')
163 if request.method == "GET":
164 print("test cases:", test_cases)
165 elif request.method == "POST":
166 test_cases_list = request.POST.getlist('testcases_list')
167 # 如果頁面勾選了用例
168 if test_cases_list:
169 print("勾選用例id:", test_cases_list)
170 # 根據頁面勾選的用例與查詢出的所有用例一一比較
171 for test_case in test_cases_list:
172 test_case = models.TestCase.objects.get(id=int(test_case))
173 # 匹配成功則新增用例
174 models.SuiteCase.objects.create(case_suite=case_suite, test_case=test_case)
175 # 未勾選用例
176 else:
177 print("新增測試用例失敗")
178 return HttpResponse("新增的測試用例為空,請選擇用例後再新增!")
179 return render(request, 'add_case_in_suite.html',
180 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
181
182
183 # 用例集合頁-檢視/刪除用例
184 @login_required
185 def show_and_delete_case_in_suite(request, suite_id):
186 case_suite = models.CaseSuite.objects.get(id=suite_id)
187 test_cases = models.SuiteCase.objects.filter(case_suite=case_suite)
188 if request.method == "POST":
189 test_cases_list = request.POST.getlist('test_cases_list')
190 if test_cases_list:
191 print("勾選用例:", test_cases_list)
192 for test_case in test_cases_list:
193 test_case = models.TestCase.objects.get(id=int(test_case))
194 models.SuiteCase.objects.filter(case_suite=case_suite, test_case=test_case).first().delete()
195 else:
196 print("測試用例刪除失敗")
197 return HttpResponse("所選測試用例為空,請選擇用例後再進行刪除!")
198 case_suite = models.CaseSuite.objects.get(id=suite_id)
199 return render(request, 'show_and_delete_case_in_suite.html',
200 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
201
202
203 # 用例執行結果-選單項
204 @login_required
205 def test_case_execute_record(request):
206 test_case_execute_records = models.TestCaseExecuteResult.objects.filter().order_by('-id')
207 return render(request, 'test_case_execute_records.html', {'test_case_execute_records':
208 get_paginator(request, test_case_execute_records)})
209
210
211 # 用例執行結果-對比差異
212 @login_required
213 def case_result_diff(request, test_record_id):
214 test_record_data = models.TestCaseExecuteResult.objects.get(id=test_record_id)
215 print("用例執行結果記錄: {}".format(test_record_data))
216 present_response = test_record_data.response_data
217 if present_response:
218 present_response = json.dumps(json.loads(present_response), sort_keys=True, indent=4,
219 ensure_ascii=False) # 中文字元不轉ascii編碼
220 print("當前響應結果: {}".format(present_response))
221 last_time_execute_response = test_record_data.last_time_response_data
222 if last_time_execute_response:
223 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True, indent=4,
224 ensure_ascii=False)
225 print("上一次響應結果: {}".format(last_time_execute_response))
226 return render(request, 'case_result_diff.html', locals())
227
228
229 # 用例執行結果-異常資訊展示
230 @login_required
231 def show_exception(request, execute_id):
232 test_record = models.TestCaseExecuteResult.objects.get(id=execute_id)
233 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
234
235
236 # 用例集合執行結果-選單項
237 @login_required
238 def case_suite_execute_record(request):
239 case_suite_execute_records = models.CaseSuiteExecuteRecord.objects.filter().order_by('-id')
240 return render(request, 'case_suite_execute_record.html',
241 {'case_suite_execute_records': get_paginator(request, case_suite_execute_records)})
242
243
244 # 預設頁的檢視函式
245 @login_required
246 def index(request):
247 return render(request, 'index.html')
248
249
250 # 登入頁的檢視函式
251 def login(request):
252 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
253 if request.session.get('is_login', None):
254 return redirect('/')
255 # 如果是表單提交行為,則進行登入校驗
256 if request.method == "POST":
257 login_form = UserForm(request.POST)
258 message = "請檢查填寫的內容!"
259 if login_form.is_valid():
260 username = login_form.cleaned_data['username']
261 password = login_form.cleaned_data['password']
262 try:
263 # 使用django提供的身份驗證功能
264 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
265 if user is not None:
266 print("使用者【%s】登入成功" % username)
267 auth.login(request, user)
268 request.session['is_login'] = True
269 # 登入成功,跳轉主頁
270 return redirect('/')
271 else:
272 message = "使用者名稱不存在或者密碼不正確!"
273 except:
274 traceback.print_exc()
275 message = "登入程式出現異常"
276 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
277 else:
278 return render(request, 'login.html', locals())
279 # 不是表單提交,代表只是訪問登入頁
280 else:
281 login_form = UserForm()
282 return render(request, 'login.html', locals())
283
284
285 # 註冊頁的檢視函式
286 def register(request):
287 return render(request, 'register.html')
288
289
290 # 登出的檢視函式:重定向至login檢視函式
291 @login_required
292 def logout(request):
293 auth.logout(request)
294 request.session.flush()
295 return redirect("/login/")

11.3 定義模板

1)新增“用例集合執行結果”模板:case_suite_execute_record.html

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}測試集合執行結果{% endblock %}
4
5 {% block content %}
6
7 <div class="table-responsive">
8 <table class="table table-striped">
9 <thead>
10 <tr>
11 <th>id</th>
12 <th>測試集合名稱</th>
13 <th>延遲執行時間</th>
14 <th>執行狀態</th>
15 <th>測試結果</th>
16 <th>建立者</th>
17 <th>建立時間</th>
18 </tr>
19 </thead>
20 <tbody>
21
22 {% for case_suite_execute_record in case_suite_execute_records %}
23 <tr>
24 <td>{{ case_suite_execute_record.id }}</td>
25 <td><a href="">{{ case_suite_execute_record.case_suite.suite_desc }}</a></td>
26 <td>{{ case_suite_execute_record.run_time_interval }}</td>
27 {% if case_suite_execute_record.status %}
28 <td>執行完畢</td>
29 {% else %}
30 <td>待執行</td>
31 {% endif %}
32 <td>{{ case_suite_execute_record.test_result|default_if_none:"" }}</td>
33 <td>{{ case_suite_execute_record.creator }}</td>
34 <td>{{ case_suite_execute_record.create_time|date:"Y-n-d H:i" }}</td>
35 </tr>
36 {% endfor %}
37
38
39 </tbody>
40 </table>
41 </div>
42
43 {# 實現分頁標籤的程式碼 #}
44 {# 這裡使用 bootstrap 渲染頁面 #}
45 <div id="pages" class="text-center">
46 <nav>
47 <ul class="pagination">
48 <li class="step-links">
49 {% if case_suite_execute_records.has_previous %}
50 <a class='active' href="?page={{ case_suite_execute_records.previous_page_number }}">上一頁</a>
51 {% endif %}
52
53 <span class="current">
54 第 {{ case_suite_execute_records.number }} 頁 / 共 {{ case_suite_execute_records.paginator.num_pages }} 頁</span>
55
56 {% if case_suite_execute_records.has_next %}
57 <a class='active' href="?page={{ case_suite_execute_records.next_page_number }}">下一頁</a>
58 {% endif %}
59 </li>
60 </ul>
61 </nav>
62 </div>
63 {% endblock %}

此時,頁面中展示了測試集合維度的執行結果,並沒有展示其包含的用例的結果,下面處理一下測試集合包含用例的結果展示。

11.4 集合包含用例結果展示

該頁面的實現邏輯與用例執行結果模組類似。

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"),
path('test_case_execute_record/', views.test_case_execute_record, name="test_case_execute_record"),
re_path('case_result_diff/(?P<test_record_id>[0-9]+)', views.case_result_diff, name="case_result_diff"),
re_path('show_exception/(?P<execute_id>[0-9]+)$', views.show_exception, name="show_exception"),
path('case_suite_execute_record/', views.case_suite_execute_record, name="case_suite_execute_record"),
re_path('suite_case_execute_record/(?P<suite_record_id>[0-9]+)', views.suite_case_execute_record, name="suite_case_execute_record"),
re_path('suite_case_result_diff/(?P<suite_case_record_id>[0-9]+)', views.suite_case_result_diff, name="suite_case_result_diff"),
re_path('suite_case_exception/(?P<suite_case_record_id>[0-9]+)', views.suite_case_exception, name="suite_case_exception"),
]

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 import json
8 from . import models
9 from .task import case_task, suite_task
10
11
12 # 封裝分頁處理
13 def get_paginator(request, data):
14 paginator = Paginator(data, 10) # 預設每頁展示10條資料
15 # 獲取 url 後面的 page 引數的值, 首頁不顯示 page 引數, 預設值是 1
16 page = request.GET.get('page')
17 try:
18 paginator_pages = paginator.page(page)
19 except PageNotAnInteger:
20 # 如果請求的頁數不是整數, 返回第一頁。
21 paginator_pages = paginator.page(1)
22 except InvalidPage:
23 # 如果請求的頁數不存在, 重定向頁面
24 return HttpResponse('找不到頁面的內容')
25 return paginator_pages
26
27
28 # 專案選單項
29 @login_required
30 def project(request):
31 print("request.user.is_authenticated: ", request.user.is_authenticated)
32 projects = models.Project.objects.filter().order_by('-id')
33 print("projects:", projects)
34 return render(request, 'project.html', {'projects': get_paginator(request, projects)})
35
36
37 # 模組選單項
38 @login_required
39 def module(request):
40 if request.method == "GET": # 請求get時候,id倒序查詢所有的模組資料
41 modules = models.Module.objects.filter().order_by('-id')
42 return render(request, 'module.html', {'modules': get_paginator(request, modules)})
43 else: # 否則就是Post請求,會根據輸入內容,使用模糊的方式查詢所有的專案
44 proj_name = request.POST['proj_name']
45 projects = models.Project.objects.filter(name__contains=proj_name.strip())
46 projs = [proj.id for proj in projects]
47 modules = models.Module.objects.filter(belong_project__in=projs) # 把專案中所有的模組都找出來
48 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
49
50
51 # 獲取測試用例執行的介面地址
52 def get_server_address(env):
53 if env: # 環境處理
54 env_data = models.InterfaceServer.objects.filter(env=env[0])
55 print("env_data: {}".format(env_data))
56 if env_data:
57 ip = env_data[0].ip
58 port = env_data[0].port
59 print("ip: {}, port: {}".format(ip, port))
60 server_address = "http://{}:{}".format(ip, port)
61 print("server_address: {}".format(server_address))
62 return server_address
63 else:
64 return ""
65 else:
66 return ""
67
68
69 # 測試用例選單項
70 @login_required
71 def test_case(request):
72 print("request.session['is_login']: {}".format(request.session['is_login']))
73 test_cases = ""
74 if request.method == "GET":
75 test_cases = models.TestCase.objects.filter().order_by('id')
76 print("testcases: {}".format(test_cases))
77 elif request.method == "POST":
78 print("request.POST: {}".format(request.POST))
79 test_case_id_list = request.POST.getlist('test_cases_list')
80 env = request.POST.getlist('env')
81 print("env: {}".format(env))
82 server_address = get_server_address(env)
83 if not server_address:
84 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
85 if test_case_id_list:
86 test_case_id_list.sort()
87 print("test_case_id_list: {}".format(test_case_id_list))
88 print("獲取到用例,開始用例執行")
89 case_task(test_case_id_list, server_address)
90 else:
91 print("執行測試用例失敗")
92 return HttpResponse("提交的執行測試用例為空,請選擇用例後在提交!")
93 test_cases = models.TestCase.objects.filter().order_by('id')
94 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
95
96
97 # 用例詳情頁
98 @login_required
99 def test_case_detail(request, test_case_id):
100 test_case_id = int(test_case_id)
101 test_case = models.TestCase.objects.get(id=test_case_id)
102 print("test_case: {}".format(test_case))
103 print("test_case.id: {}".format(test_case.id))
104 print("test_case.belong_project: {}".format(test_case.belong_project))
105
106 return render(request, 'test_case_detail.html', {'test_case': test_case})
107
108
109 # 模組頁展示測試用例
110 @login_required
111 def module_test_cases(request, module_id):
112 module = ""
113 if module_id: # 訪問的時候,會從url中提取模組的id,根據模組id查詢到模組資料,在模板中展現
114 module = models.Module.objects.get(id=int(module_id))
115 test_cases = models.TestCase.objects.filter(belong_module=module).order_by('-id')
116 print("test_case in module_test_cases: {}".format(test_cases))
117 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
118
119
120 # 用例集合選單項
121 @login_required
122 def case_suite(request):
123 if request.method == "POST":
124 count_down_time = 0
125 if request.POST['delay_time']:
126 print("輸入的延遲時間是: {}".format(request.POST['delay_time']))
127 try:
128 count_down_time = int(request.POST['delay_time'])
129 except:
130 print("輸入的延遲時間是非數字!")
131 else:
132 print("沒有輸入延遲時間")
133 env = request.POST.getlist('env')
134 print("env: {}".format(env))
135 server_address = get_server_address(env)
136 if not server_address:
137 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
138 case_suite_list = request.POST.getlist('case_suite_list')
139 if case_suite_list:
140 print("所需執行的用例集合列表:", case_suite_list)
141 for suite_id in case_suite_list:
142 test_suite = models.CaseSuite.objects.get(id=int(suite_id))
143 print("所需執行的用例集合: {}".format(test_suite))
144 username = request.user.username
145 test_suite_record = models.CaseSuiteExecuteRecord.objects.create(case_suite=test_suite,
146 run_time_interval=count_down_time,
147 creator=username)
148 suite_task(test_suite_record, test_suite, server_address)
149 else:
150 print("執行測試集合用例失敗")
151 return HttpResponse("執行的測試集合為空,請選擇測試集合後再執行!")
152 case_suites = models.CaseSuite.objects.filter()
153 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
154
155
156 # 用例集合-新增測試用例頁
157 @login_required
158 def add_case_in_suite(request, suite_id):
159 # 查詢指定的用例集合
160 case_suite = models.CaseSuite.objects.get(id=suite_id)
161 # 根據id號查詢所有的用例
162 test_cases = models.TestCase.objects.filter().order_by('id')
163 if request.method == "GET":
164 print("test cases:", test_cases)
165 elif request.method == "POST":
166 test_cases_list = request.POST.getlist('testcases_list')
167 # 如果頁面勾選了用例
168 if test_cases_list:
169 print("勾選用例id:", test_cases_list)
170 # 根據頁面勾選的用例與查詢出的所有用例一一比較
171 for test_case in test_cases_list:
172 test_case = models.TestCase.objects.get(id=int(test_case))
173 # 匹配成功則新增用例
174 models.SuiteCase.objects.create(case_suite=case_suite, test_case=test_case)
175 # 未勾選用例
176 else:
177 print("新增測試用例失敗")
178 return HttpResponse("新增的測試用例為空,請選擇用例後再新增!")
179 return render(request, 'add_case_in_suite.html',
180 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
181
182
183 # 用例集合頁-檢視/刪除用例
184 @login_required
185 def show_and_delete_case_in_suite(request, suite_id):
186 case_suite = models.CaseSuite.objects.get(id=suite_id)
187 test_cases = models.SuiteCase.objects.filter(case_suite=case_suite)
188 if request.method == "POST":
189 test_cases_list = request.POST.getlist('test_cases_list')
190 if test_cases_list:
191 print("勾選用例:", test_cases_list)
192 for test_case in test_cases_list:
193 test_case = models.TestCase.objects.get(id=int(test_case))
194 models.SuiteCase.objects.filter(case_suite=case_suite, test_case=test_case).first().delete()
195 else:
196 print("測試用例刪除失敗")
197 return HttpResponse("所選測試用例為空,請選擇用例後再進行刪除!")
198 case_suite = models.CaseSuite.objects.get(id=suite_id)
199 return render(request, 'show_and_delete_case_in_suite.html',
200 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
201
202
203 # 用例執行結果-選單項
204 @login_required
205 def test_case_execute_record(request):
206 test_case_execute_records = models.TestCaseExecuteResult.objects.filter().order_by('-id')
207 return render(request, 'test_case_execute_records.html', {'test_case_execute_records':
208 get_paginator(request, test_case_execute_records)})
209
210
211 # 用例執行結果-對比差異
212 @login_required
213 def case_result_diff(request, test_record_id):
214 test_record_data = models.TestCaseExecuteResult.objects.get(id=test_record_id)
215 print("用例執行結果記錄: {}".format(test_record_data))
216 present_response = test_record_data.response_data
217 if present_response:
218 present_response = json.dumps(json.loads(present_response), sort_keys=True, indent=4,
219 ensure_ascii=False) # 中文字元不轉ascii編碼
220 print("當前響應結果: {}".format(present_response))
221 last_time_execute_response = test_record_data.last_time_response_data
222 if last_time_execute_response:
223 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True, indent=4,
224 ensure_ascii=False)
225 print("上一次響應結果: {}".format(last_time_execute_response))
226 return render(request, 'case_result_diff.html', locals())
227
228
229 # 用例執行結果-異常資訊展示
230 @login_required
231 def show_exception(request, execute_id):
232 test_record = models.TestCaseExecuteResult.objects.get(id=execute_id)
233 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
234
235
236 # 用例集合執行結果
237 @login_required
238 def case_suite_execute_record(request):
239 case_suite_execute_record = models.CaseSuiteExecuteRecord.objects.filter().order_by('-id')
240 return render(request, 'case_suite_execute_record.html',
241 {'case_suite_execute_records': get_paginator(request, case_suite_execute_record)})
242
243
244 # 用例集合執行結果-包含用例結果展示
245 @login_required
246 def suite_case_execute_record(request, suite_record_id):
247 case_suite_execute_record = models.CaseSuiteExecuteRecord.objects.get(id=suite_record_id)
248 suite_case_execute_records = models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=case_suite_execute_record)
249 return render(request, 'suite_case_execute_record.html',
250 {'suite_case_execute_records': get_paginator(request, suite_case_execute_records)})
251
252
253 # 用例集合執行結果-包含用例結果展示-差異比對
254 @login_required
255 def suite_case_result_diff(request, suite_case_record_id):
256 suite_record_data = models.CaseSuiteTestCaseExecuteRecord.objects.get(id=suite_case_record_id)
257 present_response = suite_record_data.response_data
258 if present_response:
259 present_response = json.dumps(json.loads(present_response),sort_keys=True, indent=4, ensure_ascii=False)
260 print("當前響應: {}".format(present_response))
261 last_time_execute_response = suite_record_data.last_time_response_data
262 if last_time_execute_response:
263 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True,
264 indent=4, ensure_ascii=False)
265 print("上一次響應: {}".format(last_time_execute_response))
266 return render(request, 'case_result_diff.html', locals())
267
268
269 # 用例集合執行結果-包含用例結果展示-異常資訊展示
270 @login_required
271 def suite_case_exception(request, suite_case_record_id):
272 test_record = models.CaseSuiteTestCaseExecuteRecord.objects.get(id=suite_case_record_id)
273 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
274
275
276 # 預設頁的檢視函式
277 @login_required
278 def index(request):
279 return render(request, 'index.html')
280
281
282 # 登入頁的檢視函式
283 def login(request):
284 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
285 if request.session.get('is_login', None):
286 return redirect('/')
287 # 如果是表單提交行為,則進行登入校驗
288 if request.method == "POST":
289 login_form = UserForm(request.POST)
290 message = "請檢查填寫的內容!"
291 if login_form.is_valid():
292 username = login_form.cleaned_data['username']
293 password = login_form.cleaned_data['password']
294 try:
295 # 使用django提供的身份驗證功能
296 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
297 if user is not None:
298 print("使用者【%s】登入成功" % username)
299 auth.login(request, user)
300 request.session['is_login'] = True
301 # 登入成功,跳轉主頁
302 return redirect('/')
303 else:
304 message = "使用者名稱不存在或者密碼不正確!"
305 except:
306 traceback.print_exc()
307 message = "登入程式出現異常"
308 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
309 else:
310 return render(request, 'login.html', locals())
311 # 不是表單提交,代表只是訪問登入頁
312 else:
313 login_form = UserForm()
314 return render(request, 'login.html', locals())
315
316
317 # 註冊頁的檢視函式
318 def register(request):
319 return render(request, 'register.html')
320
321
322 # 登出的檢視函式:重定向至login檢視函式
323 @login_required
324 def logout(request):
325 auth.logout(request)
326 request.session.flush()
327 return redirect("/login/")

3)定義模板

修改“測試集合執行結果”模板中,測試集合名稱連結:

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}測試集合執行結果{% endblock %}
4
5 {% block content %}
6
7 <div class="table-responsive">
8 <table class="table table-striped">
9 <thead>
10 <tr>
11 <th>id</th>
12 <th>測試集合名稱</th>
13 <th>延遲執行時間</th>
14 <th>執行狀態</th>
15 <th>測試結果</th>
16 <th>建立者</th>
17 <th>建立時間</th>
18 </tr>
19 </thead>
20 <tbody>
21
22 {% for case_suite_execute_record in case_suite_execute_records %}
23 <tr>
24 <td>{{ case_suite_execute_record.id }}</td>
25 <td><a href="{% url 'suite_case_execute_record' case_suite_execute_record.id %}">{{ case_suite_execute_record.case_suite.suite_desc }}</a></td>
26 <td>{{ case_suite_execute_record.run_time_interval }}</td>
27 {% if case_suite_execute_record.status %}
28 <td>執行完畢</td>
29 {% else %}
30 <td>待執行</td>
31 {% endif %}
32
33 {% ifequal case_suite_execute_record.test_result '成功' %}
34 <td bgcolor='green'>{{ case_suite_execute_record.test_result}}</td>
35 {% else %}
36 <td bgcolor='red'>{{ case_suite_execute_record.test_result}}</td>
37 {% endifequal %}
38
39 <!--<td>{{ case_suite_execute_record.test_result|default_if_none:"" }}-->
40 <td>{{ case_suite_execute_record.creator }}</td>
41 <td>{{ case_suite_execute_record.create_time|date:"Y-n-d H:i" }}</td>
42 </tr>
43 {% endfor %}
44
45
46 </tbody>
47 </table>
48 </div>
49
50 {# 實現分頁標籤的程式碼 #}
51 {# 這裡使用 bootstrap 渲染頁面 #}
52 <div id="pages" class="text-center">
53 <nav>
54 <ul class="pagination">
55 <li class="step-links">
56 {% if case_suite_execute_records.has_previous %}
57 <a class='active' href="?page={{ case_suite_execute_records.previous_page_number }}">上一頁</a>
58 {% endif %}
59
60 <span class="current">
61 第 {{ case_suite_execute_records.number }} 頁 / 共 {{ case_suite_execute_records.paginator.num_pages }} 頁</span>
62
63 {% if case_suite_execute_records.has_next %}
64 <a class='active' href="?page={{ case_suite_execute_records.next_page_number }}">下一頁</a>
65 {% endif %}
66 </li>
67 </ul>
68 </nav>
69 </div>
70 {% endblock %}

新增用例集合關聯的測試用例執行結果頁:templates/suite_case_execute_record.html

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}用例集合用例執行結果{% endblock %}
4
5 {% block content %}
6
7 <div class="table-responsive">
8 <table class="table table-striped">
9 <thead>
10 <tr>
11 <th width="5%">執行id</th>
12 <th width="6%">集合名稱</th>
13 <th width="5%">用例名稱</th>
14 <th width="5%">狀態</th>
15 <th width="18%">請求資料</th>
16 <th width="15%">執行返回結果</th>
17 <th width="5%">操作</th>
18 <th width="8%">斷言內容</th>
19 <th width="5%">執行結果</th>
20 <th width="5%">異常資訊</th>
21 <th width="8%">請求後提取變數</th>
22 <th width="8%">開始時間</th>
23 <th width="8%">執行耗時(ms)</th>
24 </tr>
25 </thead>
26 <tbody>
27
28 {% for case_execute_record in suite_case_execute_records %}
29 <tr>
30 <td>{{ case_execute_record.id }}</td>
31 <td>{{ case_execute_record.case_suite_record.case_suite.suite_desc }}</td>
32 <td><a href="{% url 'suite_case_execute_record' case_execute_record.test_case.id%}">{{ case_execute_record.test_case.case_name }}</a></td>
33 {% if case_execute_record.status %}
34 <td>執行完畢</td>
35 {% else %}
36 <td>待執行</td>
37 {% endif %}
38 <td>{{ case_execute_record.request_data }}</td>
39 <td>{{ case_execute_record.response_data }}</td>
40 <td><a href="{% url 'suite_case_result_diff' case_execute_record.id%}" target="_blank">對比差異</a></td>
41 <td>{{ case_execute_record.test_case.assert_key }}</td>
42
43 {% ifequal case_execute_record.execute_result '成功' %}
44 <td bgcolor='green'>{{ case_execute_record.execute_result}}</td>
45 {% else %}
46 <td bgcolor='red'>{{ case_execute_record.execute_result}}</td>
47 {% endifequal %}
48
49 <!--<td>{{ case_execute_record.execute_result|default_if_none:"" }}</td>--!>
50 {% if case_execute_record.exception_info %}
51 <td><a href="{% url 'suite_case_exception' case_execute_record.id%}" target="_blank">顯示異常</a></td>
52 {% else %}
53 <td>無</td>
54 {% endif %}
55 <td>{{ case_execute_record.extract_var }}</td>
56 <td>{{ case_execute_record.execute_start_time }}</td>
57 <td>{{ case_execute_record.execute_total_time }}</td>
58
59 </tr>
60 {% endfor %}
61
62
63 </tbody>
64 </table>
65 </div>
66
67 {# 實現分頁標籤的程式碼 #}
68 {# 這裡使用 bootstrap 渲染頁面 #}
69 <div id="pages" class="text-center" >
70 <nav>
71 <ul class="pagination">
72 <li class="step-links">
73 {% if suite_case_execute_records.has_previous %}
74 <a class='active' href="?page={{ suite_case_execute_records.previous_page_number }}">上一頁</a>
75 {% endif %}
76
77 <span class="current">
78 第 {{ suite_case_execute_records.number }} 頁 / 共 {{ suite_case_execute_records.paginator.num_pages }} 頁</span>
79
80 {% if suite_case_execute_records.has_next %}
81 <a class='active' href="?page={{ suite_case_execute_records.next_page_number }}">下一頁</a>
82 {% endif %}
83 </li></ul></nav></div>
84 {% endblock %}

12. 用例集合歷史執行結果統計

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"),
path('test_case_execute_record/', views.test_case_execute_record, name="test_case_execute_record"),
re_path('case_result_diff/(?P<test_record_id>[0-9]+)', views.case_result_diff, name="case_result_diff"),
re_path('show_exception/(?P<execute_id>[0-9]+)$', views.show_exception, name="show_exception"),
path('case_suite_execute_record/', views.case_suite_execute_record, name="case_suite_execute_record"),
re_path('suite_case_execute_record/(?P<suite_record_id>[0-9]+)', views.suite_case_execute_record, name="suite_case_execute_record"),
re_path('suite_case_result_diff/(?P<suite_case_record_id>[0-9]+)', views.suite_case_result_diff, name="suite_case_result_diff"),
re_path('suite_case_exception/(?P<suite_case_record_id>[0-9]+)', views.suite_case_exception, name="suite_case_exception"),
re_path('case_suite_statistics/(?P<suite_id>[0-9]+)', views.case_suite_statistics, name="case_suite_statistics"),
]

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 import json
8 from . import models
9 from .task import case_task, suite_task
10
11
12 # 封裝分頁處理
13 def get_paginator(request, data):
14 paginator = Paginator(data, 10) # 預設每頁展示10條資料
15 # 獲取 url 後面的 page 引數的值, 首頁不顯示 page 引數, 預設值是 1
16 page = request.GET.get('page')
17 try:
18 paginator_pages = paginator.page(page)
19 except PageNotAnInteger:
20 # 如果請求的頁數不是整數, 返回第一頁。
21 paginator_pages = paginator.page(1)
22 except InvalidPage:
23 # 如果請求的頁數不存在, 重定向頁面
24 return HttpResponse('找不到頁面的內容')
25 return paginator_pages
26
27
28 # 專案選單項
29 @login_required
30 def project(request):
31 print("request.user.is_authenticated: ", request.user.is_authenticated)
32 projects = models.Project.objects.filter().order_by('-id')
33 print("projects:", projects)
34 return render(request, 'project.html', {'projects': get_paginator(request, projects)})
35
36
37 # 模組選單項
38 @login_required
39 def module(request):
40 if request.method == "GET": # 請求get時候,id倒序查詢所有的模組資料
41 modules = models.Module.objects.filter().order_by('-id')
42 return render(request, 'module.html', {'modules': get_paginator(request, modules)})
43 else: # 否則就是Post請求,會根據輸入內容,使用模糊的方式查詢所有的專案
44 proj_name = request.POST['proj_name']
45 projects = models.Project.objects.filter(name__contains=proj_name.strip())
46 projs = [proj.id for proj in projects]
47 modules = models.Module.objects.filter(belong_project__in=projs) # 把專案中所有的模組都找出來
48 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
49
50
51 # 獲取測試用例執行的介面地址
52 def get_server_address(env):
53 if env: # 環境處理
54 env_data = models.InterfaceServer.objects.filter(env=env[0])
55 print("env_data: {}".format(env_data))
56 if env_data:
57 ip = env_data[0].ip
58 port = env_data[0].port
59 print("ip: {}, port: {}".format(ip, port))
60 server_address = "http://{}:{}".format(ip, port)
61 print("server_address: {}".format(server_address))
62 return server_address
63 else:
64 return ""
65 else:
66 return ""
67
68
69 # 測試用例選單項
70 @login_required
71 def test_case(request):
72 print("request.session['is_login']: {}".format(request.session['is_login']))
73 test_cases = ""
74 if request.method == "GET":
75 test_cases = models.TestCase.objects.filter().order_by('id')
76 print("testcases: {}".format(test_cases))
77 elif request.method == "POST":
78 print("request.POST: {}".format(request.POST))
79 test_case_id_list = request.POST.getlist('test_cases_list')
80 env = request.POST.getlist('env')
81 print("env: {}".format(env))
82 server_address = get_server_address(env)
83 if not server_address:
84 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
85 if test_case_id_list:
86 test_case_id_list.sort()
87 print("test_case_id_list: {}".format(test_case_id_list))
88 print("獲取到用例,開始用例執行")
89 case_task(test_case_id_list, server_address)
90 else:
91 print("執行測試用例失敗")
92 return HttpResponse("提交的執行測試用例為空,請選擇用例後在提交!")
93 test_cases = models.TestCase.objects.filter().order_by('id')
94 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
95
96
97 # 用例詳情頁
98 @login_required
99 def test_case_detail(request, test_case_id):
100 test_case_id = int(test_case_id)
101 test_case = models.TestCase.objects.get(id=test_case_id)
102 print("test_case: {}".format(test_case))
103 print("test_case.id: {}".format(test_case.id))
104 print("test_case.belong_project: {}".format(test_case.belong_project))
105
106 return render(request, 'test_case_detail.html', {'test_case': test_case})
107
108
109 # 模組頁展示測試用例
110 @login_required
111 def module_test_cases(request, module_id):
112 module = ""
113 if module_id: # 訪問的時候,會從url中提取模組的id,根據模組id查詢到模組資料,在模板中展現
114 module = models.Module.objects.get(id=int(module_id))
115 test_cases = models.TestCase.objects.filter(belong_module=module).order_by('-id')
116 print("test_case in module_test_cases: {}".format(test_cases))
117 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
118
119
120 # 用例集合選單項
121 @login_required
122 def case_suite(request):
123 if request.method == "POST":
124 count_down_time = 0
125 if request.POST['delay_time']:
126 print("輸入的延遲時間是: {}".format(request.POST['delay_time']))
127 try:
128 count_down_time = int(request.POST['delay_time'])
129 except:
130 print("輸入的延遲時間是非數字!")
131 else:
132 print("沒有輸入延遲時間")
133 env = request.POST.getlist('env')
134 print("env: {}".format(env))
135 server_address = get_server_address(env)
136 if not server_address:
137 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
138 case_suite_list = request.POST.getlist('case_suite_list')
139 if case_suite_list:
140 print("所需執行的用例集合列表:", case_suite_list)
141 for suite_id in case_suite_list:
142 test_suite = models.CaseSuite.objects.get(id=int(suite_id))
143 print("所需執行的用例集合: {}".format(test_suite))
144 username = request.user.username
145 test_suite_record = models.CaseSuiteExecuteRecord.objects.create(case_suite=test_suite,
146 run_time_interval=count_down_time,
147 creator=username)
148 suite_task(test_suite_record, test_suite, server_address)
149 else:
150 print("執行測試集合用例失敗")
151 return HttpResponse("執行的測試集合為空,請選擇測試集合後再執行!")
152 case_suites = models.CaseSuite.objects.filter()
153 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
154
155
156 # 用例集合-新增測試用例頁
157 @login_required
158 def add_case_in_suite(request, suite_id):
159 # 查詢指定的用例集合
160 case_suite = models.CaseSuite.objects.get(id=suite_id)
161 # 根據id號查詢所有的用例
162 test_cases = models.TestCase.objects.filter().order_by('id')
163 if request.method == "GET":
164 print("test cases:", test_cases)
165 elif request.method == "POST":
166 test_cases_list = request.POST.getlist('testcases_list')
167 # 如果頁面勾選了用例
168 if test_cases_list:
169 print("勾選用例id:", test_cases_list)
170 # 根據頁面勾選的用例與查詢出的所有用例一一比較
171 for test_case in test_cases_list:
172 test_case = models.TestCase.objects.get(id=int(test_case))
173 # 匹配成功則新增用例
174 models.SuiteCase.objects.create(case_suite=case_suite, test_case=test_case)
175 # 未勾選用例
176 else:
177 print("新增測試用例失敗")
178 return HttpResponse("新增的測試用例為空,請選擇用例後再新增!")
179 return render(request, 'add_case_in_suite.html',
180 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
181
182
183 # 用例集合頁-檢視/刪除用例
184 @login_required
185 def show_and_delete_case_in_suite(request, suite_id):
186 case_suite = models.CaseSuite.objects.get(id=suite_id)
187 test_cases = models.SuiteCase.objects.filter(case_suite=case_suite)
188 if request.method == "POST":
189 test_cases_list = request.POST.getlist('test_cases_list')
190 if test_cases_list:
191 print("勾選用例:", test_cases_list)
192 for test_case in test_cases_list:
193 test_case = models.TestCase.objects.get(id=int(test_case))
194 models.SuiteCase.objects.filter(case_suite=case_suite, test_case=test_case).first().delete()
195 else:
196 print("測試用例刪除失敗")
197 return HttpResponse("所選測試用例為空,請選擇用例後再進行刪除!")
198 case_suite = models.CaseSuite.objects.get(id=suite_id)
199 return render(request, 'show_and_delete_case_in_suite.html',
200 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
201
202
203 # 用例執行結果-選單項
204 @login_required
205 def test_case_execute_record(request):
206 test_case_execute_records = models.TestCaseExecuteResult.objects.filter().order_by('-id')
207 return render(request, 'test_case_execute_records.html', {'test_case_execute_records':
208 get_paginator(request, test_case_execute_records)})
209
210
211 # 用例執行結果-對比差異
212 @login_required
213 def case_result_diff(request, test_record_id):
214 test_record_data = models.TestCaseExecuteResult.objects.get(id=test_record_id)
215 print("用例執行結果記錄: {}".format(test_record_data))
216 present_response = test_record_data.response_data
217 if present_response:
218 present_response = json.dumps(json.loads(present_response), sort_keys=True, indent=4,
219 ensure_ascii=False) # 中文字元不轉ascii編碼
220 print("當前響應結果: {}".format(present_response))
221 last_time_execute_response = test_record_data.last_time_response_data
222 if last_time_execute_response:
223 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True, indent=4,
224 ensure_ascii=False)
225 print("上一次響應結果: {}".format(last_time_execute_response))
226 return render(request, 'case_result_diff.html', locals())
227
228
229 # 用例執行結果-異常資訊展示
230 @login_required
231 def show_exception(request, execute_id):
232 test_record = models.TestCaseExecuteResult.objects.get(id=execute_id)
233 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
234
235
236 # 用例集合執行結果
237 @login_required
238 def case_suite_execute_record(request):
239 case_suite_execute_record = models.CaseSuiteExecuteRecord.objects.filter().order_by('-id')
240 return render(request, 'case_suite_execute_record.html',
241 {'case_suite_execute_records': get_paginator(request, case_suite_execute_record)})
242
243
244 # 用例集合執行結果-包含用例結果展示
245 @login_required
246 def suite_case_execute_record(request, suite_record_id):
247 case_suite_execute_record = models.CaseSuiteExecuteRecord.objects.get(id=suite_record_id)
248 suite_case_execute_records = models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=case_suite_execute_record)
249 return render(request, 'suite_case_execute_record.html',
250 {'suite_case_execute_records': get_paginator(request, suite_case_execute_records)})
251
252
253 # 用例集合執行結果-包含用例結果展示-差異比對
254 @login_required
255 def suite_case_result_diff(request, suite_case_record_id):
256 suite_record_data = models.CaseSuiteTestCaseExecuteRecord.objects.get(id=suite_case_record_id)
257 present_response = suite_record_data.response_data
258 if present_response:
259 present_response = json.dumps(json.loads(present_response),sort_keys=True, indent=4, ensure_ascii=False)
260 print("當前響應: {}".format(present_response))
261 last_time_execute_response = suite_record_data.last_time_response_data
262 if last_time_execute_response:
263 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True,
264 indent=4, ensure_ascii=False)
265 print("上一次響應: {}".format(last_time_execute_response))
266 return render(request, 'case_result_diff.html', locals())
267
268
269 # 用例集合執行結果-包含用例結果展示-異常資訊展示
270 @login_required
271 def suite_case_exception(request, suite_case_record_id):
272 test_record = models.CaseSuiteTestCaseExecuteRecord.objects.get(id=suite_case_record_id)
273 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
274
275
276 # 用例集合執行結果統計
277 def case_suite_statistics(request, suite_id):
278 case_suite = models.CaseSuite.objects.get(id=suite_id)
279 success_num = len(models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite, test_result="成功"))
280 fail_num = len(models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite, test_result="失敗"))
281 case_suite_records = models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite).order_by('-id')
282 return render(request, 'case_suite_statistics.html',
283 {'case_suite_records': get_paginator(request, case_suite_records), 'success_num': success_num,
284 'fail_num': fail_num})
285
286
287 # 預設頁的檢視函式
288 @login_required
289 def index(request):
290 return render(request, 'index.html')
291
292
293 # 登入頁的檢視函式
294 def login(request):
295 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
296 if request.session.get('is_login', None):
297 return redirect('/')
298 # 如果是表單提交行為,則進行登入校驗
299 if request.method == "POST":
300 login_form = UserForm(request.POST)
301 message = "請檢查填寫的內容!"
302 if login_form.is_valid():
303 username = login_form.cleaned_data['username']
304 password = login_form.cleaned_data['password']
305 try:
306 # 使用django提供的身份驗證功能
307 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
308 if user is not None:
309 print("使用者【%s】登入成功" % username)
310 auth.login(request, user)
311 request.session['is_login'] = True
312 # 登入成功,跳轉主頁
313 return redirect('/')
314 else:
315 message = "使用者名稱不存在或者密碼不正確!"
316 except:
317 traceback.print_exc()
318 message = "登入程式出現異常"
319 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
320 else:
321 return render(request, 'login.html', locals())
322 # 不是表單提交,代表只是訪問登入頁
323 else:
324 login_form = UserForm()
325 return render(request, 'login.html', locals())
326
327
328 # 註冊頁的檢視函式
329 def register(request):
330 return render(request, 'register.html')
331
332
333 # 登出的檢視函式:重定向至login檢視函式
334 @login_required
335 def logout(request):
336 auth.logout(request)
337 request.session.flush()
338 return redirect("/login/")

3)定義模板

修改 case_suite.html 模板,增加檢視統計結果連結:

  1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}用例集合{% endblock %}
4 {% block content %}
5 <script>
6 //頁面載入的時候,所有的複選框都是未選中的狀態
7 function checkOrCancelAll() {
8 var all_check = document.getElementById("all_check"); // 1.獲取all的元素物件
9 var all_check = all_check.checked; // 2.獲取選中狀態
10 // 3.若checked=true,將所有的複選框選中,checked=false,將所有的複選框取消
11 var allCheck = document.getElementsByName("case_suite_list");
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 function ischecked() {
27 // 3.若checked=true,將所有的複選框選中,checked=false,將所有的複選框取消
28 var allCheck = document.getElementsByName("case_suite_list");
29 for (var i = 0; i < allCheck.length; i++) {
30
31 if (allCheck[i].checked == true) {
32 alert("所需執行的測試集合提交成功!");
33 return true
34 }
35 }
36 alert("請選擇要執行的測試集合!")
37 return false
38 }
39 </script>
40
41 <form action="" method="POST">
42 {% csrf_token %}
43 <span style="margin-left: 5px;">延遲執行的時間(單位:秒):</span>
44 <input type="text" style="width: 70px; margin-left: 5px; margin-right: 10px;" placeholder="請輸入" name="delay_time"/>
45 <span style="margin-left: 5px;">執行環境:</span>
46 <select name="env">
47 <option selected value="dev">dev</option>
48 <option value="prod">prod</option>
49 </select>
50 <input style="margin-left: 10px;" type="submit" id="all_check1" value='執行測試集合' onclick="return ischecked()"/>
51
52 <div class="table-responsive">
53 <table class="table table-striped">
54 <thead>
55 <tr>
56 <th><input type="checkbox" id="all_check" onclick="checkOrCancelAll();"/>全選</th>
57 <th>id</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 case_suite in case_suites %}
69 <tr>
70 <td><input type="checkbox" value="{{ case_suite.id }}" name="case_suite_list"></td>
71 <td>{{ case_suite.id }}</td>
72 <td>{{ case_suite.suite_desc }}</td>
73 <td>{{ case_suite.creator }}</td>
74 <td>{{ case_suite.create_time|date:"Y-n-d H:i" }}</td>
75 <td><a href="{% url 'show_and_delete_case_in_suite' case_suite.id %}">檢視/刪除測試用例</a></td>
76 <td><a href="{% url 'add_case_in_suite' case_suite.id %}">新增測試用例</a></td>
77 <td><a href="{% url 'case_suite_statistics' case_suite.id %}">檢視用例集合執行結果</a></td>
78 </tr>
79 {% endfor %}
80 </tbody>
81 </table>
82 </div>
83 </form>
84
85 {# 實現分頁標籤的程式碼 #}
86 {# 這裡使用 bootstrap 渲染頁面 #}
87 <div id="pages" class="text-center">
88 <nav>
89 <ul class="pagination">
90 <li class="step-links">
91 {% if case_suites.has_previous %}
92 <a class='active' href="?page={{ case_suites.previous_page_number }}">上一頁</a>
93 {% endif %}
94
95 <span class="current">
96 第 {{ case_suites.number }} 頁 / 共 {{ case_suites.paginator.num_pages }} 頁</span>
97
98 {% if case_suites.has_next %}
99 <a class='active' href="?page={{ case_suites.next_page_number }}">下一頁</a>
100 {% endif %}
101 </li>
102 </ul>
103 </nav>
104 </div>
105 {% endblock %}

新增統計結果頁面模板:templates/case_suite_statistics.html

  1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}測試集合結果統計{% endblock %}
4 {% block content %}
5
6
7 <body>
8 <p style="margin-left: 10px;">
9 <span style="margin-left: 5px;">用例集合執行結果統計:成功 {{ success_num }} 次,失敗 {{ fail_num }} 次</span>
10 <p>
11 <div id="main" style="width: 600px;height:400px; margin-left: 10px;"></div>
12 <!--<script src="{% static 'js/echarts.simple.min.js' %}"></script>-->
13 <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
14 <script>
15 // 繪製圖表。
16 echarts.init(document.getElementById('main')).setOption({
17 series: {
18 type: 'pie',
19 color: ['green', 'red'],
20 data: [
21 {name: '成功的次數', value: {{ success_num }}},
22 {name: '失敗的次數', value: {{ fail_num }}},
23 ]
24 }
25 });
26 echarts.init(document.getElementById('main')).setOption({
27 title: {
28 text: '結果統計',
29 subtext: '即時資料',
30 left: 'center'
31 },
32 tooltip: {
33 trigger: 'item'
34 },
35 legend: {
36 orient: 'vertical',
37 left: 'left'
38 },
39 series: {
40 name: '結果統計',
41 radius: '55%',
42 type: 'pie',
43 color: ['green', 'red'],
44 data: [
45 {name: '執行成功次數', value: {{ success_num }}},
46 {name: '執行失敗次數', value: {{ fail_num }}},
47
48 ],
49 label:{ // 餅圖圖形上的文字標籤
50 normal:{
51 show:true,
52 formatter: "{b} : {c} ({d}%)"
53 }
54 }
55 }
56 });
57 </script>
58
59 <div class="table-responsive">
60 <table class="table table-striped">
61 <thead>
62 <tr>
63 <th>id</th>
64 <th>測試集合名稱</th>
65 <th>延遲執行時間</th>
66 <th>執行狀態</th>
67 <th>測試結果</th>
68 <th>建立者</th>
69 <th>建立時間</th>
70 </tr>
71 </thead>
72 <tbody>
73
74 {% for case_suite_record in case_suite_records %}
75 <tr>
76 <td>{{ case_suite_record.id }}</td>
77 <td><a href="{% url 'suite_case_execute_record' case_suite_record.id %}">{{ case_suite_record.case_suite.suite_desc }}</a></td>
78 <td>{{ case_suite_record.run_time_interval }}</a></td>
79 {% if case_suite_record.status %}
80 <td>執行完畢</td>
81 {% else %}
82 <td>待執行</td>
83 {% endif %}
84 <td>{{ case_suite_record.test_result|default_if_none:"" }}</a></td>
85 <td>{{ case_suite_record.creator }}</td>
86 <td>{{ case_suite_record.create_time|date:"Y-n-d H:i" }}</td>
87 </tr>
88 {% endfor %}
89
90 </tbody>
91 </table>
92 </div>
93
94 {# 實現分頁標籤的程式碼 #}
95 {# 這裡使用 bootstrap 渲染頁面 #}
96 <div id="pages" class="text-center">
97 <nav>
98 <ul class="pagination">
99 <li class="step-links">
100 {% if case_suite_records.has_previous %}
101 <a class='active' href="?page={{ case_suite_records.previous_page_number }}">上一頁</a>
102 {% endif %}
103
104 <span class="current">
105 第 {{ case_suite_records.number }} 頁 / 共 {{ case_suite_records.paginator.num_pages }} 頁</span>
106
107 {% if case_suite_records.has_next %}
108 <a class='active' href="?page={{ case_suite_records.next_page_number }}">下一頁</a>
109 {% endif %}
110 </li>
111 </ul>
112 </nav>
113 </div>
114 </body>
115
116 {% endblock %}

13. 用例集合單次執行結果統計

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"),
path('test_case_execute_record/', views.test_case_execute_record, name="test_case_execute_record"),
re_path('case_result_diff/(?P<test_record_id>[0-9]+)', views.case_result_diff, name="case_result_diff"),
re_path('show_exception/(?P<execute_id>[0-9]+)$', views.show_exception, name="show_exception"),
path('case_suite_execute_record/', views.case_suite_execute_record, name="case_suite_execute_record"),
re_path('suite_case_execute_record/(?P<suite_record_id>[0-9]+)', views.suite_case_execute_record, name="suite_case_execute_record"),
re_path('suite_case_result_diff/(?P<suite_case_record_id>[0-9]+)', views.suite_case_result_diff, name="suite_case_result_diff"),
re_path('suite_case_exception/(?P<suite_case_record_id>[0-9]+)', views.suite_case_exception, name="suite_case_exception"),
re_path('suite_case_statistics/(?P<suite_id>[0-9]+)', views.suite_case_statistics, name="suite_case_statistics"),
re_path('case_suite_statistics/(?P<suite_id>[0-9]+)', views.case_suite_statistics, name="case_suite_statistics"),
]

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 import json
8 from . import models
9 from .task import case_task, suite_task
10
11
12 # 封裝分頁處理
13 def get_paginator(request, data):
14 paginator = Paginator(data, 10) # 預設每頁展示10條資料
15 # 獲取 url 後面的 page 引數的值, 首頁不顯示 page 引數, 預設值是 1
16 page = request.GET.get('page')
17 try:
18 paginator_pages = paginator.page(page)
19 except PageNotAnInteger:
20 # 如果請求的頁數不是整數, 返回第一頁。
21 paginator_pages = paginator.page(1)
22 except InvalidPage:
23 # 如果請求的頁數不存在, 重定向頁面
24 return HttpResponse('找不到頁面的內容')
25 return paginator_pages
26
27
28 # 專案選單項
29 @login_required
30 def project(request):
31 print("request.user.is_authenticated: ", request.user.is_authenticated)
32 projects = models.Project.objects.filter().order_by('-id')
33 print("projects:", projects)
34 return render(request, 'project.html', {'projects': get_paginator(request, projects)})
35
36
37 # 模組選單項
38 @login_required
39 def module(request):
40 if request.method == "GET": # 請求get時候,id倒序查詢所有的模組資料
41 modules = models.Module.objects.filter().order_by('-id')
42 return render(request, 'module.html', {'modules': get_paginator(request, modules)})
43 else: # 否則就是Post請求,會根據輸入內容,使用模糊的方式查詢所有的專案
44 proj_name = request.POST['proj_name']
45 projects = models.Project.objects.filter(name__contains=proj_name.strip())
46 projs = [proj.id for proj in projects]
47 modules = models.Module.objects.filter(belong_project__in=projs) # 把專案中所有的模組都找出來
48 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
49
50
51 # 獲取測試用例執行的介面地址
52 def get_server_address(env):
53 if env: # 環境處理
54 env_data = models.InterfaceServer.objects.filter(env=env[0])
55 print("env_data: {}".format(env_data))
56 if env_data:
57 ip = env_data[0].ip
58 port = env_data[0].port
59 print("ip: {}, port: {}".format(ip, port))
60 server_address = "http://{}:{}".format(ip, port)
61 print("server_address: {}".format(server_address))
62 return server_address
63 else:
64 return ""
65 else:
66 return ""
67
68
69 # 測試用例選單項
70 @login_required
71 def test_case(request):
72 print("request.session['is_login']: {}".format(request.session['is_login']))
73 test_cases = ""
74 if request.method == "GET":
75 test_cases = models.TestCase.objects.filter().order_by('id')
76 print("testcases: {}".format(test_cases))
77 elif request.method == "POST":
78 print("request.POST: {}".format(request.POST))
79 test_case_id_list = request.POST.getlist('test_cases_list')
80 env = request.POST.getlist('env')
81 print("env: {}".format(env))
82 server_address = get_server_address(env)
83 if not server_address:
84 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
85 if test_case_id_list:
86 test_case_id_list.sort()
87 print("test_case_id_list: {}".format(test_case_id_list))
88 print("獲取到用例,開始用例執行")
89 case_task(test_case_id_list, server_address)
90 else:
91 print("執行測試用例失敗")
92 return HttpResponse("提交的執行測試用例為空,請選擇用例後在提交!")
93 test_cases = models.TestCase.objects.filter().order_by('id')
94 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
95
96
97 # 用例詳情頁
98 @login_required
99 def test_case_detail(request, test_case_id):
100 test_case_id = int(test_case_id)
101 test_case = models.TestCase.objects.get(id=test_case_id)
102 print("test_case: {}".format(test_case))
103 print("test_case.id: {}".format(test_case.id))
104 print("test_case.belong_project: {}".format(test_case.belong_project))
105
106 return render(request, 'test_case_detail.html', {'test_case': test_case})
107
108
109 # 模組頁展示測試用例
110 @login_required
111 def module_test_cases(request, module_id):
112 module = ""
113 if module_id: # 訪問的時候,會從url中提取模組的id,根據模組id查詢到模組資料,在模板中展現
114 module = models.Module.objects.get(id=int(module_id))
115 test_cases = models.TestCase.objects.filter(belong_module=module).order_by('-id')
116 print("test_case in module_test_cases: {}".format(test_cases))
117 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
118
119
120 # 用例集合選單項
121 @login_required
122 def case_suite(request):
123 if request.method == "POST":
124 count_down_time = 0
125 if request.POST['delay_time']:
126 print("輸入的延遲時間是: {}".format(request.POST['delay_time']))
127 try:
128 count_down_time = int(request.POST['delay_time'])
129 except:
130 print("輸入的延遲時間是非數字!")
131 else:
132 print("沒有輸入延遲時間")
133 env = request.POST.getlist('env')
134 print("env: {}".format(env))
135 server_address = get_server_address(env)
136 if not server_address:
137 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
138 case_suite_list = request.POST.getlist('case_suite_list')
139 if case_suite_list:
140 print("所需執行的用例集合列表:", case_suite_list)
141 for suite_id in case_suite_list:
142 test_suite = models.CaseSuite.objects.get(id=int(suite_id))
143 print("所需執行的用例集合: {}".format(test_suite))
144 username = request.user.username
145 test_suite_record = models.CaseSuiteExecuteRecord.objects.create(case_suite=test_suite,
146 run_time_interval=count_down_time,
147 creator=username)
148 suite_task(test_suite_record, test_suite, server_address)
149 else:
150 print("執行測試集合用例失敗")
151 return HttpResponse("執行的測試集合為空,請選擇測試集合後再執行!")
152 case_suites = models.CaseSuite.objects.filter()
153 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
154
155
156 # 用例集合-新增測試用例頁
157 @login_required
158 def add_case_in_suite(request, suite_id):
159 # 查詢指定的用例集合
160 case_suite = models.CaseSuite.objects.get(id=suite_id)
161 # 根據id號查詢所有的用例
162 test_cases = models.TestCase.objects.filter().order_by('id')
163 if request.method == "GET":
164 print("test cases:", test_cases)
165 elif request.method == "POST":
166 test_cases_list = request.POST.getlist('testcases_list')
167 # 如果頁面勾選了用例
168 if test_cases_list:
169 print("勾選用例id:", test_cases_list)
170 # 根據頁面勾選的用例與查詢出的所有用例一一比較
171 for test_case in test_cases_list:
172 test_case = models.TestCase.objects.get(id=int(test_case))
173 # 匹配成功則新增用例
174 models.SuiteCase.objects.create(case_suite=case_suite, test_case=test_case)
175 # 未勾選用例
176 else:
177 print("新增測試用例失敗")
178 return HttpResponse("新增的測試用例為空,請選擇用例後再新增!")
179 return render(request, 'add_case_in_suite.html',
180 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
181
182
183 # 用例集合頁-檢視/刪除用例
184 @login_required
185 def show_and_delete_case_in_suite(request, suite_id):
186 case_suite = models.CaseSuite.objects.get(id=suite_id)
187 test_cases = models.SuiteCase.objects.filter(case_suite=case_suite)
188 if request.method == "POST":
189 test_cases_list = request.POST.getlist('test_cases_list')
190 if test_cases_list:
191 print("勾選用例:", test_cases_list)
192 for test_case in test_cases_list:
193 test_case = models.TestCase.objects.get(id=int(test_case))
194 models.SuiteCase.objects.filter(case_suite=case_suite, test_case=test_case).first().delete()
195 else:
196 print("測試用例刪除失敗")
197 return HttpResponse("所選測試用例為空,請選擇用例後再進行刪除!")
198 case_suite = models.CaseSuite.objects.get(id=suite_id)
199 return render(request, 'show_and_delete_case_in_suite.html',
200 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
201
202
203 # 用例執行結果-選單項
204 @login_required
205 def test_case_execute_record(request):
206 test_case_execute_records = models.TestCaseExecuteResult.objects.filter().order_by('-id')
207 return render(request, 'test_case_execute_records.html', {'test_case_execute_records':
208 get_paginator(request, test_case_execute_records)})
209
210
211 # 用例執行結果-對比差異
212 @login_required
213 def case_result_diff(request, test_record_id):
214 test_record_data = models.TestCaseExecuteResult.objects.get(id=test_record_id)
215 print("用例執行結果記錄: {}".format(test_record_data))
216 present_response = test_record_data.response_data
217 if present_response:
218 present_response = json.dumps(json.loads(present_response), sort_keys=True, indent=4,
219 ensure_ascii=False) # 中文字元不轉ascii編碼
220 print("當前響應結果: {}".format(present_response))
221 last_time_execute_response = test_record_data.last_time_response_data
222 if last_time_execute_response:
223 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True, indent=4,
224 ensure_ascii=False)
225 print("上一次響應結果: {}".format(last_time_execute_response))
226 return render(request, 'case_result_diff.html', locals())
227
228
229 # 用例執行結果-異常資訊展示
230 @login_required
231 def show_exception(request, execute_id):
232 test_record = models.TestCaseExecuteResult.objects.get(id=execute_id)
233 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
234
235
236 # 用例集合執行結果
237 @login_required
238 def case_suite_execute_record(request):
239 case_suite_execute_record = models.CaseSuiteExecuteRecord.objects.filter().order_by('-id')
240 return render(request, 'case_suite_execute_record.html',
241 {'case_suite_execute_records': get_paginator(request, case_suite_execute_record)})
242
243
244 # 用例集合執行結果-包含用例結果展示
245 @login_required
246 def suite_case_execute_record(request, suite_record_id):
247 case_suite_execute_record = models.CaseSuiteExecuteRecord.objects.get(id=suite_record_id)
248 suite_case_execute_records = models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=case_suite_execute_record)
249 return render(request, 'suite_case_execute_record.html',
250 {'suite_case_execute_records': get_paginator(request, suite_case_execute_records)})
251
252
253 # 用例集合執行結果-包含用例結果展示-差異比對
254 @login_required
255 def suite_case_result_diff(request, suite_case_record_id):
256 suite_record_data = models.CaseSuiteTestCaseExecuteRecord.objects.get(id=suite_case_record_id)
257 present_response = suite_record_data.response_data
258 if present_response:
259 present_response = json.dumps(json.loads(present_response),sort_keys=True, indent=4, ensure_ascii=False)
260 print("當前響應: {}".format(present_response))
261 last_time_execute_response = suite_record_data.last_time_response_data
262 if last_time_execute_response:
263 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True,
264 indent=4, ensure_ascii=False)
265 print("上一次響應: {}".format(last_time_execute_response))
266 return render(request, 'case_result_diff.html', locals())
267
268
269 # 用例集合執行結果-包含用例結果展示-異常資訊展示
270 @login_required
271 def suite_case_exception(request, suite_case_record_id):
272 test_record = models.CaseSuiteTestCaseExecuteRecord.objects.get(id=suite_case_record_id)
273 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
274
275
276 # 用例集合執行結果單次統計
277 def suite_case_statistics(request, suite_id):
278 success_num = len(models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=suite_id, execute_result="成功"))
279 fail_num = len(models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=suite_id, execute_result="失敗"))
280 suite_case_records = models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=suite_id).order_by('-id')
281 return render(request, 'suite_case_statistics.html',
282 {'suite_case_records': get_paginator(request, suite_case_records), 'success_num': success_num,
283 'fail_num': fail_num})
284
285
286 # 用例集合執行結果歷史統計
287 def case_suite_statistics(request, suite_id):
288 case_suite = models.CaseSuite.objects.get(id=suite_id)
289 success_num = len(models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite, test_result="成功"))
290 fail_num = len(models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite, test_result="失敗"))
291 case_suite_records = models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite).order_by('-id')
292 return render(request, 'case_suite_statistics.html',
293 {'case_suite_records': get_paginator(request, case_suite_records), 'success_num': success_num,
294 'fail_num': fail_num})
295
296
297 # 預設頁的檢視函式
298 @login_required
299 def index(request):
300 return render(request, 'index.html')
301
302
303 # 登入頁的檢視函式
304 def login(request):
305 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
306 if request.session.get('is_login', None):
307 return redirect('/')
308 # 如果是表單提交行為,則進行登入校驗
309 if request.method == "POST":
310 login_form = UserForm(request.POST)
311 message = "請檢查填寫的內容!"
312 if login_form.is_valid():
313 username = login_form.cleaned_data['username']
314 password = login_form.cleaned_data['password']
315 try:
316 # 使用django提供的身份驗證功能
317 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
318 if user is not None:
319 print("使用者【%s】登入成功" % username)
320 auth.login(request, user)
321 request.session['is_login'] = True
322 # 登入成功,跳轉主頁
323 return redirect('/')
324 else:
325 message = "使用者名稱不存在或者密碼不正確!"
326 except:
327 traceback.print_exc()
328 message = "登入程式出現異常"
329 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
330 else:
331 return render(request, 'login.html', locals())
332 # 不是表單提交,代表只是訪問登入頁
333 else:
334 login_form = UserForm()
335 return render(request, 'login.html', locals())
336
337
338 # 註冊頁的檢視函式
339 def register(request):
340 return render(request, 'register.html')
341
342
343 # 登出的檢視函式:重定向至login檢視函式
344 @login_required
345 def logout(request):
346 auth.logout(request)
347 request.session.flush()
348 return redirect("/login/")

3)定義模板

修改 case_suite_execute_record.html:新增測試結果統計連結

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}測試集合執行結果{% endblock %}
4
5 {% block content %}
6
7 <div class="table-responsive">
8 <table class="table table-striped">
9 <thead>
10 <tr>
11 <th>id</th>
12 <th>測試集合名稱</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_execute_record in case_suite_execute_records %}
24 <tr>
25 <td>{{ case_suite_execute_record.id }}</td>
26 <td><a href="{% url 'suite_case_execute_record' case_suite_execute_record.id %}">{{ case_suite_execute_record.case_suite.suite_desc }}</a></td>
27 <td>{{ case_suite_execute_record.run_time_interval }}</td>
28 {% if case_suite_execute_record.status %}
29 <td>執行完畢</td>
30 {% else %}
31 <td>待執行</td>
32 {% endif %}
33
34 {% ifequal case_suite_execute_record.test_result '成功' %}
35 <td bgcolor='green'>{{ case_suite_execute_record.test_result}}</td>
36 {% else %}
37 <td bgcolor='red'>{{ case_suite_execute_record.test_result}}</td>
38 {% endifequal %}
39 <td><a href="{% url 'suite_case_statistics' case_suite_execute_record.id %}">測試結果統計</a></td>
40 <td>{{ case_suite_execute_record.creator }}</td>
41 <td>{{ case_suite_execute_record.create_time|date:"Y-n-d H:i" }}</td>
42 </tr>
43 {% endfor %}
44
45
46 </tbody>
47 </table>
48 </div>
49
50 {# 實現分頁標籤的程式碼 #}
51 {# 這裡使用 bootstrap 渲染頁面 #}
52 <div id="pages" class="text-center">
53 <nav>
54 <ul class="pagination">
55 <li class="step-links">
56 {% if case_suite_execute_records.has_previous %}
57 <a class='active' href="?page={{ case_suite_execute_records.previous_page_number }}">上一頁</a>
58 {% endif %}
59
60 <span class="current">
61 第 {{ case_suite_execute_records.number }} 頁 / 共 {{ case_suite_execute_records.paginator.num_pages }} 頁</span>
62
63 {% if case_suite_execute_records.has_next %}
64 <a class='active' href="?page={{ case_suite_execute_records.next_page_number }}">下一頁</a>
65 {% endif %}
66 </li>
67 </ul>
68 </nav>
69 </div>
70 {% endblock %}

新增 templates/suite_case_statistics.html:

  1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}用例集合單次執行結果統計{% endblock %}
4 {% block content %}
5
6
7 <body>
8 <p style="margin-left: 10px;">
9 <span style="margin-left: 5px;">用例集合執行結果統計:成功 {{ success_num }} 次,失敗 {{ fail_num }} 次</span>
10 <p>
11 <div id="main" style="width: 600px;height:400px; margin-left: 10px;"></div>
12 <!--<script src="{% static 'js/echarts.simple.min.js' %}"></script>-->
13 <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
14 <script>
15 // 繪製圖表。
16 echarts.init(document.getElementById('main')).setOption({
17 series: {
18 type: 'pie',
19 color: ['green', 'red'],
20 data: [
21 {name: '通過用例數', value: {{ success_num }}},
22 {name: '失敗用例數', value: {{ fail_num }}},
23 ]
24 }
25 });
26 echarts.init(document.getElementById('main')).setOption({
27 title: {
28 text: '結果統計',
29 subtext: '即時資料',
30 left: 'center'
31 },
32 tooltip: {
33 trigger: 'item'
34 },
35 legend: {
36 orient: 'vertical',
37 left: 'left'
38 },
39 series: {
40 name: '結果統計',
41 radius: '55%',
42 type: 'pie',
43 color: ['green', 'red'],
44 data: [
45 {name: '通過用例數', value: {{ success_num }}},
46 {name: '失敗用例數', value: {{ fail_num }}},
47
48 ],
49 label:{ // 餅圖圖形上的文字標籤
50 normal:{
51 show:true,
52 formatter: "{b} : {c} ({d}%)"
53 }
54 }
55 }
56 });
57 </script>
58
59 <table class="table table-striped">
60 <thead>
61 <tr>
62 <th>測試集合名稱</th>
63 <th width="6%">用例id</th>
64 <th>用例名稱</th>
65 <th>所屬專案</th>
66 <th>所屬模組</th>
67 <th>編寫人員</th>
68 <th>建立時間</th>
69 <th>更新時間</th>
70 <th>建立用例使用者名稱</th>
71 </tr>
72 </thead>
73 <tbody>
74
75 {% for suite_case in suite_case_records %}
76 <tr>
77 <td>{{suite_case.case_suite_record.case_suite.suite_desc}}</td>
78 <td>{{ suite_case.id }}</td>
79 <td><a href="{% url 'test_case_detail' suite_case.id%}">{{ suite_case.test_case.case_name }}</a></td>
80 <td>{{ suite_case.test_case.belong_project.name }}</td>
81 <td>{{ suite_case.test_case.belong_module.name }}</td>
82 <td>{{ suite_case.test_case.maintainer }}</td>
83 <td>{{ suite_case.test_case.created_time|date:"Y-n-d H:i" }}</td>
84 <td>{{ suite_case.test_case.updated_time|date:"Y-n-d H:i" }}</td>
85 <td>{{ suite_case.test_case.user }}</td>
86 </tr>
87 {% endfor %}
88 </tbody>
89 </table>
90 </div>
91 </form>
92
93 {# 實現分頁標籤的程式碼 #}
94 {# 這裡使用 bootstrap 渲染頁面 #}
95 <div id="pages" class="text-center">
96 <nav>
97 <ul class="pagination">
98 <li class="step-links">
99 {% if suite_case_records.has_previous %}
100 <a class='active' href="?page={{ suite_case_records.previous_page_number }}">上一頁</a>
101 {% endif %}
102
103 <span class="current">
104 第 {{ suite_case_records.number }} 頁 / 共 {{ suite_case_records.paginator.num_pages }} 頁</span>
105
106 {% if suite_case_records.has_next %}
107 <a class='active' href="?page={{ suite_case_records.next_page_number }}">下一頁</a>
108 {% endif %}
109 </li>
110 </ul>
111 </nav>
112 </div>
113 </div>
114 </div>
115 </body>
116 {% endblock %}

14. 模組測試結果統計

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"),
path('test_case_execute_record/', views.test_case_execute_record, name="test_case_execute_record"),
re_path('case_result_diff/(?P<test_record_id>[0-9]+)', views.case_result_diff, name="case_result_diff"),
re_path('show_exception/(?P<execute_id>[0-9]+)$', views.show_exception, name="show_exception"),
path('case_suite_execute_record/', views.case_suite_execute_record, name="case_suite_execute_record"),
re_path('suite_case_execute_record/(?P<suite_record_id>[0-9]+)', views.suite_case_execute_record, name="suite_case_execute_record"),
re_path('suite_case_result_diff/(?P<suite_case_record_id>[0-9]+)', views.suite_case_result_diff, name="suite_case_result_diff"),
re_path('suite_case_exception/(?P<suite_case_record_id>[0-9]+)', views.suite_case_exception, name="suite_case_exception"),
re_path('suite_case_statistics/(?P<suite_id>[0-9]+)', views.suite_case_statistics, name="suite_case_statistics"),
re_path('case_suite_statistics/(?P<suite_id>[0-9]+)', views.case_suite_statistics, name="case_suite_statistics"),
re_path('module_statistics/(?P<module_id>[0-9]+)', views.module_statistics, name="module_statistics"),
]

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 import json
8 from . import models
9 from .task import case_task, suite_task
10
11
12 # 封裝分頁處理
13 def get_paginator(request, data):
14 paginator = Paginator(data, 10) # 預設每頁展示10條資料
15 # 獲取 url 後面的 page 引數的值, 首頁不顯示 page 引數, 預設值是 1
16 page = request.GET.get('page')
17 try:
18 paginator_pages = paginator.page(page)
19 except PageNotAnInteger:
20 # 如果請求的頁數不是整數, 返回第一頁。
21 paginator_pages = paginator.page(1)
22 except InvalidPage:
23 # 如果請求的頁數不存在, 重定向頁面
24 return HttpResponse('找不到頁面的內容')
25 return paginator_pages
26
27
28 # 專案選單項
29 @login_required
30 def project(request):
31 print("request.user.is_authenticated: ", request.user.is_authenticated)
32 projects = models.Project.objects.filter().order_by('-id')
33 print("projects:", projects)
34 return render(request, 'project.html', {'projects': get_paginator(request, projects)})
35
36
37 # 模組選單項
38 @login_required
39 def module(request):
40 if request.method == "GET": # 請求get時候,id倒序查詢所有的模組資料
41 modules = models.Module.objects.filter().order_by('-id')
42 return render(request, 'module.html', {'modules': get_paginator(request, modules)})
43 else: # 否則就是Post請求,會根據輸入內容,使用模糊的方式查詢所有的專案
44 proj_name = request.POST['proj_name']
45 projects = models.Project.objects.filter(name__contains=proj_name.strip())
46 projs = [proj.id for proj in projects]
47 modules = models.Module.objects.filter(belong_project__in=projs) # 把專案中所有的模組都找出來
48 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
49
50
51 # 獲取測試用例執行的介面地址
52 def get_server_address(env):
53 if env: # 環境處理
54 env_data = models.InterfaceServer.objects.filter(env=env[0])
55 print("env_data: {}".format(env_data))
56 if env_data:
57 ip = env_data[0].ip
58 port = env_data[0].port
59 print("ip: {}, port: {}".format(ip, port))
60 server_address = "http://{}:{}".format(ip, port)
61 print("server_address: {}".format(server_address))
62 return server_address
63 else:
64 return ""
65 else:
66 return ""
67
68
69 # 測試用例選單項
70 @login_required
71 def test_case(request):
72 print("request.session['is_login']: {}".format(request.session['is_login']))
73 test_cases = ""
74 if request.method == "GET":
75 test_cases = models.TestCase.objects.filter().order_by('id')
76 print("testcases: {}".format(test_cases))
77 elif request.method == "POST":
78 print("request.POST: {}".format(request.POST))
79 test_case_id_list = request.POST.getlist('test_cases_list')
80 env = request.POST.getlist('env')
81 print("env: {}".format(env))
82 server_address = get_server_address(env)
83 if not server_address:
84 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
85 if test_case_id_list:
86 test_case_id_list.sort()
87 print("test_case_id_list: {}".format(test_case_id_list))
88 print("獲取到用例,開始用例執行")
89 case_task(test_case_id_list, server_address)
90 else:
91 print("執行測試用例失敗")
92 return HttpResponse("提交的執行測試用例為空,請選擇用例後在提交!")
93 test_cases = models.TestCase.objects.filter().order_by('id')
94 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
95
96
97 # 用例詳情頁
98 @login_required
99 def test_case_detail(request, test_case_id):
100 test_case_id = int(test_case_id)
101 test_case = models.TestCase.objects.get(id=test_case_id)
102 print("test_case: {}".format(test_case))
103 print("test_case.id: {}".format(test_case.id))
104 print("test_case.belong_project: {}".format(test_case.belong_project))
105
106 return render(request, 'test_case_detail.html', {'test_case': test_case})
107
108
109 # 模組頁展示測試用例
110 @login_required
111 def module_test_cases(request, module_id):
112 module = ""
113 if module_id: # 訪問的時候,會從url中提取模組的id,根據模組id查詢到模組資料,在模板中展現
114 module = models.Module.objects.get(id=int(module_id))
115 test_cases = models.TestCase.objects.filter(belong_module=module).order_by('-id')
116 print("test_case in module_test_cases: {}".format(test_cases))
117 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
118
119
120 # 用例集合選單項
121 @login_required
122 def case_suite(request):
123 if request.method == "POST":
124 count_down_time = 0
125 if request.POST['delay_time']:
126 print("輸入的延遲時間是: {}".format(request.POST['delay_time']))
127 try:
128 count_down_time = int(request.POST['delay_time'])
129 except:
130 print("輸入的延遲時間是非數字!")
131 else:
132 print("沒有輸入延遲時間")
133 env = request.POST.getlist('env')
134 print("env: {}".format(env))
135 server_address = get_server_address(env)
136 if not server_address:
137 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
138 case_suite_list = request.POST.getlist('case_suite_list')
139 if case_suite_list:
140 print("所需執行的用例集合列表:", case_suite_list)
141 for suite_id in case_suite_list:
142 test_suite = models.CaseSuite.objects.get(id=int(suite_id))
143 print("所需執行的用例集合: {}".format(test_suite))
144 username = request.user.username
145 test_suite_record = models.CaseSuiteExecuteRecord.objects.create(case_suite=test_suite,
146 run_time_interval=count_down_time,
147 creator=username)
148 suite_task(test_suite_record, test_suite, server_address)
149 else:
150 print("執行測試集合用例失敗")
151 return HttpResponse("執行的測試集合為空,請選擇測試集合後再執行!")
152 case_suites = models.CaseSuite.objects.filter()
153 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
154
155
156 # 用例集合-新增測試用例頁
157 @login_required
158 def add_case_in_suite(request, suite_id):
159 # 查詢指定的用例集合
160 case_suite = models.CaseSuite.objects.get(id=suite_id)
161 # 根據id號查詢所有的用例
162 test_cases = models.TestCase.objects.filter().order_by('id')
163 if request.method == "GET":
164 print("test cases:", test_cases)
165 elif request.method == "POST":
166 test_cases_list = request.POST.getlist('testcases_list')
167 # 如果頁面勾選了用例
168 if test_cases_list:
169 print("勾選用例id:", test_cases_list)
170 # 根據頁面勾選的用例與查詢出的所有用例一一比較
171 for test_case in test_cases_list:
172 test_case = models.TestCase.objects.get(id=int(test_case))
173 # 匹配成功則新增用例
174 models.SuiteCase.objects.create(case_suite=case_suite, test_case=test_case)
175 # 未勾選用例
176 else:
177 print("新增測試用例失敗")
178 return HttpResponse("新增的測試用例為空,請選擇用例後再新增!")
179 return render(request, 'add_case_in_suite.html',
180 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
181
182
183 # 用例集合頁-檢視/刪除用例
184 @login_required
185 def show_and_delete_case_in_suite(request, suite_id):
186 case_suite = models.CaseSuite.objects.get(id=suite_id)
187 test_cases = models.SuiteCase.objects.filter(case_suite=case_suite)
188 if request.method == "POST":
189 test_cases_list = request.POST.getlist('test_cases_list')
190 if test_cases_list:
191 print("勾選用例:", test_cases_list)
192 for test_case in test_cases_list:
193 test_case = models.TestCase.objects.get(id=int(test_case))
194 models.SuiteCase.objects.filter(case_suite=case_suite, test_case=test_case).first().delete()
195 else:
196 print("測試用例刪除失敗")
197 return HttpResponse("所選測試用例為空,請選擇用例後再進行刪除!")
198 case_suite = models.CaseSuite.objects.get(id=suite_id)
199 return render(request, 'show_and_delete_case_in_suite.html',
200 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
201
202
203 # 用例執行結果-選單項
204 @login_required
205 def test_case_execute_record(request):
206 test_case_execute_records = models.TestCaseExecuteResult.objects.filter().order_by('-id')
207 return render(request, 'test_case_execute_records.html', {'test_case_execute_records':
208 get_paginator(request, test_case_execute_records)})
209
210
211 # 用例執行結果-對比差異
212 @login_required
213 def case_result_diff(request, test_record_id):
214 test_record_data = models.TestCaseExecuteResult.objects.get(id=test_record_id)
215 print("用例執行結果記錄: {}".format(test_record_data))
216 present_response = test_record_data.response_data
217 if present_response:
218 present_response = json.dumps(json.loads(present_response), sort_keys=True, indent=4,
219 ensure_ascii=False) # 中文字元不轉ascii編碼
220 print("當前響應結果: {}".format(present_response))
221 last_time_execute_response = test_record_data.last_time_response_data
222 if last_time_execute_response:
223 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True, indent=4,
224 ensure_ascii=False)
225 print("上一次響應結果: {}".format(last_time_execute_response))
226 return render(request, 'case_result_diff.html', locals())
227
228
229 # 用例執行結果-異常資訊展示
230 @login_required
231 def show_exception(request, execute_id):
232 test_record = models.TestCaseExecuteResult.objects.get(id=execute_id)
233 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
234
235
236 # 用例集合執行結果
237 @login_required
238 def case_suite_execute_record(request):
239 case_suite_execute_record = models.CaseSuiteExecuteRecord.objects.filter().order_by('-id')
240 return render(request, 'case_suite_execute_record.html',
241 {'case_suite_execute_records': get_paginator(request, case_suite_execute_record)})
242
243
244 # 用例集合執行結果-包含用例結果展示
245 @login_required
246 def suite_case_execute_record(request, suite_record_id):
247 case_suite_execute_record = models.CaseSuiteExecuteRecord.objects.get(id=suite_record_id)
248 suite_case_execute_records = models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=case_suite_execute_record)
249 return render(request, 'suite_case_execute_record.html',
250 {'suite_case_execute_records': get_paginator(request, suite_case_execute_records)})
251
252
253 # 用例集合執行結果-包含用例結果展示-差異比對
254 @login_required
255 def suite_case_result_diff(request, suite_case_record_id):
256 suite_record_data = models.CaseSuiteTestCaseExecuteRecord.objects.get(id=suite_case_record_id)
257 present_response = suite_record_data.response_data
258 if present_response:
259 present_response = json.dumps(json.loads(present_response),sort_keys=True, indent=4, ensure_ascii=False)
260 print("當前響應: {}".format(present_response))
261 last_time_execute_response = suite_record_data.last_time_response_data
262 if last_time_execute_response:
263 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True,
264 indent=4, ensure_ascii=False)
265 print("上一次響應: {}".format(last_time_execute_response))
266 return render(request, 'case_result_diff.html', locals())
267
268
269 # 用例集合執行結果-包含用例結果展示-異常資訊展示
270 @login_required
271 def suite_case_exception(request, suite_case_record_id):
272 test_record = models.CaseSuiteTestCaseExecuteRecord.objects.get(id=suite_case_record_id)
273 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
274
275
276 # 用例集合執行結果單次統計
277 def suite_case_statistics(request, suite_id):
278 success_num = len(models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=suite_id, execute_result="成功"))
279 fail_num = len(models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=suite_id, execute_result="失敗"))
280 suite_case_records = models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=suite_id).order_by('-id')
281 return render(request, 'suite_case_statistics.html',
282 {'suite_case_records': get_paginator(request, suite_case_records), 'success_num': success_num,
283 'fail_num': fail_num})
284
285
286 # 用例集合執行結果歷史統計
287 def case_suite_statistics(request, suite_id):
288 case_suite = models.CaseSuite.objects.get(id=suite_id)
289 success_num = len(models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite, test_result="成功"))
290 fail_num = len(models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite, test_result="失敗"))
291 case_suite_records = models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite).order_by('-id')
292 return render(request, 'case_suite_statistics.html',
293 {'case_suite_records': get_paginator(request, case_suite_records), 'success_num': success_num,
294 'fail_num': fail_num})
295
296 # 模組測試結果統計
297 @login_required
298 def module_statistics(request, module_id):
299 test_module = models.Module.objects.get(id=int(module_id))
300 test_cases = models.TestCase.objects.filter(belong_module=test_module)
301 test_suit_success_num = len(
302 models.CaseSuiteTestCaseExecuteRecord.objects.filter(test_case__in=test_cases, execute_result="成功"))
303 test_suit_fail_num = len(
304 models.CaseSuiteTestCaseExecuteRecord.objects.filter(test_case__in=test_cases, execute_result="失敗"))
305 test_case_success_num = len(
306 models.TestCaseExecuteResult.objects.filter(belong_test_case__in=test_cases, execute_result="成功"))
307 test_case_fail_num = len(
308 models.TestCaseExecuteResult.objects.filter(belong_test_case__in=test_cases, execute_result="失敗"))
309 success_num = test_suit_success_num + test_case_success_num
310 fail_num = test_suit_fail_num + test_case_fail_num
311 return render(request, 'module_statistics.html',
312 {'test_module': test_module, 'success_num': success_num, 'fail_num': fail_num})
313
314
315 # 預設頁的檢視函式
316 @login_required
317 def index(request):
318 return render(request, 'index.html')
319
320
321 # 登入頁的檢視函式
322 def login(request):
323 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
324 if request.session.get('is_login', None):
325 return redirect('/')
326 # 如果是表單提交行為,則進行登入校驗
327 if request.method == "POST":
328 login_form = UserForm(request.POST)
329 message = "請檢查填寫的內容!"
330 if login_form.is_valid():
331 username = login_form.cleaned_data['username']
332 password = login_form.cleaned_data['password']
333 try:
334 # 使用django提供的身份驗證功能
335 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
336 if user is not None:
337 print("使用者【%s】登入成功" % username)
338 auth.login(request, user)
339 request.session['is_login'] = True
340 # 登入成功,跳轉主頁
341 return redirect('/')
342 else:
343 message = "使用者名稱不存在或者密碼不正確!"
344 except:
345 traceback.print_exc()
346 message = "登入程式出現異常"
347 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
348 else:
349 return render(request, 'login.html', locals())
350 # 不是表單提交,代表只是訪問登入頁
351 else:
352 login_form = UserForm()
353 return render(request, 'login.html', locals())
354
355
356 # 註冊頁的檢視函式
357 def register(request):
358 return render(request, 'register.html')
359
360
361 # 登出的檢視函式:重定向至login檢視函式
362 @login_required
363 def logout(request):
364 auth.logout(request)
365 request.session.flush()
366 return redirect("/login/")

3)定義模板

模組頁面 module.html 新增測試結果統計的連結:

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}模組{% endblock %}
4
5 {% block content %}
6 <form action="{% url 'module'%}" method="POST">
7 {% csrf_token %}
8 <input style="margin-left: 5px;" type="text" name="proj_name" value="{{ proj_name }}" placeholder="輸入專案名稱搜尋模組">
9 <input type="submit" value="搜尋">
10 </form>
11
12 <div class="table-responsive">
13
14 <table class="table table-striped">
15 <thead>
16 <tr>
17 <th>id</th>
18 <th>模組名稱</th>
19 <th>所屬專案</th>
20 <th>測試負責人</th>
21 <th>模組描述</th>
22 <th>建立時間</th>
23 <th>更新時間</th>
24 <th>測試結果統計</th>
25 </tr>
26 </thead>
27 <tbody>
28
29 {% for module in modules %}
30 <tr>
31 <td>{{ module.id }}</td>
32 <td><a href="{% url 'module_test_cases' module.id %}">{{ module.name }}</a></td>
33 <td>{{ module.belong_project.name }}</td>
34 <td>{{ module.test_owner }}</td>
35 <td>{{ module.desc }}</td>
36 <td>{{ module.create_time|date:"Y-n-d H:i" }}</td>
37 <td>{{ module.update_time|date:"Y-n-d H:i" }}</td>
38 <td><a href="{% url 'module_statistics' module.id %}">檢視</a></td>
39 </tr>
40 {% endfor %}
41
42 </tbody>
43 </table>
44 </div>
45
46 {# 實現分頁標籤的程式碼 #}
47 {# 這裡使用 bootstrap 渲染頁面 #}
48 <div id="pages" class="text-center">
49 <nav>
50 <ul class="pagination">
51 <li class="step-links">
52 {% if modules.has_previous %}
53 <a class='active' href="?page={{ modules.previous_page_number }}">上一頁</a>
54 {% endif %}
55
56 <span class="current">
57 第 {{ modules.number }} 頁 / 共 {{ modules.paginator.num_pages }} 頁</span>
58
59 {% if modules.has_next %}
60 <a class='active' href="?page={{ modules.next_page_number }}">下一頁</a>
61 {% endif %}
62 </li>
63 </ul>
64 </nav>
65 </div>
66 {% endblock %}

新增模組測試結果統計模板 module_statistics.html:

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}模組測試結果統計{% endblock %}
4 {% block content %}
5
6 <style>
7 .center{
8 width:500px,
9 margin-left: 10px;
10 background-color: bisque;
11 }
12
13
14 </style>
15 <body>
16 <p style="margin-left: 10px;">
17 <span style="margin-left: 5px;"> 【{{ test_module.name }}】執行統計結果:成功 {{ success_num }} 次,失敗 {{ fail_num }} 次</span>
18 </p>
19 <p style="margin-left: 10px;">
20 <span></span>
21 </p>
22 <div class="center" id="main" style="width: 600px; height:400px; margin-left: 10px;" align="center"></div>
23 <!--<script src="{% static 'js/echarts.simple.min.js' %}"></script>-->
24 <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
25 <script>
26 // 繪製圖表。
27 echarts.init(document.getElementById('main')).setOption({
28 title: {
29 text: '統計結果',
30 subtext: '即時資料',
31 left: 'center'
32 },
33 tooltip: {
34 trigger: 'item'
35 },
36 legend: {
37 orient: 'vertical',
38 left: 'left'
39 },
40 series: {
41 name: '結果統計',
42 radius: '55%',
43 type: 'pie',
44 color: ['green', 'red'],
45 data: [
46 {name: '成功用例數', value: {{ success_num }}},
47 {name: '失敗用例數', value: {{ fail_num }}},
48
49 ],
50 label:{
51 normal:{
52 show:true,
53 formatter: "{b} : {c} ({d}%)"
54 }
55 }
56 }
57 });
58
59 </script>
60 </body>
61
62 {% endblock %}

15. 專案測試結果統計

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"),
path('test_case_execute_record/', views.test_case_execute_record, name="test_case_execute_record"),
re_path('case_result_diff/(?P<test_record_id>[0-9]+)', views.case_result_diff, name="case_result_diff"),
re_path('show_exception/(?P<execute_id>[0-9]+)$', views.show_exception, name="show_exception"),
path('case_suite_execute_record/', views.case_suite_execute_record, name="case_suite_execute_record"),
re_path('suite_case_execute_record/(?P<suite_record_id>[0-9]+)', views.suite_case_execute_record, name="suite_case_execute_record"),
re_path('suite_case_result_diff/(?P<suite_case_record_id>[0-9]+)', views.suite_case_result_diff, name="suite_case_result_diff"),
re_path('suite_case_exception/(?P<suite_case_record_id>[0-9]+)', views.suite_case_exception, name="suite_case_exception"),
re_path('suite_case_statistics/(?P<suite_id>[0-9]+)', views.suite_case_statistics, name="suite_case_statistics"),
re_path('case_suite_statistics/(?P<suite_id>[0-9]+)', views.case_suite_statistics, name="case_suite_statistics"),
re_path('module_statistics/(?P<module_id>[0-9]+)', views.module_statistics, name="module_statistics"),
re_path('project_statistics/(?P<project_id>[0-9]+)', views.project_statistics, name="project_statistics"),
]

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 import json
8 from . import models
9 from .task import case_task, suite_task
10
11
12 # 封裝分頁處理
13 def get_paginator(request, data):
14 paginator = Paginator(data, 10) # 預設每頁展示10條資料
15 # 獲取 url 後面的 page 引數的值, 首頁不顯示 page 引數, 預設值是 1
16 page = request.GET.get('page')
17 try:
18 paginator_pages = paginator.page(page)
19 except PageNotAnInteger:
20 # 如果請求的頁數不是整數, 返回第一頁。
21 paginator_pages = paginator.page(1)
22 except InvalidPage:
23 # 如果請求的頁數不存在, 重定向頁面
24 return HttpResponse('找不到頁面的內容')
25 return paginator_pages
26
27
28 # 專案選單項
29 @login_required
30 def project(request):
31 print("request.user.is_authenticated: ", request.user.is_authenticated)
32 projects = models.Project.objects.filter().order_by('-id')
33 print("projects:", projects)
34 return render(request, 'project.html', {'projects': get_paginator(request, projects)})
35
36
37 # 模組選單項
38 @login_required
39 def module(request):
40 if request.method == "GET": # 請求get時候,id倒序查詢所有的模組資料
41 modules = models.Module.objects.filter().order_by('-id')
42 return render(request, 'module.html', {'modules': get_paginator(request, modules)})
43 else: # 否則就是Post請求,會根據輸入內容,使用模糊的方式查詢所有的專案
44 proj_name = request.POST['proj_name']
45 projects = models.Project.objects.filter(name__contains=proj_name.strip())
46 projs = [proj.id for proj in projects]
47 modules = models.Module.objects.filter(belong_project__in=projs) # 把專案中所有的模組都找出來
48 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
49
50
51 # 獲取測試用例執行的介面地址
52 def get_server_address(env):
53 if env: # 環境處理
54 env_data = models.InterfaceServer.objects.filter(env=env[0])
55 print("env_data: {}".format(env_data))
56 if env_data:
57 ip = env_data[0].ip
58 port = env_data[0].port
59 print("ip: {}, port: {}".format(ip, port))
60 server_address = "http://{}:{}".format(ip, port)
61 print("server_address: {}".format(server_address))
62 return server_address
63 else:
64 return ""
65 else:
66 return ""
67
68
69 # 測試用例選單項
70 @login_required
71 def test_case(request):
72 print("request.session['is_login']: {}".format(request.session['is_login']))
73 test_cases = ""
74 if request.method == "GET":
75 test_cases = models.TestCase.objects.filter().order_by('id')
76 print("testcases: {}".format(test_cases))
77 elif request.method == "POST":
78 print("request.POST: {}".format(request.POST))
79 test_case_id_list = request.POST.getlist('test_cases_list')
80 env = request.POST.getlist('env')
81 print("env: {}".format(env))
82 server_address = get_server_address(env)
83 if not server_address:
84 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
85 if test_case_id_list:
86 test_case_id_list.sort()
87 print("test_case_id_list: {}".format(test_case_id_list))
88 print("獲取到用例,開始用例執行")
89 case_task(test_case_id_list, server_address)
90 else:
91 print("執行測試用例失敗")
92 return HttpResponse("提交的執行測試用例為空,請選擇用例後在提交!")
93 test_cases = models.TestCase.objects.filter().order_by('id')
94 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
95
96
97 # 用例詳情頁
98 @login_required
99 def test_case_detail(request, test_case_id):
100 test_case_id = int(test_case_id)
101 test_case = models.TestCase.objects.get(id=test_case_id)
102 print("test_case: {}".format(test_case))
103 print("test_case.id: {}".format(test_case.id))
104 print("test_case.belong_project: {}".format(test_case.belong_project))
105
106 return render(request, 'test_case_detail.html', {'test_case': test_case})
107
108
109 # 模組頁展示測試用例
110 @login_required
111 def module_test_cases(request, module_id):
112 module = ""
113 if module_id: # 訪問的時候,會從url中提取模組的id,根據模組id查詢到模組資料,在模板中展現
114 module = models.Module.objects.get(id=int(module_id))
115 test_cases = models.TestCase.objects.filter(belong_module=module).order_by('-id')
116 print("test_case in module_test_cases: {}".format(test_cases))
117 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
118
119
120 # 用例集合選單項
121 @login_required
122 def case_suite(request):
123 if request.method == "POST":
124 count_down_time = 0
125 if request.POST['delay_time']:
126 print("輸入的延遲時間是: {}".format(request.POST['delay_time']))
127 try:
128 count_down_time = int(request.POST['delay_time'])
129 except:
130 print("輸入的延遲時間是非數字!")
131 else:
132 print("沒有輸入延遲時間")
133 env = request.POST.getlist('env')
134 print("env: {}".format(env))
135 server_address = get_server_address(env)
136 if not server_address:
137 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
138 case_suite_list = request.POST.getlist('case_suite_list')
139 if case_suite_list:
140 print("所需執行的用例集合列表:", case_suite_list)
141 for suite_id in case_suite_list:
142 test_suite = models.CaseSuite.objects.get(id=int(suite_id))
143 print("所需執行的用例集合: {}".format(test_suite))
144 username = request.user.username
145 test_suite_record = models.CaseSuiteExecuteRecord.objects.create(case_suite=test_suite,
146 run_time_interval=count_down_time,
147 creator=username)
148 suite_task(test_suite_record, test_suite, server_address)
149 else:
150 print("執行測試集合用例失敗")
151 return HttpResponse("執行的測試集合為空,請選擇測試集合後再執行!")
152 case_suites = models.CaseSuite.objects.filter()
153 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
154
155
156 # 用例集合-新增測試用例頁
157 @login_required
158 def add_case_in_suite(request, suite_id):
159 # 查詢指定的用例集合
160 case_suite = models.CaseSuite.objects.get(id=suite_id)
161 # 根據id號查詢所有的用例
162 test_cases = models.TestCase.objects.filter().order_by('id')
163 if request.method == "GET":
164 print("test cases:", test_cases)
165 elif request.method == "POST":
166 test_cases_list = request.POST.getlist('testcases_list')
167 # 如果頁面勾選了用例
168 if test_cases_list:
169 print("勾選用例id:", test_cases_list)
170 # 根據頁面勾選的用例與查詢出的所有用例一一比較
171 for test_case in test_cases_list:
172 test_case = models.TestCase.objects.get(id=int(test_case))
173 # 匹配成功則新增用例
174 models.SuiteCase.objects.create(case_suite=case_suite, test_case=test_case)
175 # 未勾選用例
176 else:
177 print("新增測試用例失敗")
178 return HttpResponse("新增的測試用例為空,請選擇用例後再新增!")
179 return render(request, 'add_case_in_suite.html',
180 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
181
182
183 # 用例集合頁-檢視/刪除用例
184 @login_required
185 def show_and_delete_case_in_suite(request, suite_id):
186 case_suite = models.CaseSuite.objects.get(id=suite_id)
187 test_cases = models.SuiteCase.objects.filter(case_suite=case_suite)
188 if request.method == "POST":
189 test_cases_list = request.POST.getlist('test_cases_list')
190 if test_cases_list:
191 print("勾選用例:", test_cases_list)
192 for test_case in test_cases_list:
193 test_case = models.TestCase.objects.get(id=int(test_case))
194 models.SuiteCase.objects.filter(case_suite=case_suite, test_case=test_case).first().delete()
195 else:
196 print("測試用例刪除失敗")
197 return HttpResponse("所選測試用例為空,請選擇用例後再進行刪除!")
198 case_suite = models.CaseSuite.objects.get(id=suite_id)
199 return render(request, 'show_and_delete_case_in_suite.html',
200 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
201
202
203 # 用例執行結果-選單項
204 @login_required
205 def test_case_execute_record(request):
206 test_case_execute_records = models.TestCaseExecuteResult.objects.filter().order_by('-id')
207 return render(request, 'test_case_execute_records.html', {'test_case_execute_records':
208 get_paginator(request, test_case_execute_records)})
209
210
211 # 用例執行結果-對比差異
212 @login_required
213 def case_result_diff(request, test_record_id):
214 test_record_data = models.TestCaseExecuteResult.objects.get(id=test_record_id)
215 print("用例執行結果記錄: {}".format(test_record_data))
216 present_response = test_record_data.response_data
217 if present_response:
218 present_response = json.dumps(json.loads(present_response), sort_keys=True, indent=4,
219 ensure_ascii=False) # 中文字元不轉ascii編碼
220 print("當前響應結果: {}".format(present_response))
221 last_time_execute_response = test_record_data.last_time_response_data
222 if last_time_execute_response:
223 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True, indent=4,
224 ensure_ascii=False)
225 print("上一次響應結果: {}".format(last_time_execute_response))
226 return render(request, 'case_result_diff.html', locals())
227
228
229 # 用例執行結果-異常資訊展示
230 @login_required
231 def show_exception(request, execute_id):
232 test_record = models.TestCaseExecuteResult.objects.get(id=execute_id)
233 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
234
235
236 # 用例集合執行結果
237 @login_required
238 def case_suite_execute_record(request):
239 case_suite_execute_record = models.CaseSuiteExecuteRecord.objects.filter().order_by('-id')
240 return render(request, 'case_suite_execute_record.html',
241 {'case_suite_execute_records': get_paginator(request, case_suite_execute_record)})
242
243
244 # 用例集合執行結果-包含用例結果展示
245 @login_required
246 def suite_case_execute_record(request, suite_record_id):
247 case_suite_execute_record = models.CaseSuiteExecuteRecord.objects.get(id=suite_record_id)
248 suite_case_execute_records = models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=case_suite_execute_record)
249 return render(request, 'suite_case_execute_record.html',
250 {'suite_case_execute_records': get_paginator(request, suite_case_execute_records)})
251
252
253 # 用例集合執行結果-包含用例結果展示-差異比對
254 @login_required
255 def suite_case_result_diff(request, suite_case_record_id):
256 suite_record_data = models.CaseSuiteTestCaseExecuteRecord.objects.get(id=suite_case_record_id)
257 present_response = suite_record_data.response_data
258 if present_response:
259 present_response = json.dumps(json.loads(present_response),sort_keys=True, indent=4, ensure_ascii=False)
260 print("當前響應: {}".format(present_response))
261 last_time_execute_response = suite_record_data.last_time_response_data
262 if last_time_execute_response:
263 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True,
264 indent=4, ensure_ascii=False)
265 print("上一次響應: {}".format(last_time_execute_response))
266 return render(request, 'case_result_diff.html', locals())
267
268
269 # 用例集合執行結果-包含用例結果展示-異常資訊展示
270 @login_required
271 def suite_case_exception(request, suite_case_record_id):
272 test_record = models.CaseSuiteTestCaseExecuteRecord.objects.get(id=suite_case_record_id)
273 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
274
275
276 # 用例集合執行結果單次統計
277 def suite_case_statistics(request, suite_id):
278 success_num = len(models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=suite_id, execute_result="成功"))
279 fail_num = len(models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=suite_id, execute_result="失敗"))
280 suite_case_records = models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=suite_id).order_by('-id')
281 return render(request, 'suite_case_statistics.html',
282 {'suite_case_records': get_paginator(request, suite_case_records), 'success_num': success_num,
283 'fail_num': fail_num})
284
285
286 # 用例集合執行結果歷史統計
287 def case_suite_statistics(request, suite_id):
288 case_suite = models.CaseSuite.objects.get(id=suite_id)
289 success_num = len(models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite, test_result="成功"))
290 fail_num = len(models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite, test_result="失敗"))
291 case_suite_records = models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite).order_by('-id')
292 return render(request, 'case_suite_statistics.html',
293 {'case_suite_records': get_paginator(request, case_suite_records), 'success_num': success_num,
294 'fail_num': fail_num})
295
296
297 # 模組測試結果統計
298 @login_required
299 def module_statistics(request, module_id):
300 test_module = models.Module.objects.get(id=int(module_id))
301 test_cases = models.TestCase.objects.filter(belong_module=test_module)
302 test_suit_success_num = len(
303 models.CaseSuiteTestCaseExecuteRecord.objects.filter(test_case__in=test_cases, execute_result="成功"))
304 test_suit_fail_num = len(
305 models.CaseSuiteTestCaseExecuteRecord.objects.filter(test_case__in=test_cases, execute_result="失敗"))
306 test_case_success_num = len(
307 models.TestCaseExecuteResult.objects.filter(belong_test_case__in=test_cases, execute_result="成功"))
308 test_case_fail_num = len(
309 models.TestCaseExecuteResult.objects.filter(belong_test_case__in=test_cases, execute_result="失敗"))
310 success_num = test_suit_success_num + test_case_success_num
311 fail_num = test_suit_fail_num + test_case_fail_num
312 return render(request, 'module_statistics.html',
313 {'test_module': test_module, 'success_num': success_num, 'fail_num': fail_num})
314
315
316 # 專案測試結果統計
317 @login_required
318 def project_statistics(request, project_id):
319 test_project = models.Project.objects.get(id=int(project_id))
320 test_cases = models.TestCase.objects.filter(belong_project=test_project)
321 test_suit_success_num = len(
322 models.CaseSuiteTestCaseExecuteRecord.objects.filter(test_case__in=test_cases, execute_result="成功"))
323 test_suit_fail_num = len(
324 models.CaseSuiteTestCaseExecuteRecord.objects.filter(test_case__in=test_cases, execute_result="失敗"))
325 test_case_success_num = len(
326 models.TestCaseExecuteResult.objects.filter(belong_test_case__in=test_cases, execute_result="成功"))
327 test_case_fail_num = len(
328 models.TestCaseExecuteResult.objects.filter(belong_test_case__in=test_cases, execute_result="失敗"))
329 success_num = test_suit_success_num + test_case_success_num
330 fail_num = test_suit_fail_num + test_case_fail_num
331 return render(request, 'project_statistics.html',
332 {'test_project': test_project, 'success_num': success_num, 'fail_num': fail_num})
333
334
335 # 預設頁的檢視函式
336 @login_required
337 def index(request):
338 return render(request, 'index.html')
339
340
341 # 登入頁的檢視函式
342 def login(request):
343 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
344 if request.session.get('is_login', None):
345 return redirect('/')
346 # 如果是表單提交行為,則進行登入校驗
347 if request.method == "POST":
348 login_form = UserForm(request.POST)
349 message = "請檢查填寫的內容!"
350 if login_form.is_valid():
351 username = login_form.cleaned_data['username']
352 password = login_form.cleaned_data['password']
353 try:
354 # 使用django提供的身份驗證功能
355 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
356 if user is not None:
357 print("使用者【%s】登入成功" % username)
358 auth.login(request, user)
359 request.session['is_login'] = True
360 # 登入成功,跳轉主頁
361 return redirect('/')
362 else:
363 message = "使用者名稱不存在或者密碼不正確!"
364 except:
365 traceback.print_exc()
366 message = "登入程式出現異常"
367 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
368 else:
369 return render(request, 'login.html', locals())
370 # 不是表單提交,代表只是訪問登入頁
371 else:
372 login_form = UserForm()
373 return render(request, 'login.html', locals())
374
375
376 # 註冊頁的檢視函式
377 def register(request):
378 return render(request, 'register.html')
379
380
381 # 登出的檢視函式:重定向至login檢視函式
382 @login_required
383 def logout(request):
384 auth.logout(request)
385 request.session.flush()
386 return redirect("/login/")

3)定義模板

修改專案頁模板 project.html:新增測試結果統計的連結

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}主頁{% endblock %}
4
5 {% block content %}
6 <div class="table-responsive">
7 <table class="table table-striped">
8 <thead>
9 <tr>
10 <th>id</th>
11 <th>專案名稱</th>
12 <th>專案負責人</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 project in projects %}
24 <tr>
25 <td>{{ project.id }}</td>
26 <td>{{ project.name }}</td>
27 <td>{{ project.proj_owner }}</td>
28 <td>{{ project.test_owner }}</td>
29 <td>{{ project.dev_owner }}</td>
30 <td>{{ project.desc }}</td>
31 <td>{{ project.create_time|date:"Y-n-d H:i" }}</td>
32 <td>{{ project.update_time|date:"Y-n-d H:i" }}</td>
33 <td><a href="{% url 'project_statistics' project.id %}"> 檢視</a></td>
34 </tr>
35 {% endfor %}
36 </tbody>
37 </table>
38 </div>
39
40 {# 實現分頁標籤的程式碼 #}
41 {# 這裡使用 bootstrap 渲染頁面 #}
42 <div id="pages" class="text-center">
43 <nav>
44 <ul class="pagination">
45 <li class="step-links">
46 {% if projects.has_previous %}
47 <a class='active' href="?page={{ projects.previous_page_number }}">上一頁</a>
48 {% endif %}
49
50 <span class="current">
51 第 {{ projects.number }} 頁 / 共 {{ projects.paginator.num_pages }} 頁</span>
52
53 {% if projects.has_next %}
54 <a class='active' href="?page={{ projects.next_page_number }}">下一頁</a>
55 {% endif %}
56 </li>
57 </ul>
58 </nav>
59 </div>
60 {% endblock %}

新增專案測試結果統計模板:project_statistics.html

 1 {% extends 'base.html' %}
2 {% load static %}
3 {% block title %}模組測試結果統計{% endblock %}
4 {% block content %}
5
6 <style>
7 .center{
8 width:500px,
9 margin-left: 10px;
10 background-color: bisque;
11 }
12
13 </style>
14 <body>
15 <p style="margin-left: 10px;">
16 <span style="margin-left: 5px;"> 【{{ test_project.name }}】執行統計結果:成功 {{ success_num }} 次,失敗 {{ fail_num }} 次</span>
17 </p>
18 <p style="margin-left: 10px;">
19 <span></span>
20 </p>
21 <div class="center" id="main" style="width: 600px; height:400px; margin-left: 10px;" align="center"></div>
22 <!--<script src="{% static 'js/echarts.simple.min.js' %}"></script>-->
23 <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
24 <script>
25 // 繪製圖表。
26 echarts.init(document.getElementById('main')).setOption({
27 title: {
28 text: '統計結果',
29 subtext: '即時資料',
30 left: 'center'
31 },
32 tooltip: {
33 trigger: 'item'
34 },
35 legend: {
36 orient: 'vertical',
37 left: 'left'
38 },
39 series: {
40 name: '結果統計',
41 radius: '55%',
42 type: 'pie',
43 color: ['green', 'red'],
44 data: [
45 {name: '成功用例數', value: {{ success_num }}},
46 {name: '失敗用例數', value: {{ fail_num }}},
47
48 ],
49 label:{
50 normal:{
51 show:true,
52 formatter: "{b} : {c} ({d}%)"
53 }
54 }
55 }
56 });
57
58 </script>
59 </body>
60
61 {% endblock %}

16. Celery 非同步執行用例

本專案使用 Redis 儲存 Celery 的任務執行結果,因此 Redis 需同時啟動著。

16.1 Celery 配置

1)定義 celery app:在應用目錄下新建 celery.py

 1 from __future__ import absolute_import, unicode_literals
2 import os
3 from celery import Celery
4 from django.conf import settings
5
6
7 # 引數為專案名稱
8 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'InterfaceAutoTest.settings') # 設定django環境
9 # 引數為專案名稱
10 app = Celery('InterfaceAutoTest', backend='redis://127.0.0.1:6379/1', broker='redis://127.0.0.1:6379/0')
11
12 app.config_from_object('django.conf:settings') # 使用CELERY_作為字首,在settings中寫配置
13
14 app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) # 發現任務檔案每個app下的task.py
15
16 # 時區
17 app.conf.timezone = 'Asia/Shanghai'
18 # 是否使用UTC
19 app.conf.enable_utc = False

2)引入 celery app:應用目錄下(新建) __init__.py

from __future__ import absolute_import, unicode_literals
from .celery import app as celery_app __all__ = ['celery_app']

16.2 定義 Celery 任務

在應用下的 task.py 中修改如下程式碼:把用例執行程式碼通過裝飾器裝飾成 celery 任務

  1 from __future__ import absolute_import, unicode_literals
2 from celery import shared_task
3 import time
4 import os
5 import traceback
6 import json
7 from . import models
8 from .utils.data_process import data_preprocess, assert_result, data_postprocess
9 from .utils.request_process import request_process
10
11
12 # 測試用例執行
13 @shared_task
14 def case_task(test_case_id_list, server_address):
15 global_key = 'case'+ str(int(time.time() * 100000))
16 os.environ[global_key] = '{}'
17 print()
18 print("全域性變數識別符號【global_key】: {}".format(global_key))
19 print("全域性變數內容【os.environ[global_key]】: {}".format(os.environ[global_key]))
20 for test_case_id in test_case_id_list:
21
22 test_case = models.TestCase.objects.filter(id=int(test_case_id))[0]
23 last_execute_record_data = models.TestCaseExecuteResult.objects.filter(
24 belong_test_case_id=test_case_id).order_by('-id')
25 if last_execute_record_data:
26 last_time_execute_response_data = last_execute_record_data[0].response_data
27 else:
28 last_time_execute_response_data = ''
29 print("上一次響應結果: {}".format(last_execute_record_data))
30 print("上一次響應時間: {}".format(last_time_execute_response_data))
31 execute_record = models.TestCaseExecuteResult.objects.create(belong_test_case=test_case)
32 execute_record.last_time_response_data = last_time_execute_response_data
33 # 獲取當前用例上一次執行結果
34 execute_record.save()
35
36 test_case = models.TestCase.objects.filter(id=int(test_case_id))[0]
37 print("\n######### 開始執行用例【{}】 #########".format(test_case))
38 execute_start_time = time.time() # 記錄時間戳,便於計算總耗時(毫秒)
39 execute_record.execute_start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(execute_start_time))
40
41 request_data = test_case.request_data
42 extract_var = test_case.extract_var
43 assert_key = test_case.assert_key
44 interface_name = test_case.uri
45 belong_project = test_case.belong_project
46 belong_module = test_case.belong_module
47 maintainer = test_case.maintainer
48 request_method = test_case.request_method
49 print("初始請求資料: {}".format(request_data))
50 print("關聯引數: {}".format(extract_var))
51 print("斷言關鍵字: {}".format(assert_key))
52 print("介面名稱: {}".format(interface_name))
53 print("所屬專案: {}".format(belong_project))
54 print("所屬模組: {}".format(belong_module))
55 print("用例維護人: {}".format(maintainer))
56 print("請求方法: {}".format(request_method))
57 url = "{}{}".format(server_address, interface_name)
58 print("介面地址: {}".format(url))
59 code, request_data, error_msg = data_preprocess(global_key, str(request_data))
60 # 請求資料預處理異常,結束用例執行
61 if code != 0:
62 print("資料處理異常,error: {}".format(error_msg))
63 execute_record.execute_result = "失敗"
64 execute_record.status = 1
65 execute_record.exception_info = error_msg
66 execute_end_time = time.time()
67 execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(execute_end_time))
68 execute_record.execute_total_time = int(execute_end_time - execute_start_time) * 1000
69 execute_record.save()
70 return
71 # 記錄請求預處理結果
72 else:
73 execute_record.request_data = request_data
74 # 呼叫介面
75 try:
76 res_data = request_process(url, request_method, json.loads(request_data))
77 print("響應資料: {}".format(json.dumps(res_data.json(), ensure_ascii=False))) # ensure_ascii:相容中文
78 result_flag, exception_info = assert_result(res_data, assert_key)
79 # 結果記錄儲存
80 if result_flag:
81 print("用例【%s】執行成功!" % test_case)
82 execute_record.execute_result = "成功"
83 if extract_var.strip() != "None":
84 var_value = data_postprocess(global_key, json.dumps(res_data.json(), ensure_ascii=False), extract_var)
85 execute_record.extract_var = var_value
86 else:
87 print("用例【%s】執行失敗!" % test_case)
88 execute_record.execute_result = "失敗"
89 execute_record.exception_info = exception_info
90 execute_record.response_data = json.dumps(res_data.json(), ensure_ascii=False)
91 execute_record.status = 1
92 execute_end_time = time.time()
93 execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(execute_end_time))
94 print("執行結果結束時間: {}".format(execute_record.execute_end_time))
95 execute_record.execute_total_time = int((execute_end_time - execute_start_time) * 1000)
96 print("用例執行耗時: {}".format(execute_record.execute_total_time))
97 execute_record.save()
98 except Exception as e:
99 print("介面請求異常,error: {}".format(traceback.format_exc()))
100 execute_record.execute_result = "失敗"
101 execute_record.exception_info = traceback.format_exc()
102 execute_record.status = 1
103 execute_end_time = time.time()
104 execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(execute_end_time))
105 print("執行結果結束時間: {}".format(execute_record.execute_end_time))
106 execute_record.execute_total_time = int(execute_end_time - execute_start_time) * 1000
107 print("用例執行耗時: {} 毫秒".format(execute_record.execute_total_time))
108 execute_record.save()
109
110
111 # 用例集合執行
112 @shared_task
113 def suite_task(case_suite_record, case_suite, server_address):
114 global_key = case_suite.suite_desc + str(int(time.time() * 100000))
115 # global_vars = {"{}".format(global_key): {}}
116 os.environ[global_key] = '{}'
117 print("global_key: {}".format(global_key))
118 print("os.environ[global_key]: {}".format(os.environ[global_key]))
119 case_suite_test_cases = models.SuiteCase.objects.filter(case_suite=case_suite).order_by('id')
120 print("用例集合的測試用例列表: {}".format(case_suite_test_cases))
121 case_suite_record.test_result = "成功"
122 case_suite_record.execute_start_time = time.strftime("%Y-%m-%d %H:%M:%S")
123
124 for case_suite_test_case in case_suite_test_cases:
125 test_case = case_suite_test_case.test_case
126 print("\n######### 開始執行用例【{}】 #########".format(test_case))
127 last_execute_record_data = models.CaseSuiteTestCaseExecuteRecord.objects.filter(
128 test_case_id=test_case.id).order_by('-id')
129 if last_execute_record_data:
130 last_time_execute_response_data = last_execute_record_data[0].response_data
131 else:
132 last_time_execute_response_data = ''
133 print("上一次響應結果: {}".format(last_execute_record_data))
134 print("上一次響應時間: {}".format(last_time_execute_response_data))
135 suite_case_execute_record = models.CaseSuiteTestCaseExecuteRecord.objects.create(case_suite_record=case_suite_record,
136 test_case=test_case)
137 execute_start_time = time.time() # 記錄時間戳,便於計算總耗時(毫秒)
138 suite_case_execute_record.execute_start_time = time.strftime("%Y-%m-%d %H:%M:%S",
139 time.localtime(execute_start_time))
140 print("用例集合開始執行時間: {}".format(suite_case_execute_record.execute_start_time))
141 suite_case_execute_record.last_time_response_data = last_time_execute_response_data
142 suite_case_execute_record.save()
143 request_data = test_case.request_data
144 extract_var = test_case.extract_var
145 assert_key = test_case.assert_key
146 interface_name = test_case.uri
147 belong_project = test_case.belong_project
148 belong_module = test_case.belong_module
149 maintainer = test_case.maintainer
150 request_method = test_case.request_method
151 print("初始請求資料: {}".format(request_data))
152 print("關聯引數: {}".format(extract_var))
153 print("斷言關鍵字: {}".format(assert_key))
154 print("介面名稱: {}".format(interface_name))
155 print("所屬專案: {}".format(belong_project))
156 print("所屬模組: {}".format(belong_module))
157 print("用例維護人: {}".format(maintainer))
158 print("請求方法: {}".format(request_method))
159 url = "{}{}".format(server_address, interface_name)
160 print("介面地址: {}".format(url))
161 # 請求資料預處理
162 code, request_data, error_msg = data_preprocess(global_key, str(request_data))
163 # 請求資料預處理異常,結束用例執行
164 if code != 0:
165 print("資料處理異常,error: {}".format(error_msg))
166 suite_case_execute_record.execute_result = "失敗"
167 suite_case_execute_record.status = 1
168 suite_case_execute_record.exception_info = error_msg
169 execute_end_time = time.time()
170 suite_case_execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S",
171 time.localtime(execute_end_time))
172 suite_case_execute_record.execute_total_time = int(execute_end_time - execute_start_time) * 1000
173 suite_case_execute_record.save()
174 case_suite_record.test_result = "失敗"
175 # 記錄請求預處理的結果
176 suite_case_execute_record.request_data = request_data
177 try:
178 # 呼叫介面
179 res_data = request_process(url, request_method, json.loads(request_data))
180 print("響應資料: {}".format(json.dumps(res_data.json(), ensure_ascii=False)))
181
182 result_flag, exception_info = assert_result(res_data, assert_key)
183 # 結果記錄儲存
184 if result_flag:
185 print("用例【%s】執行成功!" % test_case)
186 suite_case_execute_record.execute_result = "成功"
187 if extract_var.strip() != "None":
188 var_value = data_postprocess(global_key, json.dumps(res_data.json(), ensure_ascii=False),
189 extract_var)
190 suite_case_execute_record.extract_var = var_value
191 else:
192 print("用例【%s】執行失敗!" % test_case)
193 suite_case_execute_record.execute_result = "失敗"
194 suite_case_execute_record.exception_info = exception_info
195 case_suite_record.test_result = "失敗"
196 suite_case_execute_record.response_data = json.dumps(res_data.json(), ensure_ascii=False)
197 suite_case_execute_record.status = 1
198 execute_end_time = time.time()
199 suite_case_execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S",
200 time.localtime(execute_end_time))
201 suite_case_execute_record.execute_total_time = int((execute_end_time - execute_start_time) * 1000)
202 print("用例執行耗時: {} 毫秒".format(
203 suite_case_execute_record.execute_total_time))
204 suite_case_execute_record.save()
205 except Exception as e:
206 print("介面請求異常,error: {}".format(e))
207 suite_case_execute_record.execute_result = "失敗"
208 suite_case_execute_record.exception_info = traceback.format_exc()
209 suite_case_execute_record.status = 1
210 execute_end_time = time.time()
211 suite_case_execute_record.execute_end_time = time.strftime("%Y-%m-%d %H:%M:%S",
212 time.localtime(execute_end_time))
213 suite_case_execute_record.execute_total_time = int(execute_end_time - execute_start_time) * 1000
214 print("用例集合執行總耗時: {} 毫秒".format(suite_case_execute_record.execute_total_time))
215 suite_case_execute_record.save()
216 case_suite_record.test_result = "失敗"
217
218 case_suite_record.status = 1 # 執行完畢
219 case_suite_record.save()

16.3 Celery 啟動

在專案目錄下執行如下命令(-A 後接應用名稱):

celery worker -A  interfacetestplatform -l info -P eventlet

啟動成功日誌如下:

需要注意,在 celery 的 task.py 中呼叫的函式如果有修改,則需要重啟 celery。

16.4 Celery 任務執行

修改用例執行和用例集合執行的檢視函式,改為使用 celery 執行的方式。

  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 import json
8 from . import models
9 from .task import case_task, suite_task
10
11
12 # 封裝分頁處理
13 def get_paginator(request, data):
14 paginator = Paginator(data, 10) # 預設每頁展示10條資料
15 # 獲取 url 後面的 page 引數的值, 首頁不顯示 page 引數, 預設值是 1
16 page = request.GET.get('page')
17 try:
18 paginator_pages = paginator.page(page)
19 except PageNotAnInteger:
20 # 如果請求的頁數不是整數, 返回第一頁。
21 paginator_pages = paginator.page(1)
22 except InvalidPage:
23 # 如果請求的頁數不存在, 重定向頁面
24 return HttpResponse('找不到頁面的內容')
25 return paginator_pages
26
27
28 # 專案選單項
29 @login_required
30 def project(request):
31 print("request.user.is_authenticated: ", request.user.is_authenticated)
32 projects = models.Project.objects.filter().order_by('-id')
33 print("projects:", projects)
34 return render(request, 'project.html', {'projects': get_paginator(request, projects)})
35
36
37 # 模組選單項
38 @login_required
39 def module(request):
40 if request.method == "GET": # 請求get時候,id倒序查詢所有的模組資料
41 modules = models.Module.objects.filter().order_by('-id')
42 return render(request, 'module.html', {'modules': get_paginator(request, modules)})
43 else: # 否則就是Post請求,會根據輸入內容,使用模糊的方式查詢所有的專案
44 proj_name = request.POST['proj_name']
45 projects = models.Project.objects.filter(name__contains=proj_name.strip())
46 projs = [proj.id for proj in projects]
47 modules = models.Module.objects.filter(belong_project__in=projs) # 把專案中所有的模組都找出來
48 return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})
49
50
51 # 獲取測試用例執行的介面地址
52 def get_server_address(env):
53 if env: # 環境處理
54 env_data = models.InterfaceServer.objects.filter(env=env[0])
55 print("env_data: {}".format(env_data))
56 if env_data:
57 ip = env_data[0].ip
58 port = env_data[0].port
59 print("ip: {}, port: {}".format(ip, port))
60 server_address = "http://{}:{}".format(ip, port)
61 print("server_address: {}".format(server_address))
62 return server_address
63 else:
64 return ""
65 else:
66 return ""
67
68
69 # 測試用例選單項
70 @login_required
71 def test_case(request):
72 print("request.session['is_login']: {}".format(request.session['is_login']))
73 test_cases = ""
74 if request.method == "GET":
75 test_cases = models.TestCase.objects.filter().order_by('id')
76 print("testcases: {}".format(test_cases))
77 elif request.method == "POST":
78 print("request.POST: {}".format(request.POST))
79 test_case_id_list = request.POST.getlist('test_cases_list')
80 env = request.POST.getlist('env')
81 print("env: {}".format(env))
82 server_address = get_server_address(env)
83 if not server_address:
84 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
85 if test_case_id_list:
86 test_case_id_list.sort()
87 print("test_case_id_list: {}".format(test_case_id_list))
88 print("獲取到用例,開始用例執行")
89 # 普通執行
90 # case_task(test_case_id_list, server_address)
91 # celery 執行
92 case_task.apply_async((test_case_id_list, server_address))
93 else:
94 print("執行測試用例失敗")
95 return HttpResponse("提交的執行測試用例為空,請選擇用例後在提交!")
96 test_cases = models.TestCase.objects.filter().order_by('id')
97 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
98
99
100 # 用例詳情頁
101 @login_required
102 def test_case_detail(request, test_case_id):
103 test_case_id = int(test_case_id)
104 test_case = models.TestCase.objects.get(id=test_case_id)
105 print("test_case: {}".format(test_case))
106 print("test_case.id: {}".format(test_case.id))
107 print("test_case.belong_project: {}".format(test_case.belong_project))
108
109 return render(request, 'test_case_detail.html', {'test_case': test_case})
110
111
112 # 模組頁展示測試用例
113 @login_required
114 def module_test_cases(request, module_id):
115 module = ""
116 if module_id: # 訪問的時候,會從url中提取模組的id,根據模組id查詢到模組資料,在模板中展現
117 module = models.Module.objects.get(id=int(module_id))
118 test_cases = models.TestCase.objects.filter(belong_module=module).order_by('-id')
119 print("test_case in module_test_cases: {}".format(test_cases))
120 return render(request, 'test_case.html', {'test_cases': get_paginator(request, test_cases)})
121
122
123 # 用例集合選單項
124 @login_required
125 def case_suite(request):
126 if request.method == "POST":
127 count_down_time = 0
128 if request.POST['delay_time']:
129 print("輸入的延遲時間是: {}".format(request.POST['delay_time']))
130 try:
131 count_down_time = int(request.POST['delay_time'])
132 except:
133 print("輸入的延遲時間是非數字!")
134 else:
135 print("沒有輸入延遲時間")
136 env = request.POST.getlist('env')
137 print("env: {}".format(env))
138 server_address = get_server_address(env)
139 if not server_address:
140 return HttpResponse("提交的執行環境為空,請選擇環境後再提交!")
141 case_suite_list = request.POST.getlist('case_suite_list')
142 if case_suite_list:
143 print("所需執行的用例集合列表:", case_suite_list)
144 for suite_id in case_suite_list:
145 test_suite = models.CaseSuite.objects.get(id=int(suite_id))
146 print("所需執行的用例集合: {}".format(test_suite))
147 username = request.user.username
148 test_suite_record = models.CaseSuiteExecuteRecord.objects.create(case_suite=test_suite,
149 run_time_interval=count_down_time,
150 creator=username)
151 # 普通執行
152 # suite_task(test_suite_record, test_suite, server_address)
153 # celery 執行:countdown表示任務延遲的時間,該引數值可以從前端表單中傳遞過來
154 suite_task.apply_async((test_suite_record, test_suite, server_address), countdown=count_down_time)
155 else:
156 print("執行測試集合用例失敗")
157 return HttpResponse("執行的測試集合為空,請選擇測試集合後再執行!")
158 case_suites = models.CaseSuite.objects.filter()
159 return render(request, 'case_suite.html', {'case_suites': get_paginator(request, case_suites)})
160
161
162 # 用例集合-新增測試用例頁
163 @login_required
164 def add_case_in_suite(request, suite_id):
165 # 查詢指定的用例集合
166 case_suite = models.CaseSuite.objects.get(id=suite_id)
167 # 根據id號查詢所有的用例
168 test_cases = models.TestCase.objects.filter().order_by('id')
169 if request.method == "GET":
170 print("test cases:", test_cases)
171 elif request.method == "POST":
172 test_cases_list = request.POST.getlist('testcases_list')
173 # 如果頁面勾選了用例
174 if test_cases_list:
175 print("勾選用例id:", test_cases_list)
176 # 根據頁面勾選的用例與查詢出的所有用例一一比較
177 for test_case in test_cases_list:
178 test_case = models.TestCase.objects.get(id=int(test_case))
179 # 匹配成功則新增用例
180 models.SuiteCase.objects.create(case_suite=case_suite, test_case=test_case)
181 # 未勾選用例
182 else:
183 print("新增測試用例失敗")
184 return HttpResponse("新增的測試用例為空,請選擇用例後再新增!")
185 return render(request, 'add_case_in_suite.html',
186 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
187
188
189 # 用例集合頁-檢視/刪除用例
190 @login_required
191 def show_and_delete_case_in_suite(request, suite_id):
192 case_suite = models.CaseSuite.objects.get(id=suite_id)
193 test_cases = models.SuiteCase.objects.filter(case_suite=case_suite)
194 if request.method == "POST":
195 test_cases_list = request.POST.getlist('test_cases_list')
196 if test_cases_list:
197 print("勾選用例:", test_cases_list)
198 for test_case in test_cases_list:
199 test_case = models.TestCase.objects.get(id=int(test_case))
200 models.SuiteCase.objects.filter(case_suite=case_suite, test_case=test_case).first().delete()
201 else:
202 print("測試用例刪除失敗")
203 return HttpResponse("所選測試用例為空,請選擇用例後再進行刪除!")
204 case_suite = models.CaseSuite.objects.get(id=suite_id)
205 return render(request, 'show_and_delete_case_in_suite.html',
206 {'test_cases': get_paginator(request, test_cases), 'case_suite': case_suite})
207
208
209 # 用例執行結果-選單項
210 @login_required
211 def test_case_execute_record(request):
212 test_case_execute_records = models.TestCaseExecuteResult.objects.filter().order_by('-id')
213 return render(request, 'test_case_execute_records.html', {'test_case_execute_records':
214 get_paginator(request, test_case_execute_records)})
215
216
217 # 用例執行結果-對比差異
218 @login_required
219 def case_result_diff(request, test_record_id):
220 test_record_data = models.TestCaseExecuteResult.objects.get(id=test_record_id)
221 print("用例執行結果記錄: {}".format(test_record_data))
222 present_response = test_record_data.response_data
223 if present_response:
224 present_response = json.dumps(json.loads(present_response), sort_keys=True, indent=4,
225 ensure_ascii=False) # 中文字元不轉ascii編碼
226 print("當前響應結果: {}".format(present_response))
227 last_time_execute_response = test_record_data.last_time_response_data
228 if last_time_execute_response:
229 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True, indent=4,
230 ensure_ascii=False)
231 print("上一次響應結果: {}".format(last_time_execute_response))
232 return render(request, 'case_result_diff.html', locals())
233
234
235 # 用例執行結果-異常資訊展示
236 @login_required
237 def show_exception(request, execute_id):
238 test_record = models.TestCaseExecuteResult.objects.get(id=execute_id)
239 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
240
241
242 # 用例集合執行結果
243 @login_required
244 def case_suite_execute_record(request):
245 case_suite_execute_record = models.CaseSuiteExecuteRecord.objects.filter().order_by('-id')
246 return render(request, 'case_suite_execute_record.html',
247 {'case_suite_execute_records': get_paginator(request, case_suite_execute_record)})
248
249
250 # 用例集合執行結果-包含用例結果展示
251 @login_required
252 def suite_case_execute_record(request, suite_record_id):
253 case_suite_execute_record = models.CaseSuiteExecuteRecord.objects.get(id=suite_record_id)
254 suite_case_execute_records = models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=case_suite_execute_record)
255 return render(request, 'suite_case_execute_record.html',
256 {'suite_case_execute_records': get_paginator(request, suite_case_execute_records)})
257
258
259 # 用例集合執行結果-包含用例結果展示-差異比對
260 @login_required
261 def suite_case_result_diff(request, suite_case_record_id):
262 suite_record_data = models.CaseSuiteTestCaseExecuteRecord.objects.get(id=suite_case_record_id)
263 present_response = suite_record_data.response_data
264 if present_response:
265 present_response = json.dumps(json.loads(present_response),sort_keys=True, indent=4, ensure_ascii=False)
266 print("當前響應: {}".format(present_response))
267 last_time_execute_response = suite_record_data.last_time_response_data
268 if last_time_execute_response:
269 last_time_execute_response = json.dumps(json.loads(last_time_execute_response), sort_keys=True,
270 indent=4, ensure_ascii=False)
271 print("上一次響應: {}".format(last_time_execute_response))
272 return render(request, 'case_result_diff.html', locals())
273
274
275 # 用例集合執行結果-包含用例結果展示-異常資訊展示
276 @login_required
277 def suite_case_exception(request, suite_case_record_id):
278 test_record = models.CaseSuiteTestCaseExecuteRecord.objects.get(id=suite_case_record_id)
279 return render(request, 'show_exception.html', {'exception_info': test_record.exception_info})
280
281
282 # 用例集合執行結果單次統計
283 def suite_case_statistics(request, suite_id):
284 success_num = len(models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=suite_id, execute_result="成功"))
285 fail_num = len(models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=suite_id, execute_result="失敗"))
286 suite_case_records = models.CaseSuiteTestCaseExecuteRecord.objects.filter(case_suite_record=suite_id).order_by('-id')
287 return render(request, 'suite_case_statistics.html',
288 {'suite_case_records': get_paginator(request, suite_case_records), 'success_num': success_num,
289 'fail_num': fail_num})
290
291
292 # 用例集合執行結果歷史統計
293 def case_suite_statistics(request, suite_id):
294 case_suite = models.CaseSuite.objects.get(id=suite_id)
295 success_num = len(models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite, test_result="成功"))
296 fail_num = len(models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite, test_result="失敗"))
297 case_suite_records = models.CaseSuiteExecuteRecord.objects.filter(case_suite=case_suite).order_by('-id')
298 return render(request, 'case_suite_statistics.html',
299 {'case_suite_records': get_paginator(request, case_suite_records), 'success_num': success_num,
300 'fail_num': fail_num})
301
302
303 # 模組測試結果統計
304 @login_required
305 def module_statistics(request, module_id):
306 test_module = models.Module.objects.get(id=int(module_id))
307 test_cases = models.TestCase.objects.filter(belong_module=test_module)
308 test_suit_success_num = len(
309 models.CaseSuiteTestCaseExecuteRecord.objects.filter(test_case__in=test_cases, execute_result="成功"))
310 test_suit_fail_num = len(
311 models.CaseSuiteTestCaseExecuteRecord.objects.filter(test_case__in=test_cases, execute_result="失敗"))
312 test_case_success_num = len(
313 models.TestCaseExecuteResult.objects.filter(belong_test_case__in=test_cases, execute_result="成功"))
314 test_case_fail_num = len(
315 models.TestCaseExecuteResult.objects.filter(belong_test_case__in=test_cases, execute_result="失敗"))
316 success_num = test_suit_success_num + test_case_success_num
317 fail_num = test_suit_fail_num + test_case_fail_num
318 return render(request, 'module_statistics.html',
319 {'test_module': test_module, 'success_num': success_num, 'fail_num': fail_num})
320
321
322 # 專案測試結果統計
323 @login_required
324 def project_statistics(request, project_id):
325 test_project = models.Project.objects.get(id=int(project_id))
326 test_cases = models.TestCase.objects.filter(belong_project=test_project)
327 test_suit_success_num = len(
328 models.CaseSuiteTestCaseExecuteRecord.objects.filter(test_case__in=test_cases, execute_result="成功"))
329 test_suit_fail_num = len(
330 models.CaseSuiteTestCaseExecuteRecord.objects.filter(test_case__in=test_cases, execute_result="失敗"))
331 test_case_success_num = len(
332 models.TestCaseExecuteResult.objects.filter(belong_test_case__in=test_cases, execute_result="成功"))
333 test_case_fail_num = len(
334 models.TestCaseExecuteResult.objects.filter(belong_test_case__in=test_cases, execute_result="失敗"))
335 success_num = test_suit_success_num + test_case_success_num
336 fail_num = test_suit_fail_num + test_case_fail_num
337 return render(request, 'project_statistics.html',
338 {'test_project': test_project, 'success_num': success_num, 'fail_num': fail_num})
339
340
341 # 預設頁的檢視函式
342 @login_required
343 def index(request):
344 return render(request, 'index.html')
345
346
347 # 登入頁的檢視函式
348 def login(request):
349 print("request.session.items(): {}".format(request.session.items())) # 列印session資訊
350 if request.session.get('is_login', None):
351 return redirect('/')
352 # 如果是表單提交行為,則進行登入校驗
353 if request.method == "POST":
354 login_form = UserForm(request.POST)
355 message = "請檢查填寫的內容!"
356 if login_form.is_valid():
357 username = login_form.cleaned_data['username']
358 password = login_form.cleaned_data['password']
359 try:
360 # 使用django提供的身份驗證功能
361 user = auth.authenticate(username=username, password=password) # 從auth_user表中匹配資訊,匹配到則返回使用者物件
362 if user is not None:
363 print("使用者【%s】登入成功" % username)
364 auth.login(request, user)
365 request.session['is_login'] = True
366 # 登入成功,跳轉主頁
367 return redirect('/')
368 else:
369 message = "使用者名稱不存在或者密碼不正確!"
370 except:
371 traceback.print_exc()
372 message = "登入程式出現異常"
373 # 使用者名稱或密碼為空,返回登入頁和錯誤提示資訊
374 else:
375 return render(request, 'login.html', locals())
376 # 不是表單提交,代表只是訪問登入頁
377 else:
378 login_form = UserForm()
379 return render(request, 'login.html', locals())
380
381
382 # 註冊頁的檢視函式
383 def register(request):
384 return render(request, 'register.html')
385
386
387 # 登出的檢視函式:重定向至login檢視函式
388 @login_required
389 def logout(request):
390 auth.logout(request)
391 request.session.flush()
392 return redirect("/login/")

頁面勾選用例/集合提交執行,在 celery 命令列介面,檢視用例執行結果,如下所示: