Django接口自動化平臺實現(五)

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 true35                 }36             }37             alert("請選擇要執行的測試用例!")38             return false39         }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 models2 from smart_selects.db_fields import GroupedForeignKey  # pip install django-smart-selects:后臺級聯選擇3 from django.contrib.auth.models import User4 5 6 class Project(models.Model):7     id = models.AutoField(primary_key=True)8     name = models.CharField('項目名稱', max_length=50, unique=True, null=False)9     proj_owner = models.CharField('項目負責人', max_length=20, null=False)10     test_owner = models.CharField('測試負責人', max_length=20, null=False)11     dev_owner = models.CharField('開發負責人', max_length=20, null=False)12     desc = models.CharField('項目描述', max_length=100, null=True)13     create_time = models.DateTimeField('項目創建時間', auto_now_add=True)14     update_time = models.DateTimeField('項目更新時間', auto_now=True, null=True)15 16     def __str__(self):17         return self.name18 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.name35 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)  # 如 register44     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_name59 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.env97 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 admin2 from .import models3 4 5 class ProjectAdmin(admin.ModelAdmin):6     list_display = ("id", "name", "proj_owner", "test_owner", "dev_owner", "desc", "create_time", "update_time")7 8 admin.site.register(models.Project, ProjectAdmin)9 
10 
11 class ModuleAdmin(admin.ModelAdmin):
12     list_display = ("id", "name", "belong_project", "test_owner", "desc", "create_time", "update_time")
13 
14 admin.site.register(models.Module, ModuleAdmin)
15 
16 
17 class TestCaseAdmin(admin.ModelAdmin):
18     list_display = (
19         "id", "case_name", "belong_project", "belong_module", "request_data", "uri", "assert_key", "maintainer",
20         "extract_var", "request_method", "status", "created_time", "updated_time", "user")
21 
22 admin.site.register(models.TestCase, TestCaseAdmin)
23 
24 
25 class CaseSuiteAdmin(admin.ModelAdmin):
26     list_display = ("id", "suite_desc", "creator", "create_time")
27 
28 admin.site.register(models.CaseSuite, CaseSuiteAdmin)
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 re2 import hashlib3 import os4 import json5 import traceback6 import redis7 from InterfaceAutoTest.settings import redis_port8 9 10 # 連接redis11 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 = None19     try:20         redis_value = redis_obj.get(unique_number)  # {"unique_number": 666}21         if redis_value:22             data = redis_value23             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 = None32     finally:33         return data34 35 36 def md5(s):37     m5 = hashlib.md5()38     m5.update(s.encode("utf-8"))39     md5_value = m5.hexdigest()40     return md5_value41 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_value59             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     return95 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 requests2 import json3 # from Util.Log import logger4 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 time2 import os3 import traceback4 import json5 from . import models6 from .utils.data_process import data_preprocess, assert_result, data_postprocess7 from .utils.request_process import request_process8 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, HttpResponse2 from django.contrib import auth  # Django用戶認證(Auth)組件一般用在用戶的登錄注冊上,用于判斷當前的用戶是否合法3 from django.contrib.auth.decorators import login_required4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage5 from .form import UserForm6 import traceback7 from .models import Project, Module, TestCase, CaseSuite, SuiteCase, InterfaceServer8 from .task import case_task9 10 11 # 封裝分頁處理12 def get_paginator(request, data):13     paginator = Paginator(data, 10)  # 默認每頁展示10條數據14     # 獲取 url 后面的 page 參數的值, 首頁不顯示 page 參數, 默認值是 115     page = request.GET.get('page')16     try:17         paginator_pages = paginator.page(page)18     except PageNotAnInteger:19         # 如果請求的頁數不是整數, 返回第一頁。20         paginator_pages = paginator.page(1)21     except InvalidPage:22         # 如果請求的頁數不存在, 重定向頁面23         return HttpResponse('找不到頁面的內容')24     return paginator_pages25 26 27 # 項目菜單項28 @login_required29 def project(request):30     print("request.user.is_authenticated: ", request.user.is_authenticated)31     projects = Project.objects.filter().order_by('-id')32     print("projects:", projects)33     return render(request, 'project.html', {'projects': get_paginator(request, projects)})34 35 36 # 模塊菜單項37 @login_required38 def module(request):39     if request.method == "GET":  # 請求get時候,id倒序查詢所有的模塊數據40         modules = Module.objects.filter().order_by('-id')41         return render(request, 'module.html', {'modules': get_paginator(request, modules)})42     else:  # 否則就是Post請求,會根據輸入內容,使用模糊的方式查找所有的項目43         proj_name = request.POST['proj_name']44         projects = Project.objects.filter(name__contains=proj_name.strip())45         projs = [proj.id for proj in projects]46         modules = Module.objects.filter(belong_project__in=projs)  # 把項目中所有的模塊都找出來47         return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})48 49 50 # 獲取測試用例執行的接口地址51 def get_server_address(env):52     if env:  # 環境處理53         env_data = InterfaceServer.objects.filter(env=env[0])54         print("env_data: {}".format(env_data))55         if env_data:56             ip = env_data[0].ip57             port = env_data[0].port58             print("ip: {}, port: {}".format(ip, port))59             server_address = "http://{}:{}".format(ip, port)60             print("server_address: {}".format(server_address))61             return server_address62         else:63             return ""64     else:65         return ""66 67 68 # 測試用例菜單項69 @login_required70 def test_case(request):71     print("request.session['is_login']: {}".format(request.session['is_login']))72     test_cases = ""73     if request.method == "GET":74         test_cases = TestCase.objects.filter().order_by('id')75         print("testcases: {}".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_required98 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 models2 from smart_selects.db_fields import GroupedForeignKey  # pip install django-smart-selects:后臺級聯選擇3 from django.contrib.auth.models import User4 5 6 class Project(models.Model):7     id = models.AutoField(primary_key=True)8     name = models.CharField('項目名稱', max_length=50, unique=True, null=False)9     proj_owner = models.CharField('項目負責人', max_length=20, null=False)10     test_owner = models.CharField('測試負責人', max_length=20, null=False)11     dev_owner = models.CharField('開發負責人', max_length=20, null=False)12     desc = models.CharField('項目描述', max_length=100, null=True)13     create_time = models.DateTimeField('項目創建時間', auto_now_add=True)14     update_time = models.DateTimeField('項目更新時間', auto_now=True, null=True)15 16     def __str__(self):17         return self.name18 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.name35 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)  # 如 register44     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_name59 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.env97 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 time2 import os3 import traceback4 import json5 from . import models6 from .utils.data_process import data_preprocess, assert_result, data_postprocess7 from .utils.request_process import request_process8 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_data23         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_data29         # 獲取當前用例上一次執行結果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_data38         extract_var = test_case.extract_var39         assert_key = test_case.assert_key40         interface_name = test_case.uri41         belong_project = test_case.belong_project42         belong_module = test_case.belong_module43         maintainer = test_case.maintainer44         request_method = test_case.request_method45         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 = 161             execute_record.exception_info = error_msg62             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) * 100065             execute_record.save()66             return67         # 記錄請求預處理結果68         else:69             execute_record.request_data = request_data70         # 調用接口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_value82             else:83                 print("用例【%s】執行失敗!" % test_case)84                 execute_record.execute_result = "失敗"85                 execute_record.exception_info = exception_info86             execute_record.response_data = json.dumps(res_data.json(), ensure_ascii=False)87             execute_record.status = 188             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 = 199             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 viewsurlpatterns = [path('', views.index),path('login/', views.login),path('logout/', views.logout),path('project/', views.project, name='project'),path('module/', views.module, name='module'),path('test_case/', views.test_case, name="test_case"),re_path('test_case_detail/(?P<test_case_id>[0-9]+)', views.test_case_detail, name="test_case_detail"),re_path('module_test_cases/(?P<module_id>[0-9]+)/$', views.module_test_cases, name="module_test_cases"),path('case_suite/', views.case_suite, name="case_suite"),re_path('add_case_in_suite/(?P<suite_id>[0-9]+)', views.add_case_in_suite, name="add_case_in_suite"),re_path('show_and_delete_case_in_suite/(?P<suite_id>[0-9]+)', views.show_and_delete_case_in_suite, name="show_and_delete_case_in_suite"),path('test_case_execute_record/', views.test_case_execute_record, name="test_case_execute_record"),
]

