Django 介面自動化測試平臺

Juno3550發表於2021-07-19

本專案工程 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: admin@123.com  # 符合郵箱格式即可
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 命令列介面,檢視用例執行結果,如下所示:

 

相關文章