自动化测试平台设计与实现(二、自动化测试用例对象设计实现、关键字对象设计与实现)
1、模型设计
建立自动化用例,关键字模型。其中自动化用例基本内容包含title(目录展示)、name等常见文本信息,关键字则是实现自动化测试提速的关键所在,考虑到业务场景的自动化,就能发现有很多业务步骤是重复的:比如一个管理系统里面的创建对象,我们将该步骤抽象出来,形成一个关键字(keyword),关键字保存url、header等信息,要录入到自动化用例testcase模型中,则使用中间表TestCaseKeyword,它会详细记录某个自动化用例的(关键字-顺序),并且关键字会带有详细的params、body等信息,也就是可以被全局变量,或者常量所覆盖。
我们的project、testcase、keyword设计理念:首先project是用例的集合,每个project内包含name、title不能重复的testcase,至于project之间如果testcase name重复则无所谓。创建testcase,要带上project信息,用project_case来标记唯一。testcase一旦创建,就固定属于某个project,没必要对testcase做project级别的迁移。因为现在以projectname+testcasetitle唯一标记一个用例,那我们的update操作,就对project内的用例才生效,show_all也是展示同project的用例,search、delete_title_list同样。
对于keyword和testcase,每个testcase以keyword+data+keyword顺序来形成一套业务流程。keyword默认只存一些url、header-key,data-key等,在testcase创建、修改过程中,填入header-value、data-value等形成可用请求,填入order标记本keyword的顺序。
断言:是自动化用例来判断是否通过的必要元素。断言设计:给keyword添加断言配置,在模型里面应该是个断言列表,每个元素是一个断言,每个断言由(目标值,比较符,被比较值组成),目标值、被比较值可以是int、string,比较符可以是数值型的大于、小于、等于、大于等于、小于等于,字符型的equal:相等、contains:被比较值包含目标值字符串、in:目标值字符串包含被比较值。 业务逻辑:1、keyword新增时,可以新增断言到断言列表,这个时候一个断言元素的被比较值非必填项、但比较符、目标值都是必填元素;新增keyword也可以不必新增断言。2、keyword修改时,同理,可以新增、修改、删除断言,但断言元素的被比较值非必填项、但比较符、目标值都是必填元素; 3、新增、修改自动化用例的时候,可以给用例的keyword新增、修改、删除断言元素,这个时候断言元素的被比较值可以填充了。
import functools import uuid import random import string from django.db import models from django.core.exceptions import ValidationError from django.utils import timezone # Create your models here. def generate_random_string(except_str, length=10): characters = string.ascii_letters + string.digits random_chars = ''.join(random.choice(characters) for _ in range(length)) return f'{except_str}_{random_chars}' if except_str else random_chars def validate_positive(value): if value < 0: raise ValidationError('%(value)s is not a positive integer or 0', params={'value': value}) class Project(models.Model): name = models.CharField(max_length=100, unique=True) description = models.TextField(blank=True, null=True) def __str__(self): return self.name class Testcase(models.Model): # 通过 related_name 访问所有关联的 Testcase 实例:testcases = project.testcases.all() project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='testcases') title_default = functools.partial(generate_random_string, "title") name_default = functools.partial(generate_random_string, "test") title = models.CharField(max_length=50, null=False, blank=False, default=title_default) name = models.CharField(max_length=50, null=False, blank=False, default=name_default) project_case = models.CharField(max_length=150, unique=True, null=False, blank=False) level = models.IntegerField(default=0, validators=[validate_positive]) precondition = models.CharField(max_length=300, null=True, blank=True, default=None) test_precondition = models.CharField(max_length=300, null=True, blank=True, default=None) expected_result = models.CharField(max_length=300, null=True, blank=True, default=None) TYPE = [ ("function_case", "功能用例"), ("performance_case", "性能用例"), ("reliability_case", "可靠性用例"), ] type = models.CharField(max_length=20, choices=TYPE, default="function_case") auto_flag = models.BooleanField(default=False, null=True, blank=True) description = models.TextField(blank=True, null=True) keywords = models.ManyToManyField("KeyWord", through='TestCaseKeyword') class Meta: # unique_together 确保在数据库层面上字段组合的唯一性约束。在 Testcase 模型中,它确保每个项目中的 title 和 name 的组合是唯一的 # 可以在数据库层面上提供额外的安全保障 unique_together = ('project', 'title', 'name') def save(self, *args, **kwargs): # 在保存对象到数据库之前或之后执行一些操作。在 Testcase 模型中,重写了 save 方法以自动生成 project_case 字段的值 self.project_case = f"{self.project.name}_{self.title}" super(Testcase, self).save(*args, **kwargs) def __str__(self): return f"{self.title}_{self.name}" class KeyWord(models.Model): BODY_TYPES = [ ('application/x-www-form-urlencoded', 'Application/X-WWW-Form-Urlencoded'), ('raw', 'Raw'), ('multipart/form-data', 'Multipart/Form-Data'), ] name_default = functools.partial(generate_random_string, "kw") name = models.CharField(max_length=100, unique=True, null=False, blank=False, default=name_default) url = models.URLField() params = models.JSONField(blank=True, null=True) headers = models.JSONField(blank=True, null=True) body_type = models.CharField(max_length=50, choices=BODY_TYPES) body = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True) def __str__(self): return self.name class TestCaseKeyword(models.Model): test_case = models.ForeignKey(Testcase, on_delete=models.CASCADE) keyword = models.ForeignKey(KeyWord, on_delete=models.CASCADE) order = models.PositiveIntegerField() params = models.JSONField(blank=True, null=True) headers = models.JSONField(blank=True, null=True) body = models.TextField(blank=True, null=True) class Meta: ordering = ['order'] def __str__(self): return f"{self.test_case.name} - {self.keyword.name} ({self.order})"
2、 业务分析与设计:
视图操作
-
testcase视图:
POST
请求,根据operate
字段执行不同操作:create
: 创建测试用例。update
: 更新测试用例。delete
: 删除测试用例。show_all
: 展示所有测试用例。search
: 搜索测试用例。show_testcase
: 展示单个测试用例及其关联的关键字。
-
project视图:
POST
请求,根据operate
字段执行不同操作:create
: 创建项目。update
: 更新项目。delete
: 删除项目。show_all
: 展示所有项目。
关键函数分析
-
create_testcase:
- 创建测试用例并处理关联的关键字。
- 关键字信息保存在
TestCaseKeyword
中间表中。
-
update_testcase:
- 更新测试用例及其关联的关键字。
- 通过比较请求中的关键字与当前关键字,决定新增、修改或删除。
-
delete_testcase:
- 删除测试用例及其关联的关键字。
- 级联删除通过
on_delete=models.CASCADE
实现。
-
show_all_testcases:
- 展示所有测试用例,按项目筛选。
-
search_testcase:
- 按
title
模糊搜索测试用例。
- 按
-
show_testcase:
- 展示单个测试用例及其关联的关键字。
-
create_project:
- 创建项目,使用序列化器验证数据。
-
update_project:
- 更新项目,根据
update_source_name
查找项目。
- 更新项目,根据
-
delete_project:
- 删除项目,根据
delete_list
中的项目名称批量删除。
- 删除项目,根据
-
show_all_project:
- 展示所有项目。
对于用例的keyword修改,大致逻辑:
-
创建
unchanged_keywords
列表:- 使用
unchanged_keywords
列表来存储在current_keywords_dict
和request_keywords_dict
中都存在,并且值未发生变化的关键字。
- 使用
-
分类关键字:
to_delete_keywords
:请求中不存在的当前关键字。to_add_or_update_keywords
:请求中新增或修改的关键字。unchanged_keywords
:在当前关键字和请求关键字中都存在且未发生变化的关键字。
-
删除不存在的关键字关联:
- 使用
to_delete_keywords
列表删除不存在的关键字关联。
- 使用
-
添加或更新关键字:
- 使用
to_add_or_update_keywords
列表添加或更新关键字及其断言。
- 使用
-
刷新未改动关键字的断言:
- 使用
unchanged_keywords
列表刷新未改动关键字的断言。
- 使用
对应请求体结构,统一设计为:
{ "operate": "create/update/delete/show_all/search", "project_name": "projectA", "parameters": { "title": "title", "name": "name", "level": "level", "precondition": "precondition", "test_precondition": "test_precondition", "expected_result": "expected_result", "type": "type", "auto_flag": "auto_flag", "update_source_title": "update_source_title", "delete_title_list": [], "description": "description", "keywords": [ { "name": "Example API Request", "order": 1, "params": {"param1": "value1_param1", "param2": "value1_param2"}, "headers": {"Authorization": "Bearer value1_token"}, "body": "value1_body" }, { "name": "keyword2", "order": 2, "params": {"param1": "value2_param1", "param2": "value2_param2"}, "headers": {"Authorization": "Bearer value2_token"}, "body": "value2_body" } ] } }
3、补充断言设置:断言既可在keyword创建时新增断言,keyword修改时新增、修改、删除断言;也可在创建用例时,这个时候建立与keyword的联系、这个时候刷新断言;或者修改用例的时候,既修改了关键字,也同步了所有关键字的断言。
tips:
1. python有前向引用:如果在一个模型类中引用了另一个尚未定义的模型类,可能会出现 Unresolved reference
错误。
可以在 Testcase
模型中使用前向引用,即在字符串中引用 KeyWord
模型。这样可以解决。但函数可以在代码的任何位置定义和调用。
前向引用:在字段定义中使用字符串形式的类名引用未定义的模型类。