?

9.4 定義視圖函數

?

1 from django.shortcuts import render, redirect, HttpResponse2 from django.contrib import auth  # Django用戶認證(Auth)組件一般用在用戶的登錄注冊上,用于判斷當前的用戶是否合法3 from django.contrib.auth.decorators import login_required4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage5 from .form import UserForm6 import traceback7 from .models import Project, Module, TestCase, CaseSuite, SuiteCase, InterfaceServer, TestCaseExecuteResult8 from .task import case_task9 10 11 # 封裝分頁處理12 def get_paginator(request, data):13     paginator = Paginator(data, 10)  # 默認每頁展示10條數據14     # 獲取 url 后面的 page 參數的值, 首頁不顯示 page 參數, 默認值是 115     page = request.GET.get('page')16     try:17         paginator_pages = paginator.page(page)18     except PageNotAnInteger:19         # 如果請求的頁數不是整數, 返回第一頁。20         paginator_pages = paginator.page(1)21     except InvalidPage:22         # 如果請求的頁數不存在, 重定向頁面23         return HttpResponse('找不到頁面的內容')24     return paginator_pages25 26 27 # 項目菜單項28 @login_required29 def project(request):30     print("request.user.is_authenticated: ", request.user.is_authenticated)31     projects = Project.objects.filter().order_by('-id')32     print("projects:", projects)33     return render(request, 'project.html', {'projects': get_paginator(request, projects)})34 35 36 # 模塊菜單項37 @login_required38 def module(request):39     if request.method == "GET":  # 請求get時候,id倒序查詢所有的模塊數據40         modules = Module.objects.filter().order_by('-id')41         return render(request, 'module.html', {'modules': get_paginator(request, modules)})42     else:  # 否則就是Post請求,會根據輸入內容,使用模糊的方式查找所有的項目43         proj_name = request.POST['proj_name']44         projects = Project.objects.filter(name__contains=proj_name.strip())45         projs = [proj.id for proj in projects]46         modules = Module.objects.filter(belong_project__in=projs)  # 把項目中所有的模塊都找出來47         return render(request, 'module.html', {'modules': get_paginator(request, modules), 'proj_name': proj_name})48 49 50 # 獲取測試用例執行的接口地址51 def get_server_address(env):52     if env:  # 環境處理53         env_data = InterfaceServer.objects.filter(env=env[0])54         print("env_data: {}".format(env_data))55         if env_data:56             ip = env_data[0].ip57             port = env_data[0].port58             print("ip: {}, port: {}".format(ip, port))59             server_address = "http://{}:{}".format(ip, port)60             print("server_address: {}".format(server_address))61             return server_address62         else:63             return ""64     else:65         return ""66 67 68 # 測試用例菜單項69 @login_required70 def test_case(request):71     print("request.session['is_login']: {}".format(request.session['is_login']))72     test_cases = ""73     if request.method == "GET":74         test_cases = TestCase.objects.filter().order_by('id')75         print("testcases: {}".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_required98 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 viewsurlpatterns = [path('', views.index),path('login/', views.login),path('logout/', views.logout),path('project/', views.project, name='project'),path('module/', views.module, name='module'),path('test_case/', views.test_case, name="test_case"),re_path('test_case_detail/(?P<test_case_id>[0-9]+)', views.test_case_detail, name="test_case_detail"),re_path('module_test_cases/(?P<module_id>[0-9]+)/$', views.module_test_cases, name="module_test_cases"),path('case_suite/', views.case_suite, name="case_suite"),re_path('add_case_in_suite/(?P<suite_id>[0-9]+)', views.add_case_in_suite, name="add_case_in_suite"),re_path('show_and_delete_case_in_suite/(?P<suite_id>[0-9]+)', views.show_and_delete_case_in_suite, name="show_and_delete_case_in_suite"),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, HttpResponse2 from django.contrib import auth  # Django用戶認證(Auth)組件一般用在用戶的登錄注冊上,用于判斷當前的用戶是否合法3 from django.contrib.auth.decorators import login_required4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage5 from .form import UserForm6 import traceback7 import json8 from .models import Project, Module, TestCase, CaseSuite, SuiteCase, InterfaceServer, TestCaseExecuteResult9 from .task import case_task10 11 12 # 封裝分頁處理13 def get_paginator(request, data):14     paginator = Paginator(data, 10)  # 默認每頁展示10條數據15     # 獲取 url 后面的 page 參數的值, 首頁不顯示 page 參數, 默認值是 116     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_pages26 27 28 # 項目菜單項29 @login_required30 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_required39 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].ip58             port = env_data[0].port59             print("ip: {}, port: {}".format(ip, port))60             server_address = "http://{}:{}".format(ip, port)61             print("server_address: {}".format(server_address))62             return server_address63         else:64             return ""65     else:66         return ""67 68 69 # 測試用例菜單項70 @login_required71 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_required99 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 viewsurlpatterns = [path('', views.index),path('login/', views.login),path('logout/', views.logout),path('project/', views.project, name='project'),path('module/', views.module, name='module'),path('test_case/', views.test_case, name="test_case"),re_path('test_case_detail/(?P<test_case_id>[0-9]+)', views.test_case_detail, name="test_case_detail"),re_path('module_test_cases/(?P<module_id>[0-9]+)/$', views.module_test_cases, name="module_test_cases"),path('case_suite/', views.case_suite, name="case_suite"),re_path('add_case_in_suite/(?P<suite_id>[0-9]+)', views.add_case_in_suite, name="add_case_in_suite"),re_path('show_and_delete_case_in_suite/(?P<suite_id>[0-9]+)', views.show_and_delete_case_in_suite, name="show_and_delete_case_in_suite"),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, HttpResponse2 from django.contrib import auth  # Django用戶認證(Auth)組件一般用在用戶的登錄注冊上,用于判斷當前的用戶是否合法3 from django.contrib.auth.decorators import login_required4 from django.core.paginator import Paginator, PageNotAnInteger, InvalidPage5 from .form import UserForm6 import traceback7 import json8 from .models import Project, Module, TestCase, CaseSuite, SuiteCase, InterfaceServer, TestCaseExecuteResult9 from .task import case_task10 11 12 # 封裝分頁處理13 def get_paginator(request, data):14     paginator = Paginator(data, 10)  # 默認每頁展示10條數據15     # 獲取 url 后面的 page 參數的值, 首頁不顯示 page 參數, 默認值是 116     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_pages26 27 28 # 項目菜單項29 @login_required30 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_required39 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].ip58             port = env_data[0].port59             print("ip: {}, port: {}".format(ip, port))60             server_address = "http://{}:{}".format(ip, port)61             print("server_address: {}".format(server_address))62             return server_address63         else:64             return ""65     else:66         return ""67 68 69 # 測試用例菜單項70 @login_required71 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_required99 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 %}

