8. 測試用例執行?
預期效果如下:
?
用例執行邏輯如下:
- 前端提交用例 id 列表到后臺,后臺獲取每一條用例的信息;
- 后臺獲取域名信息、用例 id 列表;
- 對用例的請求數據進行變量的參數化、函數化等預處理操作;
- 根據先后順序進行接口請求,并對響應數據進行斷言;
- 根據用例中的提取變量表達式,從斷言成功的響應數據中提取關聯變量值用于后續用例使用。
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 %}
?
??
?