自动化测试平台设计与实现(二、自动化测试用例对象设计实现、关键字对象设计与实现)

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、 业务分析与设计:

视图操作

  1. testcase视图:

    • POST请求,根据operate字段执行不同操作:
      • create: 创建测试用例。
      • update: 更新测试用例。
      • delete: 删除测试用例。
      • show_all: 展示所有测试用例。
      • search: 搜索测试用例。
      • show_testcase: 展示单个测试用例及其关联的关键字。
  2. project视图:

    • POST请求,根据operate字段执行不同操作:
      • create: 创建项目。
      • update: 更新项目。
      • delete: 删除项目。
      • show_all: 展示所有项目。

关键函数分析

  1. create_testcase:

    • 创建测试用例并处理关联的关键字。
    • 关键字信息保存在TestCaseKeyword中间表中。
  2. update_testcase:

    • 更新测试用例及其关联的关键字。
    • 通过比较请求中的关键字与当前关键字,决定新增、修改或删除。
  3. delete_testcase:

    • 删除测试用例及其关联的关键字。
    • 级联删除通过on_delete=models.CASCADE实现。
  4. show_all_testcases:

    • 展示所有测试用例,按项目筛选。
  5. search_testcase:

    • title模糊搜索测试用例。
  6. show_testcase:

    • 展示单个测试用例及其关联的关键字。
  7. create_project:

    • 创建项目,使用序列化器验证数据。
  8. update_project:

    • 更新项目,根据update_source_name查找项目。
  9. delete_project:

    • 删除项目,根据delete_list中的项目名称批量删除。
  10. show_all_project:

    • 展示所有项目。

对于用例的keyword修改,大致逻辑:

  • 创建 unchanged_keywords 列表

    • 使用 unchanged_keywords 列表来存储在 current_keywords_dictrequest_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 模型。这样可以解决。但函数可以在代码的任何位置定义和调用。

前向引用:在字段定义中使用字符串形式的类名引用未定义的模型类。

posted @ 2024-08-06 01:38  jpx  阅读(90)  评论(0编辑  收藏  举报