?

??

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/90658.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/90658.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/90658.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

一個沒有手動加分號引發的bug

最近因為分號的疏忽&#xff0c;導致出現了一個bug&#xff0c;記錄下來&#xff0c;分享給大家。 1、一個示例 給你下面這一段代碼&#xff0c;你根據經驗判斷一下運營結果 let [a,b] [a,b] let [x,y] [1,2] if(x < y){[x,y] [y,x][a,b] [b,a] }按照一般的理解&#xf…

Elasticsearch安全審計日志設置與最佳實踐

一、Elasticsearch安全審計簡介 審計日志&#xff08;Audit Logging&#xff09;用于記錄Elasticsearch中的安全相關事件&#xff0c;包括認證失敗、連接拒絕、數據訪問事件以及通過API對安全配置&#xff08;如用戶、角色、API密鑰&#xff09;的變更記錄。 注意&#xff1a;審…

算法訓練營day29 貪心算法③ 134. 加油站、135. 分發糖果 、860.檸檬水找零、406.根據身高重建隊列

貪心算法的第三篇博客&#xff0c;繼續腦筋風暴&#xff01; 134. 加油站 寫在前面 這道題規定了有解的話&#xff0c;必定為唯一解 貪心思路 直接從全局進行貪心選擇&#xff0c;情況如下&#xff1a; 情況一&#xff1a;如果gas的總和小于cost總和&#xff0c;那么無論從…

