自动化测试平台开发(六):接口测试 - 数据表设计

本篇开始接口测试部分详细开发。

1. 数据表设计

  django ORM设计、处理数据表,表字段内容后期空闲了再更新。。。

  1. BaseModel - 基础表
  2. 部门表
  3. 项目表
  4. 项目动态
  5. 项目成员
  6. 接口分组
  7. 接口表
  8. 接口更新历史
  9. YAPI事件表
  10. 全局环境配置
  11. 全局变量
  12. 全局请求头
  13. 全局标签
  14. 全局校验规则
  15. 测试用例集
  16. 测试用例表
  17. 测试步骤表
  18. 测试步骤表
  19. 任务

 2. 附源码

  1 import json
  2 from django.utils import timezone
  3 from django.db import models
  4 from django.core.validators import validate_comma_separated_integer_list
  5 from django.contrib.auth.models import User
  6 from rest_framework.authtoken.models import Token
  7 
  8 # 测试结果选项
  9 RESULT_CHOICE = (
 10     ('passed', '成功'),
 11     ('failed', '失败'),
 12     ('skipped', '跳过'),
 13     ('error', '故障'),
 14     ('', '未执行'),
 15 )
 16 # 接口变更处理进度选项
 17 API_UPDATE_STATUS_CHOICE = (
 18     (0, '待处理'),
 19     (1, '待验证'),
 20     (2, '已处理')
 21 )
 22 # 测试构建类型选项
 23 BUILD_TYPE_CHOICE = (
 24     ('环境验证', '环境验证'),
 25     ('冒烟测试', '冒烟测试'),
 26     ('业务巡检', '业务巡检'),
 27     ('其他', '其他')
 28 )
 29 
 30 
 31 class SoftDelTableQuerySet(models.QuerySet):
 32     def delete(self):
 33         self.update(is_delete=True, delete_time=timezone.now())
 34 
 35 
 36 class BaseManager(models.Manager):
 37     _queryset_class = SoftDelTableQuerySet
 38 
 39     def get_queryset(self):
 40         return super().get_queryset().filter(is_delete=False)
 41 
 42 
 43 # 基础表:公共字段列 - 创建时间/更新时间/状态/描述
 44 class BaseModel(models.Model):
 45     """
 46     公共字段列
 47     """
 48 
 49     create_time = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)
 50     update_time = models.DateTimeField(verbose_name='更新时间', null=True, auto_now=True)
 51     delete_time = models.DateTimeField(verbose_name="删除时间", null=True, default=None)
 52     is_delete = models.BooleanField(verbose_name='是否已删除', default=False)
 53 
 54     # creator = models.CharField(verbose_name="创建人", max_length=20, null=True)
 55     # updater = models.CharField(verbose_name="更新人", max_length=20, null=True)
 56     status = models.BooleanField(default=True, verbose_name='状态(1正常 0停用)')
 57     description = models.CharField(max_length=4096, blank=True, null=True, verbose_name='描述')
 58 
 59     def delete(self, using=None, keep_parents=False):
 60         self.is_delete = True
 61         self.delete_time = timezone.now()
 62         self.save()
 63 
 64     objects = BaseManager()
 65 
 66     class Meta:
 67         abstract = True  # 抽象基类
 68         verbose_name = "公共字段表"
 69         db_table = 'base_table'
 70 
 71 
 72 # 字典类型表 - 灵活配置、kv存储
 73 class DictType(models.Model):
 74     id = models.AutoField(primary_key=True)
 75     name = models.CharField(max_length=100, default='', verbose_name='字典名称')
 76     type = models.CharField(max_length=100, default='', verbose_name='字典类型')
 77     remark = models.CharField(max_length=500, default='', verbose_name='备注')
 78 
 79     creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
 80                                 verbose_name="创建人", related_name="dict_type_creator")
 81     updater = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
 82                                 verbose_name="更新人", related_name="dict_type_updater")
 83 
 84     def __unicode__(self):
 85         return self.name
 86 
 87     def __str__(self):
 88         return self.name
 89 
 90     class Meta:
 91         verbose_name = '字典类型表'
 92         verbose_name_plural = '字典类型表'
 93 
 94 
 95 # 字典数据表 - 灵活配置、kv存储
 96 class DictData(BaseModel):
 97     id = models.AutoField(primary_key=True)
 98     dict_type = models.ForeignKey(DictType, on_delete=models.SET_NULL, null=True, max_length=50,
 99                                   verbose_name="字典名称", related_name="data_dict")