【09】C#入門到精通——C# 結構體對齊 與 常用數據 對應關系

文章目錄1 C# 結構體對齊1.1 默認對齊方式1.2 節對齊方式設置1.3 偏移量設置2 C#與C/C之間類型的對應關系1 C# 結構體對齊 1.1 默認對齊方式 struct默認對齊方式&#xff0c;結構體長度必須是&#xff0c;最大成員長度的整數倍&#xff0c;所以下面結構體大小是 24 (實際占用…

pytest 測試報告生成方案有哪些?

在 pytest 中&#xff0c;除了 Allure 和 HTMLTestRunner&#xff0c;還有許多其他生成測試報告的方法和插件。以下是一些常用的方案及其特點&#xff1a;1. pytest-html&#xff08;官方推薦&#xff09;特點&#xff1a;輕量級、易集成&#xff0c;生成獨立的 HTML 報告。安裝…

Unity中EditorPrefs與PlayerPrefs對比分析

Unity中EditorPrefs與PlayerPrefs對比分析 EditorPrefs與PlayerPrefs是Unity引擎中用于數據持久化的兩個核心類&#xff0c;分別用于于編輯器擴展與游戲運行時場景。以下從設計目標、存儲位置、數據類型、生命周期、安全性、使用場景等方面展開對比&#xff0c;并結合代碼示例說…

藍光中的愧疚

藍光中的愧疚活動結束那晚&#xff0c;深圳的空氣吸飽了水汽&#xff0c;沉甸甸地壓在胸口。我站在西鄉社區活動中心冰涼的玻璃門外&#xff0c;目送著最后一個離開的王老師。她關掉門廳的燈&#xff0c;電子門鎖合攏時發出輕微卻尖銳的“嘀”聲&#xff0c;像一根細針扎在我緊…

Linux: network: wireshark: esp attempt to detec null-encrypted esp payloads

最近看到一個pcap文件&#xff0c;里面有esp協議包&#xff0c;而且是明文/沒有加密的消息&#xff0c;為什么wireshark沒有將esp上層的tcp/sip消息沒有解出來。 類似于Info列只有ESP的信息。后來選中了協議選項里的&#xff1a;attempt to detect/decode NULL encrypted ESP p…

10分鐘搭建腳手架:Spring Boot 3.2 + Vue3 前后端分離模板

10分鐘搭建腳手架&#xff1a;Spring Boot 3.2 Vue3 前后端分離模板一、項目結構設計二、后端搭建&#xff08;Spring Boot 3.2&#xff09;1. 快速初始化&#xff08;使用 Spring Initializr&#xff09;2. 核心配置application.yml跨域配置 CorsConfig.java3. 安全配置Secur…

【軌物方案】分布式光伏電站運維升級智能化系列:老電站的數智化重生

自2010年分布式光伏在國內興起以來&#xff0c;十余年間&#xff0c;市場裝機容量已實現飛躍式增長。長期以來&#xff0c;傳統的人工巡查和抄表模式是它們日常運維的主要手段。然而&#xff0c;隨著電站數量的激增和設備的老化&#xff0c;由此導致的事故頻發&#xff0c;使得…

RAG 技術深度面試題:架構、優化與實踐應用

1. RAG 基礎架構設計 問題&#xff1a;對比單階段檢索&#xff08;Single-stage Retrieval&#xff09;與兩階段檢索&#xff08;Two-stage Retrieval&#xff09;在 RAG 系統中的架構差異&#xff0c;說明在企業知識庫場景下為何優先選擇兩階段檢索&#xff1f; 答案&#xff…