100     dict_sort = models.CharField(max_length=100, default='', verbose_name='字典排序')
101     dict_label = models.CharField(max_length=100, default='', verbose_name='字典标签')
102     dict_value = models.CharField(max_length=100, default='', verbose_name='字典键值')
103     list_class = models.CharField(max_length=100, default='', verbose_name='表格回显样式(success/info/warning/danger)')
104     is_default = models.BooleanField(default=False, verbose_name='是否默认(1是 0否)')
105     remark = models.CharField(max_length=500, default='', verbose_name='备注')
106 
107     creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
108                                 verbose_name="创建人", related_name="dict_data_creator")
109     updater = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
110                                 verbose_name="更新人", related_name="dict_data_updater")
111 
112     def __unicode__(self):
113         return self.dict_type.type
114 
115     def __str__(self):
116         return self.dict_type.type
117 
118     class Meta:
119         verbose_name = '字典数据表'
120         verbose_name_plural = '字典数据表'
121 
122 
123 # 部门表
124 class Department(BaseModel):
125     id = models.AutoField(primary_key=True)
126     name = models.CharField(max_length=50, verbose_name='部门名称')
127     safe_name = models.SlugField(default='',  blank=True, null=True, max_length=50, verbose_name='部门标识(用作文件夹名)')
128     leader = models.CharField(default='',  blank=True, null=True, max_length=50, verbose_name='负责人')
129 
130     def __unicode__(self):
131         return self.name
132 
133     def __str__(self):
134         return self.name
135 
136     class Meta:
137         verbose_name = '部门'
138         verbose_name_plural = '部门'
139 
140 
141 # 项目表
142 class Project(BaseModel):
143     """项目表"""
144     id = models.AutoField(primary_key=True)
145     name = models.CharField(verbose_name='项目名称', max_length=50)
146     version = models.CharField(verbose_name='版本', max_length=50, null=True)
147     department = models.ForeignKey(Department, on_delete=models.CASCADE, null=True, verbose_name='部门')
148     creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
149                                 verbose_name="创建人", related_name="project_creator")
150     updater = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
151                                 verbose_name="更新人", related_name="project_updater")
152 
153     def __unicode__(self):
154         return self.name
155 
156     def __str__(self):
157         return self.name
158 
159     class Meta:
160         verbose_name = '项目'
161         verbose_name_plural = '项目'
162 
163 
164 # 项目动态 --TODO
165 class ProjectDynamic(models.Model):
166     """项目动态"""
167     id = models.AutoField(primary_key=True)
168     project = models.ForeignKey(Project, related_name='dynamic_project', on_delete=models.CASCADE, verbose_name='所属项目')
169     time = models.DateTimeField(verbose_name='操作时间', max_length=128)
170     type = models.CharField(verbose_name='操作类型', max_length=50)
171     operationObject = models.CharField(verbose_name='操作对象', max_length=50)
172     user = models.ForeignKey(User, blank=True, null=True, related_name='userName',
173                              on_delete=models.SET_NULL, verbose_name='操作人')
174     description = models.CharField(max_length=1024, blank=True, null=True, verbose_name='描述')
175 
176     def __unicode__(self):
177         return self.type
178 
179     class Meta:
180         verbose_name = '项目动态'
181         verbose_name_plural = '项目动态'
182 
183 
184 # 项目成员
185 class ProjectMember(models.Model):
186     """"""
187     CHOICES = (
188         ('超级管理员', '超级管理员'),
189         ('开发人员', '开发人员'),
190         ('测试人员', '测试人员'),
191         ('测试开发', '测试开发'),
192         ('游客', '游客')
193     )
194     id = models.AutoField(primary_key=True)
195     role = models.CharField(max_length=50, verbose_name='角色', choices=CHOICES)
196     status = models.BooleanField(default=True, verbose_name='状态')
197     project = models.ForeignKey(Project, related_name='member_project', on_delete=models.CASCADE, verbose_name='所属项目')
198     user = models.ForeignKey(User, related_name='member_user', on_delete=models.CASCADE, verbose_name='用户')
199 
200     def __unicode__(self):
201         return self.role
202 
203     def __str__(self):
204         return self.role
205 
206     class Meta:
207         verbose_name = '项目成员'
208         verbose_name_plural = '项目成员'
209 
210 
211 # app系统配置
212 def default_app_setting():
213     setting = {
214         "debug": {"value": True, "description": "Debug模式"},
215         "file_log_level": {"value": 'DEBUG', "description": "日志等级(文件)"},
216         "console_log_level": {"value": 'INFO', "description": "日志等级(控制台)"},
217         "testcase_max_rotation": {"value": 50, "description": "保留历史构建数据的最大个数"}
218     }
219     return setting
220 
221 
222 class AppSetting(models.Model):
223     """app系统配置,如debug模式、构建保留数等"""
224     id = models.AutoField(primary_key=True)
225     name = models.CharField(max_length=50, null=True, default='app系统配置', verbose_name='Name')
226     data = models.JSONField(default=default_app_setting, verbose_name='APP配置数据')
227     description = models.CharField(max_length=1024, blank=True, null=True, verbose_name='描述')
228     status = models.BooleanField(default=True, verbose_name='状态')
229 
230     def delete(self, using=None, keep_parents=False):
231         return "app系统配置,禁止删除!"
232 
233     def __unicode__(self):
234         return self.name
235 
236     def __str__(self):
237         return self.name
238 
239     def get_default_app_setting(self):
240         return default_app_setting()
241 
242     class Meta:
243         verbose_name = 'app系统配置'
244         verbose_name_plural = 'app系统配置'
245 
246 
247 # 全局环境配置
248 def default_env_config():
249     config = {
250         "company_id": {"value": '', "description": "公司ID"},
251         "base_url_mk": {"value": '', "description": "服务商后台地址"},
252         "base_url_qw": {"value": '', "description": "企微端地址"},
253         "base_url_oss_bill": {"value": '', "description": "运营计费地址"},
254         "base_url_oss_official": {"value": '', "description": "运营官方地址"},
255         "base_url_qyapi": {"value": 'https://qyapi.weixin.qq.com', "description": "企业微信地址"},
256         "corp_id": {"value": '', "description": "企微企业标识corp_id"}
257     }
258     return config
259 
260 
261 def default_qw_external_contact_config():
262     cf = {
263         "external_contact_token": {"value": '', "description": "企微客户联系Token"},
264         "external_contact_aes_key": {"value": '', "description": "企微客户联系AESKey"},
265         "external_contact_corp_secret": {"value": '', "description": "企微客户联系Secret"},
266     }
267     return cf
268 
269 
270 class GlobalEnv(models.Model):
271     """
272     测试环境配置 environment
273     """
274     id = models.AutoField(primary_key=True)
275     name = models.CharField(max_length=50, verbose_name='名称')
276     config = models.JSONField(default=default_env_config, verbose_name='环境基础配置')
277     qw_external_contact_config = models.JSONField(default=default_qw_external_contact_config, verbose_name='企微客户联系配置')
278     data = models.JSONField(default=dict, verbose_name='环境数据')
279     mock = models.JSONField(default=dict, verbose_name='环境mock数据')
280     mock_dynamic = models.BooleanField("动态更新mock", default=True)
281     description = models.CharField(max_length=1024, blank=True, null=True, verbose_name='描述')
282     status = models.BooleanField(default=True, verbose_name='状态')
283     is_default = models.BooleanField("默认配置", default=False)
284 
285     def __unicode__(self):
286         return self.name
287 
288     def __str__(self):
289         return self.name
290 
291     def get_default_config(self):
292         return default_env_config()
293 
294     def get_default_qw_external_contact_config(self):
295         return default_qw_external_contact_config()
296 
297     class Meta:
298         verbose_name = '环境配置'
299         verbose_name_plural = '环境配置管理'
300 
301 
302 # 全局const
303 class GlobalConst(models.Model):
304     """全局const"""
305     id = models.AutoField(primary_key=True)
306     name = models.CharField(max_length=50, verbose_name='Name')
307     value = models.TextField(blank=True, null=True, verbose_name='Value')
308     description = models.CharField(max_length=1024, blank=True, null=True, verbose_name='描述')
309     status = models.BooleanField(default=True, verbose_name='状态')
310 
311     def __unicode__(self):
312         return self.name
313 
314     def __str__(self):
315         return self.name
316 
317     class Meta:
318         verbose_name = 'Const'
319         verbose_name_plural = '全局Const管理'
320 
321 
322 # 全局Header
323 class GlobalHeader(models.Model):
324     id = models.AutoField(primary_key=True)
325     name = models.CharField(max_length=1024, verbose_name="名称")
326     value = models.TextField(blank=True, null=True, verbose_name='内容')
327     description = models.CharField(max_length=1024, blank=True, null=True, verbose_name='描述')
328     status = models.BooleanField(default=True, verbose_name='状态')
329 
330     def __unicode__(self):
331         return self.name
332 
333     def __str__(self):
334         return self.name
335 
336     class Meta:
337         verbose_name = '全局请求头'
338         verbose_name_plural = '全局请求头管理'
339 
340 
341 # 通用校验规则配置
342 class GlobalResponseValidate(models.Model):
343     id = models.AutoField(primary_key=True)
344     name = models.CharField(max_length=1024, default='默认校验', verbose_name="名称")
345     description = models.CharField(max_length=1024, blank=True, null=True, verbose_name='描述')
346     check_status_code = models.BooleanField(default=True, verbose_name='检查状态代码')
347     check_json_schema = models.BooleanField(default=True, verbose_name='检查json-schema')
348     check_response_data = models.BooleanField(default=True, verbose_name='检查响应数据')
349     status_code = models.CharField(default='200', max_length=500, verbose_name='期望状态代码',
350                                    validators=[validate_comma_separated_integer_list])
351     status = models.BooleanField(default=True, verbose_name='状态')
352     is_default = models.BooleanField("默认配置", default=False)
353 
354     def save(self, *args, **kwargs):
355         if self.is_default:
356             try:
357                 GlobalResponseValidate.objects.filter(is_default=True).update(is_default=False)
358             except GlobalResponseValidate.DoesNotExist:
359                 pass
360         super(GlobalResponseValidate, self).save(*args, **kwargs)
361 
362     def __unicode__(self):
363         return self.name
364 
365     def __str__(self):
366         return self.name
367 
368     class Meta:
369         verbose_name = '通用校验规则配置'
370         verbose_name_plural = '通用校验规则配置管理'
371 
372 
373 # 全局标签
374 class GlobalLabel(models.Model):
375     """
376     全局标签
377     """
378     LABEL_TYPE_CHOICE = (
379         ('priority', '优先级'),  # 如 P0、P1、P2
380         ('severity', '严重等级'),  # 如 normal、blocker
381         ('function', '业务功能'),  # 如 登录、权限检查
382         ('testsuite_type', '测试集类型'),  # 如 冒烟、巡检、环境验证
383         ('other', '其他'),
384     )
385     id = models.AutoField(primary_key=True)
386     name = models.CharField(max_length=50, verbose_name='标签名')
387     type = models.CharField(verbose_name='标签类型', choices=LABEL_TYPE_CHOICE, default='priority', max_length=20)
388     status = models.BooleanField(default=True, verbose_name='状态')
389     description = models.CharField(max_length=1024, blank=True, null=True, verbose_name='描述')
390 
391     def __unicode__(self):
392         return self.name
393 
394     def __str__(self):
395         return self.name
396 
397     class Meta:
398         verbose_name = '标签'
399         verbose_name_plural = '标签管理'
400 
401 
402 # 自定义方法 --TODO
403 class CustomMethod(models.Model):
404     """
405     自定义方法
406     """
407     id = models.AutoField(primary_key=True)
408     project = models.ForeignKey(Project, on_delete=models.CASCADE, verbose_name='项目')
409     name = models.CharField(max_length=50, verbose_name='方法名')
410     description = models.CharField(max_length=1024, blank=True, null=True, verbose_name='描述')
411     type = models.CharField(max_length=50, verbose_name='类型')
412     dataCode = models.TextField(verbose_name='代码')
413     status = models.BooleanField(default=True, verbose_name='状态')
414 
415     def __unicode__(self):
416         return self.name
417 
418     class Meta:
419         verbose_name = '自定义方法'
420         verbose_name_plural = '自定义方法'
421 
422 
423 # 接口分组
424 class ApiGroup(BaseModel):
425     """
426     接口分组
427     """
428     id = models.AutoField(primary_key=True)
429     name = models.CharField(max_length=50, verbose_name='接口分组名称')
430     project = models.ForeignKey(Project, on_delete=models.CASCADE, verbose_name='项目')
431 
432     def __unicode__(self):
433         return self.name
434 
435     def __str__(self):
436         return self.name
437 
438     class Meta:
439         verbose_name = '接口分组'
440         verbose_name_plural = '接口分组'
441 
442 
443 # 接口信息
444 class ApiInfo(BaseModel):
445     """
446     接口信息
447     """
448     HTTP_CHOICE = (
449         ('HTTP', 'HTTP'),
450         ('HTTPS', 'HTTPS')
451     )
452     REQUEST_TYPE_CHOICE = (
453         ('POST', 'POST'),
454         ('GET', 'GET'),
455         ('PUT', 'PUT'),
456         ('DELETE', 'DELETE'),
457         ('CALL', 'CALL')
458     )
459     HOST_TAG_CHOICE = (
460         ('mk', '服务商后台地址'),
461         ('qw', '企微端地址'),
462         ('oss_bill', '运营计费地址'),
463         ('oss_official', '运营官方地址'),
464         ('qyapi', '企业微信API'),
465     )
466     # 接口数据来源,默认manual - 手动添加
467     ORIGIN_CHOICE = (
468         ('yapi', 'yapi'),
469         ('xmind', 'xmind'),
470         ('excel', 'excel'),
471         ('manual', 'manual'),
472     )
473 
474     id = models.AutoField(primary_key=True)
475     yapi_id = models.IntegerField(default=0, verbose_name="YAPI接口ID", )
476     project = models.ForeignKey(Project, related_name='api_project', null=True, on_delete=models.CASCADE,
477                                 verbose_name='所属项目')
478     api_group = models.ForeignKey(ApiGroup, blank=True, null=True, related_name='api_group',
479                                   on_delete=models.SET_NULL, verbose_name='接口分组')
480     origin = models.CharField(max_length=50, default='manual', verbose_name='接口数据来源', choices=ORIGIN_CHOICE)
481     name = models.CharField(max_length=500, verbose_name='接口名称')
482     http_type = models.CharField(max_length=50, default='HTTP', verbose_name='HTTP/HTTPS', choices=HTTP_CHOICE)
483     host_tag = models.CharField(max_length=30, verbose_name='指定host', default='mk', choices=HOST_TAG_CHOICE)
484     method = models.CharField(max_length=50, verbose_name='请求方式', choices=REQUEST_TYPE_CHOICE)
485     path = models.CharField(max_length=1024, verbose_name='接口地址')
486     # yapi定义参数 - diff
487     yapi_req_headers = models.TextField(blank=True, null=True, verbose_name='yapi定义-请求头')  # json字符串,diff
488     yapi_req_params = models.TextField(blank=True, null=True, verbose_name='yapi定义-请求参数-params')  # json字符串,diff
489     yapi_req_query = models.TextField(blank=True, null=True, verbose_name='yapi定义-请求参数-query')  # json字符串,diff
490     yapi_req_body_form = models.TextField(blank=True, null=True, verbose_name='yapi定义-请求参数-body_form')  # json字符串,diff
491     yapi_req_body_other = models.TextField(blank=True, null=True, verbose_name='yapi定义-请求参数-body_other')  # json字符串,diff
492     yapi_res_body = models.TextField(blank=True, null=True, verbose_name='yapi定义-响应body')  # json字符串,diff
493     # 参数模板
494     req_headers = models.TextField(blank=True, null=True, verbose_name='请求头')  # json字符串
495     req_params = models.TextField(blank=True, null=True, verbose_name='请求参数-params')  # json字符串
496     req_data = models.TextField(blank=True, null=True, verbose_name='请求参数-data')  # json字符串
497     req_json = models.TextField(blank=True, null=True, verbose_name='请求参数-json')  # json字符串
498     validator = models.TextField(blank=True, null=True, verbose_name='响应数据验证')  # json字符串
499 
500     update_status = models.IntegerField(default=1, verbose_name="更新状态", choices=API_UPDATE_STATUS_CHOICE)
501     labels = models.ManyToManyField(GlobalLabel, blank=True, default=[],
502                                     verbose_name='标签', related_name="api_label")
503     creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
504                                 verbose_name="创建人", related_name="api_info_creator")
505     updater = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
506                                 verbose_name="更新人", related_name="api_info_updater")
507 
508     def __unicode__(self):
509         return self.name
510 
511     def __str__(self):
512         return self.name
513 
514     class Meta:
515         verbose_name = '接口'
516         verbose_name_plural = '接口管理'
517 
518 
519 # 接口变更历史
520 class ApiUpdateHistory(models.Model):
521     id = models.AutoField(primary_key=True)
522     api = models.ForeignKey(ApiInfo, related_name='update_api', on_delete=models.CASCADE, verbose_name='所属接口')
523     event = models.CharField(blank=True, null=True, max_length=100,
524                              verbose_name='变更事件')  # "interface_add", "interface_del", "interface_update"
525     content = models.TextField(blank=True, null=True, verbose_name='变更内容')
526     updater = models.CharField(blank=True, null=True, max_length=50, verbose_name="更新人")
527     update_time = models.DateTimeField(verbose_name='变更时间', auto_now=True)
528     update_status = models.IntegerField(default=0, verbose_name="变更处理状态", choices=API_UPDATE_STATUS_CHOICE)
529 
530     def __unicode__(self):
531         return self.id
532 
533     def __str__(self):
534         return self.id
535 
536     class Meta:
537         verbose_name = '接口变更历史'
538         verbose_name_plural = '接口变更历史管理'
539 
540 
541 # yapi接口变更事件 -- 即事件待同步处理任务列表,yapi hook秒call几十上百次变更,同步处理完成不了,存储事件表后遍历处理
542 class YApiEvent(models.Model):
543     id = models.AutoField(primary_key=True)
544     yapi_id = models.IntegerField(default=0, verbose_name='YAPI接口ID')
545     event = models.CharField(blank=True, null=True, max_length=100, verbose_name='变更事件')
546     content = models.TextField(blank=True, null=True, verbose_name='变更内容')
547     updater = models.CharField(blank=True, null=True, max_length=50, verbose_name="更新人")
548     update_time = models.DateTimeField(verbose_name='变更时间', auto_now=True)
549 
550     def __unicode__(self):
551         return self.id
552 
553     def __str__(self):
554         return self.id
555 
556     class Meta:
557         verbose_name = 'yapi接口事件'
558         verbose_name_plural = 'yapi接口事件管理'
559 
560 
561 # 测试用例集
562 class TestSuite(BaseModel):
563     """测试用例集"""
564     from django.core import serializers
565     serializers.get_serializer("json")()
566 
567     id = models.AutoField(primary_key=True)
568     name = models.CharField(max_length=50, verbose_name='用例集')
569     safe_name = models.SlugField(max_length=50, verbose_name='用例集标识(用作py文件夹名)')
570     department = models.ForeignKey(Department, related_name='suite_dept', on_delete=models.CASCADE, null=True, verbose_name='部门')
571     headers = models.TextField(blank=True, null=True, verbose_name='请求头')
572     labels = models.ManyToManyField(GlobalLabel, blank=True, default=[], verbose_name='标签', related_name="suite_label")
573 
574     setup = models.JSONField(blank=True, null=True, default=list, verbose_name='setup')
575     setup_class = models.JSONField(blank=True, null=True, default=list, verbose_name='setup_class')
576     teardown = models.JSONField(blank=True, null=True, default=list, verbose_name='teardown')
577     teardown_class = models.JSONField(blank=True, null=True, default=list, verbose_name='teardown_class')
578 
579     creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
580                                 verbose_name="创建人", related_name="suite_creator")
581     updater = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
582                                 verbose_name="更新人", related_name="suite_updater")
583 
584     def __unicode__(self):
585         return self.name
586 
587     def __str__(self):
588         return self.name
589 
590     class Meta:
591         verbose_name = '用例集'
592         verbose_name_plural = '用例集管理'
593 
594 
595 # 测试用例
596 class TestCase(BaseModel):
597     """测试用例"""
598     TEST_TYPE_CHOICE = (
599         ('单接口测试', '单接口测试'),
600         ('场景测试', '场景测试'),
601         ('性能测试', '性能测试'),
602         ('setup', 'setup'),
603         ('teardown', 'teardown'),
604     )
605     SEVERITY_CHOICE = (
606         ('blocker', '阻塞缺陷(功能未实现,无法下一步)'),
607         ('critical', '严重缺陷(功能点缺失)'),
608         ('normal', '一般缺陷(边界情况,格式错误)'),
609         ('minor', '次要缺陷(界面错误与ui需求不符)'),
610         ('trivial', '轻微缺陷(必须项无提示,或者提示不规范)'),
611     )
612     id = models.AutoField(primary_key=True)
613     name = models.CharField(max_length=50, verbose_name='用例名称')
614     safe_name = models.SlugField(max_length=50, verbose_name='用例集标识(用作py类名)')
615     test_suite = models.ForeignKey(TestSuite, related_name='case_suite', on_delete=models.CASCADE, verbose_name='所属用例集')
616     labels = models.ManyToManyField(GlobalLabel, blank=True, default=[], verbose_name='标签', related_name="case_label")
617     type = models.CharField(verbose_name='测试类型', choices=TEST_TYPE_CHOICE, default='单接口测试', max_length=20)
618     variables = models.TextField(blank=True, null=True, verbose_name='用例变量')  # json字符串,Dict[Text, Any]
619     depends = models.TextField(blank=True, null=True, verbose_name='依赖项(用例)')  # depends依赖项
620     severity = models.CharField(verbose_name='用例等级', choices=SEVERITY_CHOICE, default='normal', max_length=20)  # 暂时没使用
621     result = models.CharField(verbose_name='测试结果', choices=RESULT_CHOICE, max_length=30, default='null')
622     creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
623                                 verbose_name="创建人", related_name="case_creator")
624     updater = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
625                                 verbose_name="更新人", related_name="case_updater")
626 
627     def __unicode__(self):
628         return self.name
629 
630     def __str__(self):
631         return self.name
632 
633     class Meta:
634         verbose_name = '用例'
635         verbose_name_plural = '用例管理'
636 
637 
638 # 测试用例步骤
639 class TestStep(BaseModel):
640     """
641     测试用例步骤
642     """
643     id = models.AutoField(primary_key=True)
644     sid = models.IntegerField(default=0, verbose_name='执行步骤ID')
645     name = models.CharField(max_length=50, verbose_name='步骤名称')
646     test_case = models.ForeignKey(TestCase, related_name='step_case', on_delete=models.CASCADE, verbose_name='所属用例')
647     apiInfo = models.ForeignKey(ApiInfo, related_name='step_api', on_delete=models.CASCADE, verbose_name='所属接口')
648     labels = models.ManyToManyField(GlobalLabel, blank=True, default=[], verbose_name='标签', related_name="step_label")
649     depends = models.ManyToManyField('self', blank=True, default=[], verbose_name='依赖项(步骤)', related_name='step_depends')  # depends依赖项
650     skipif = models.TextField(blank=True, null=True, default='', verbose_name='skipif')  # 是否跳过步骤:条件表达式
651     setup_hooks = models.JSONField(blank=True, null=True, default=list, verbose_name='setup_hooks')
652     teardown_hooks = models.JSONField(blank=True, null=True, default=list, verbose_name='teardown_hooks')
653     req_path_extend = models.CharField(blank=True, null=True, max_length=1024, default='', verbose_name='请求path扩展')
654     req_headers = models.TextField(blank=True, null=True, verbose_name='请求头')  # json字符串
655     req_params = models.TextField(blank=True, null=True, verbose_name='请求参数-params')  # json字符串
656     req_json = models.TextField(blank=True, null=True, verbose_name='请求参数-json')  # json字符串
657     req_data = models.TextField(blank=True, null=True, verbose_name='请求参数-data')  # json字符串
658     validator = models.TextField(blank=True, null=True, verbose_name='响应数据验证')  # json字符串
659     extractor = models.TextField(blank=True, null=True, verbose_name='响应数据变量提取')  # json字符串
660     result = models.CharField(verbose_name='测试结果', choices=RESULT_CHOICE, max_length=30, default='null')
661     creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
662                                 verbose_name="创建人", related_name="step_creator")
663     updater = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
664                                 verbose_name="更新人", related_name="step_updater")
665 
666     def __unicode__(self):
667         return self.description
668 
669     def __str__(self):
670         return self.description
671 
672     class Meta:
673         verbose_name = '测试用例步骤'
674         verbose_name_plural = '测试用例步骤管理'
675 
676 
677 # 测试报告
678 class TestReport(BaseModel):
679     BUILD_STATUS_CHOICE = (
680         ('build-status-static', '构建完成'),
681         ('build-status-in-progress', '构建正在进行中')
682     )
683     id = models.AutoField(primary_key=True)
684     # 构建信息
685     create_time = models.DateTimeField('创建时间', auto_now_add=True)
686     build_type = models.CharField(verbose_name='构建类型', choices=BUILD_TYPE_CHOICE, default='其他', max_length=20)
687     build_status = models.CharField(verbose_name='构建状态', choices=BUILD_STATUS_CHOICE, default='build-status-in-progress', max_length=50)
688     env = models.ForeignKey(GlobalEnv, blank=True, null=True, related_name='report_env', on_delete=models.CASCADE,
689                             verbose_name='报告关联测试环境')
690     # 结果
691     status = models.BooleanField(default=True, verbose_name='状态')
692     duration = models.IntegerField(default=0, verbose_name='耗时(秒)')
693     # case统计
694     case_total = models.IntegerField(default=0, verbose_name='用例总数')
695     case_passed = models.IntegerField(default=0, verbose_name='用例成功数量')
696     case_failed = models.IntegerField(default=0, verbose_name='用例失败数量')
697     case_skipped = models.IntegerField(default=0, verbose_name='用例跳过数量')
698     case_error = models.IntegerField(default=0, verbose_name='用例故障数量')
699     case_pass_rate = models.FloatField(default=0, verbose_name='用例通过率')
700     # step 统计
701     step_total = models.IntegerField(default=0, verbose_name='步骤总数')
702     step_passed = models.IntegerField(default=0, verbose_name='步骤成功数量')
703     step_failed = models.IntegerField(default=0, verbose_name='步骤失败数量')
704     step_skipped = models.IntegerField(default=0, verbose_name='步骤跳过数量')
705     step_error = models.IntegerField(default=0, verbose_name='步骤故障数量')
706     step_pass_rate = models.FloatField(default=0, verbose_name='步骤通过率')
707     broken_apis = models.JSONField(default=dict, verbose_name='阻塞接口')
708     # 报告、日志地址记录
709     client = models.CharField(default='localhost', max_length=50, blank=True, null=True, verbose_name='测试机器Client端')
710     log_path = models.TextField(max_length=500, blank=True, null=True, verbose_name='日志文件地址')
711     html_report_path = models.TextField(max_length=500, blank=True, null=True, verbose_name='pytest-html报告地址')
712     allure_xml_path = models.TextField(max_length=500, blank=True, null=True, verbose_name='allure xml数据地址')
713     allure_url = models.URLField(max_length=500, blank=True, null=True, verbose_name='allure 报告地址')
714     jenkins_job_name = models.CharField(max_length=100, blank=True, null=True, verbose_name='jenkins job name')
715     jenkins_build_number = models.IntegerField(default=0, blank=True, null=True, verbose_name='jenkins build number')
716 
717     def __unicode__(self):
718         return self.status
719 
720     def __str__(self):
721         return self.status
722 
723     class Meta:
724         verbose_name = '测试报告'
725         verbose_name_plural = '测试报告'
726 
727 
728 # 测试任务
729 class TestTask(models.Model):
730     """测试任务"""
731     TAST_STATUS_CHOICE = (
732         (0, '等待中'),
733         (1, '运行中'),
734         (2, '已完成'),
735         (3, '暂停'),
736         (4, '无效'),
737         (5, '异常')
738     )
739     TEST_LEVEL_CHOICE = (
740         ('test_suite', 'test_suite'),  # 查询 用例集 并执行
741         ('test_case', 'test_case'),  # 查询 用例 并执行
742         ('test_step', 'test_step'),  # 查询 用例步骤 并执行
743     )
744     # 任务 - 定时
745     id = models.AutoField(primary_key=True)
746     name = models.CharField(default='', max_length=100, verbose_name='名称')
747     priority = models.IntegerField(default=0, verbose_name='优先级')
748     cron = models.CharField(default='', max_length=100, verbose_name='cron表达式')
749     status = models.SmallIntegerField(choices=TAST_STATUS_CHOICE, default=0, verbose_name='状态')
750     next_run = models.CharField(blank=True, null=True, default='', max_length=50, verbose_name='下一次执行时间')
751     duration = models.IntegerField(default=0, verbose_name='耗时(秒)')
752     create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
753     creator = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, max_length=20,
754                                 verbose_name="创建人", related_name="task_creator")
755     # APScheduler job state
756     job_state = models.JSONField(blank=True, null=True, default=dict, verbose_name='job state')
757 
758     # 测试参数
759     test_env = models.ForeignKey(GlobalEnv, blank=True, null=True, related_name='test_env', on_delete=models.CASCADE, verbose_name='执行环境')
760     test_validate = models.ForeignKey(GlobalResponseValidate, blank=True, null=True, related_name='test_validate', on_delete=models.CASCADE, verbose_name='校验规则')
761     test_level = models.CharField(blank=True, null=True, choices=TEST_LEVEL_CHOICE, default='test_case', max_length=20,
762                                   verbose_name='测试类别')
763     test_filters = models.JSONField(blank=True, null=True, default=dict, verbose_name='查询筛选器')  # dict
764 
765     def __unicode__(self):
766         return self.name
767 
768     def __str__(self):
769         return self.name
770 
771     class Meta:
772         verbose_name = '测试任务'
773         verbose_name_plural = '测试任务'
774 
775 
776 def default_cron_job():
777     job_info = {
778         "id": 0,
779         "job_state": {},
780         "next_run_time": ''
781     }
782     return job_info
783 
784 
785 # 测试环境: 验证、状态监控
786 class TestEnvMonitor(BaseModel):
787     """测试环境监控: 环境验证、冒烟测试、业务巡检"""
788     id = models.AutoField(primary_key=True)
789     env = models.ForeignKey(GlobalEnv, related_name='monitor_env', on_delete=models.CASCADE, verbose_name='被监控环境')
790     validate = models.ForeignKey(GlobalResponseValidate, related_name='monitor_validate', on_delete=models.CASCADE, verbose_name='校验规则')
791     cron = models.CharField(default='', blank=True, null=True, max_length=100, verbose_name='cron表达式')
792     cron_job = models.JSONField(default=default_cron_job, verbose_name='cron_job信息')
793     build_type = models.CharField(max_length=20, choices=BUILD_TYPE_CHOICE, default='其他', verbose_name='构建类型')
794     report = models.ForeignKey(TestReport, blank=True, null=True, related_name='env_report', on_delete=models.CASCADE, verbose_name='环境报告')
795     creator = models.ForeignKey(User, on_delete=models.SET_NULL, max_length=20, null=True, verbose_name="创建人", related_name="monitor_creator")
796 
797     def __unicode__(self):
798         return self.env.name
799 
800     def __str__(self):
801         return self.env.name
802 
803     class Meta:
804         verbose_name = '测试环境监控'
805         verbose_name_plural = '测试环境监控'
View Code

 

-------- THE END --------

posted @ 2021-12-04 17:29  徒手沉浮  阅读(546)  评论(0编辑  收藏  举报