yolov8通道級剪枝講解(超詳細思考版)

為了提升推理速度并降低部署成本&#xff0c;模型剪枝已成為關鍵技術。本文將結合實踐操作&#xff0c;講解YOLOv8模型剪枝的方法原理、實施步驟及注意事項。 雖然YOLOv8n版本本身參數量少、推理速度快&#xff0c;能滿足大多數工業檢測需求&#xff0c;但谷歌研究表明&#x…

JavaSE:隨機數生成

隨機數在游戲開發、密碼學、模擬測試等場景中扮演著關鍵角色。本文將深入探討Java中兩種主流的隨機數生成技術&#xff1a;Random類和Math.random()方法&#xff0c;并解析背后的類與對象概念&#xff0c;助你全面掌握隨機數生成的核心機制。一、隨機數生成的兩大技術 Java提供…

Android 持久化存儲原理與使用解析

一、核心存儲方案詳解1. SharedPreferences (SP)使用方式&#xff1a;// 獲取實例 SharedPreferences sp getSharedPreferences("user_prefs", MODE_PRIVATE);// 寫入數據 sp.edit().putString("username", "john_doe").putInt("login_cou…

無 sudo 權限的環境下將 nvcc (CUDA Toolkit) 安裝到個人目錄 linux

要在無 sudo 權限的環境下將 nvcc 安裝到 home 個人目錄&#xff0c;你可以手動安裝 CUDA Toolkit 到你的 $HOME 目錄&#xff0c;只需以下幾步即可使用 nvcc 編譯 CUDA 程序。 ? 步驟&#xff1a;本地安裝 CUDA Toolkit&#xff08;含 nvcc&#xff09; 下載 CUDA Toolkit Ru…

從指標定義到AI執行流:衡石SENSE 6.0的BI PaaS如何重構ISV分析鏈路

一、痛點&#xff1a;ISV行業解決方案的“三重斷鏈”傳統ISV構建行業分析模塊時面臨的核心挑戰&#xff1a;指標定義碎片化&#xff1a;客戶A的“銷售額”含稅&#xff0c;客戶B不含稅&#xff0c;衍生指標無法復用&#xff1b;分析-執行割裂&#xff1a;發現庫存異常后需人工導…

構建跨平臺遠程醫療系統中的視頻通路技術方案探究

一、遠程醫療走向日常化&#xff0c;音視頻能力成為關鍵基礎設施 隨著醫療數字化與分級診療體系的不斷演進&#xff0c;遠程醫療正從試點探索階段&#xff0c;逐步邁向常態化、標準化應用。從縣域醫院遠程問診、基層醫療協作&#xff0c;到大型三甲醫院的術中協同、專科教學直…

Blackbox Exporter Docker 安裝配置,并與 Prometheus 集成

1. 創建配置文件目錄bashmkdir -p ~/docker/blackbox/config cd ~/docker/blackbox2. 創建 Blackbox Exporter 配置文件 config/blackbox.ymlyamlmodules:http_2xx: # HTTP 可用性檢測(響應 2xx/3xx 狀態碼)prober: httphttp:valid_http_versions: ["HTTP/1.1", &qu…

杰理通用MCU串口+AT指令+485通訊工業語音芯片

一、概述 在現代智能設備與自動化系統中&#xff0c;語音交互功能日益普及&#xff0c;通用 MCU 語音芯片作為核心組件&#xff0c;承擔著關鍵的語音處理任務。其強大的功能不僅體現在語音合成、識別等方面&#xff0c;還包括高效的通信能力。串口 AT 指令 485 通訊模式為通用…

Krpano 工具如何調節全景圖片切割之后的分辨率

文章目錄概要第一步1.1 復制一下這個文件中的key &#xff0c;打開 krpano Tools.exe第二步 修改切片之后的分辨率修改前的效果修改后的效果概要 前端渲染全景圖模擬3D場景 Krpano 工具 獲取到后的默認圖片分辨率是2048*2048的&#xff0c;如果覺得分辨率低了可以自行在工具中…